npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@simtlix/simfinity-js-client

v1.0.2

Published

Introspection-driven GraphQL client for simfinity-js APIs

Downloads

313

Readme

@simtlix/simfinity-js-client

Introspection-driven GraphQL client for simfinity-js APIs. It discovers your schema at runtime via introspection and exposes a chainable query builder, CRUD helpers, aggregate queries, state-machine transitions, schema metadata access, mutation input transformation, and more — no code generation required.

Installation

npm install @simtlix/simfinity-js-client

Quick Start

import SimfinityClient from '@simtlix/simfinity-js-client';

const client = new SimfinityClient('http://localhost:3000/graphql');
await client.init();

const series = await client.find('serie').exec();
console.log(series);

API Reference

new SimfinityClient(endpoint)

Creates a new client instance pointing at the given GraphQL endpoint.

client.init()

Runs an introspection query against the endpoint and builds internal registries of types, queries, and mutations — including Simfinity-specific field extensions (relation metadata, state machine flags, read-only markers). Must be called before any other method.

Schema Inspection

| Method | Returns | |---|---| | client.getTypes() | All discovered types (objects, enums, scalars, inputs) | | client.getQueries() | All root query fields | | client.getMutations() | All root mutation fields |


Query Builder — client.find(typeName)

Returns a chainable QueryBuilder. Call .exec() to execute.

const results = await client.find('serie')
  .where('categories', 'EQ', 'Drama')
  .joinObject('director', 'name country')
  .joinCollection('seasons', 'number year state')
  .joinCollection('seasons.episodes', 'number name')
  .fields('id name categories')
  .page(1, 10)
  .sort('name', 'ASC')
  .exec();

.where(field, operator, value [, value2])

Filter by a scalar field. Supported operators depend on the server schema (e.g. EQ, NE, GT, LT, GTE, LTE, BETWEEN, LIKE, NIN).

.where(field, terms)

Filter by a collection field using an array of term objects:

.where('seasons', [
  { path: 'episodes.name', operator: 'EQ', value: 'Pilot' }
])

.joinObject(path, fields)

Include a related object's fields in the selection.

.joinCollection(path, fields [, filter])

Include a related collection's fields. Supports dot-notation for nested joins (e.g. 'seasons.episodes'). An optional filter array of terms can be passed.

.fields(selectionString)

Space-separated list of scalar fields to select on the root type. If omitted, all scalar and enum fields are selected automatically.

.autoSelect()

Automatically builds a full selection set using schema metadata, including nested object fields with display field resolution. Returns the builder for chaining. The selection metadata is available via builder._selectionMeta.

const results = await client.find('serie')
  .autoSelect()
  .page(1, 10)
  .exec();

.page(page, size [, count])

Paginate results. page is 1-based. Set count to true to include total count in response extensions.

.sort(field, order)

Sort results. order is 'ASC' or 'DESC'. Can be called multiple times for multi-field sorting.

.exec()

Executes the query and returns the result array. If the server returns extensions (e.g. pagination count), they are attached as result.__extensions.

.execWithMeta()

Executes the query and returns a { data, extensions } wrapper for clean access to response metadata:

const { data: series, extensions } = await client.find('serie')
  .page(1, 10, true)
  .execWithMeta();

console.log(series);              // the result array
console.log(extensions?.count);   // total count from server

Aggregate Builder — client.aggregate(typeName)

Returns a chainable AggregateBuilder. Call .exec() to execute.

const results = await client.aggregate('season')
  .groupBy('serie')
  .fact('COUNT', 'seasonCount', 'id')
  .fact('AVG', 'avgYear', 'year')
  .page(1, 5)
  .sort('serie', 'ASC')
  .exec();

.groupBy(field)

Required. The field to group by.

.fact(operation, factName, path)

Add an aggregation fact. operation can be COUNT, SUM, AVG, MIN, MAX, etc.

.where(), .page(), .sort()

Same API as the Query Builder.


Get by ID

const serie = await client.getById('serie', '507f1f77bcf86cd799439011', 'id name categories');

If fields is omitted, all scalar fields are returned.


Collection Item Queries — client.findByParent()

Query collection items filtered by their parent entity:

const seasons = await client.findByParent('season', 'serie', parentSerieId)
  .page(1, 10, true)
  .fields('id number year state')
  .exec();

Returns a QueryBuilder pre-configured with a connection field filter and default sort by id ASC. Chain additional .where(), .page(), .fields(), .sort() as needed.


FK/Relation Search — client.search()

Search-as-you-type for FK reference fields:

const results = await client.search('Genre', 'com', {
  displayField: 'name',
  page: 1,
  size: 10,
});
// [{ id: '1', name: 'Comedy' }, { id: '2', name: 'Romantic Comedy' }]

Automatically selects the appropriate operator (LIKE for strings, EQ for numeric/boolean) and casts the search term to the field's scalar type.

| Option | Default | Description | |---|---|---| | displayField | auto-resolved (name or first scalar) | Field to search by | | page | 1 | Page number | | size | 10 | Page size |


CRUD Mutations

const created = await client.add('star', { name: 'John Doe' }, 'id name');

const updated = await client.update('star', createdId, { name: 'Jane Doe' }, 'id name');

const deleted = await client.delete('star', createdId, 'id name');

Automatic Input Transformation

Pass { transform: true } as the last argument to have the client automatically clean mutation input based on schema metadata — stripping __typename, coercing scalar types, extracting FK IDs, handling embedded objects, and skipping read-only/state-machine fields:

const created = await client.add('serie', rawFormData, 'id name', { transform: true });

const updated = await client.update('serie', id, rawFormData, 'id name', {
  transform: true,
  skipFields: ['temporaryField'],
});

Mutation Input Transformation — client.transformInput()

Schema-aware mutation input cleaning, usable standalone:

const cleaned = client.transformInput('serie', rawInput, {
  mode: 'create',           // or 'update'
  skipFields: [],            // fields to exclude
  transientFields: [],       // transient fields to exclude
});

Handles: __typename removal, numeric/date coercion, embedded object cleaning, FK reference extraction ({ id } only), and skipping read-only, state-machine, and collection fields.


Collection Delta Mutations — client.transformCollectionDelta()

Build the { added, updated, deleted } structure for collection field mutations:

const delta = {
  added: [{ id: 'temp_1', number: '3', year: '2025', __status: 'added' }],
  updated: [{ id: 'real-id', number: '2', year: '2024', __status: 'modified' }],
  deleted: [{ id: 'del-id' }],
};

const cleaned = client.transformCollectionDelta('season', delta, {
  connectionField: 'serie',
});
// { added: [{ number: 3, year: 2025 }], updated: [{ id: 'real-id', number: 2, year: 2024 }], deleted: ['del-id'] }

Strips metadata (__status, __originalData, __typename), removes temporary IDs and connection fields, coerces scalar types, and extracts FK object IDs.


State Machine Transitions

const activated = await client.transition('season', 'activate', seasonId, 'id number state');

Optionally pass extra input fields:

await client.transition('season', 'activate', seasonId, { extraField: 'value' }, 'id number state');

Custom Mutations

const result = await client.customMutation('myCustomMutation', { arg1: 'value' }, 'id status');

Raw GraphQL Execution

const response = await client.execute(
  '{ stars(pagination: { page: 1, size: 2 }) { id name } }'
);
console.log(response.data);

Selection Set Building — client.buildSelectionSet()

Generate a complete GraphQL selection set string with metadata from the schema, useful for building dynamic UIs:

const { selection, columns, sortFieldByColumn, fieldTypeByColumn } =
  client.buildSelectionSet('serie');

Returns:

| Property | Type | Description | |---|---|---| | selection | string | GraphQL selection set string (includes nested object sub-selections) | | columns | string[] | Display column names (excludes id) | | sortFieldByColumn | object | Maps column name to sort field path (e.g. director -> director.name) | | fieldTypeByColumn | object | Maps column name to scalar type name |


Schema Metadata Access

Field Extensions

const ext = client.getFieldExtensions('serie', 'director');
// { relation: { displayField: 'name', embedded: true, connectionField: null }, stateMachine: null, readOnly: null }

| Method | Signature | Description | |---|---|---| | getFieldExtensions | (typeName, fieldName) | Full extensions object for a field | | getDisplayField | (typeName, fieldName) | Display field with fallback chain (extension -> name -> first scalar) | | isEmbeddedField | (typeName, fieldName) | Whether the field is an embedded object | | getConnectionField | (typeName, fieldName) | Back-reference field name for collections | | isStateMachineField | (typeName, fieldName) | Whether the field is state-machine managed | | isReadOnlyField | (typeName, fieldName) | Whether the field is read-only | | getEnumValues | (typeName) | Enum values for an enum type | | getFieldsOfType | (typeName) | All fields with full metadata | | getDescriptionFieldType | (typeName, fieldName) | Scalar type name of a display field |

Entity & Query Name Resolution

client.getTypeNameForQuery('series');           // 'serie'
client.getPluralQueryName('serie');              // 'series'
client.getSingularQueryName('serie');            // 'serie'
client.getListEntityNames();                    // ['episodes', 'seasons', 'series', 'stars', ...]
client.getListEntityNamesOfType('serie');        // ['series']
client.getQueryNamesForType('serie');
// { pluralQueryName: 'series', singularQueryName: 'serie', aggregateQueryName: 'series_aggregate' }

Scalar Type Utilities

Simfinity uses validated scalar names like SeasonNumber_Int where the suffix after _ indicates the base type:

client.getActualScalarType('SeasonNumber_Int');  // 'Int'
client.isNumericScalar('SeasonNumber_Int');       // true
client.isBooleanScalar('Boolean');               // true
client.isDateTimeScalar('StartDate_DateTime');   // true

State Machine Metadata

client.getStateMachineFields('season');          // ['state']
client.getAvailableTransitions('season');
// [{ action: 'activate', mutationName: 'activate_season' }, { action: 'finalize', mutationName: 'finalize_season' }]

Examples

See examples/demo.js for a comprehensive walkthrough covering all features: schema discovery, filtering, joins, pagination, sorting, CRUD, state machine transitions, aggregates, raw queries, schema metadata, selection set building, entity resolution, input transformation, collection deltas, parent queries, FK search, and response extensions.

GRAPHQL_ENDPOINT=http://localhost:3000/graphql npm run demo

License

Apache-2.0