@vantreeseba/drizzle-graphql 7.3.0 → 7.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -3,6 +3,7 @@ import { MySqlDatabase } from 'drizzle-orm/mysql-core';
3
3
  import { PgAsyncDatabase } from 'drizzle-orm/pg-core';
4
4
  import { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core';
5
5
  import { GraphQLScalarType, GraphQLOutputType, GraphQLInputType, GraphQLFieldResolver, GraphQLResolveInfo, GraphQLSchema, GraphQLInputObjectType, GraphQLObjectType, GraphQLNonNull, GraphQLList, GraphQLEnumType } from 'graphql';
6
+ import { ResolveTree } from 'graphql-parse-resolve-info';
6
7
  export { GraphQLDate, GraphQLDateTime, GraphQLJSON, GraphQLUUID } from 'graphql-scalars';
7
8
 
8
9
  /**
@@ -197,6 +198,11 @@ type OrderByArgs<TTable extends Table> = {
197
198
  };
198
199
  };
199
200
 
201
+ /** Optional mapper from table key to singular/plural name pair. Return undefined to use default naming for a table. */
202
+ type TypeNameMapper = (tableName: string) => {
203
+ singular: string;
204
+ plural: string;
205
+ } | undefined;
200
206
  /**
201
207
  * Extracts the join column info from a drizzle-orm v1 Relation object.
202
208
  * Returns the JS property name of the local column on the parent table and the
@@ -632,27 +638,39 @@ type GeneratedData<TDatabase extends AnyDrizzleDB<any>> = {
632
638
  entities: GeneratedEntities<TDatabase>;
633
639
  };
634
640
  /**
635
- * Per-feature switches for what `buildSchema` generates. Every flag defaults to `true`.
641
+ * A feature switch. `true` / `false` decides for the whole build; a predicate decides per
642
+ * table, receiving the table's key in the Drizzle schema.
643
+ *
644
+ * ```ts
645
+ * features: { delete: (table) => table !== 'auditLog' }
646
+ * ```
647
+ */
648
+ type FeatureSwitch = boolean | ((tableName: string) => boolean);
649
+ /**
650
+ * Per-feature switches for what `buildSchema` generates. Every flag defaults to `true` except
651
+ * `upsert`, `nestedWrites`, `fieldUpdateOperations`, `countMutations` and `requireWhere`.
652
+ * Each one takes a boolean or a per-table predicate, apart from `nestedWrites`, which is
653
+ * build-wide.
636
654
  * See {@link BuildSchemaConfig.features}.
637
655
  */
638
656
  type SchemaFeatures = {
639
657
  /** `<plural>Aggregate` root queries and the aggregate output types. @default true */
640
- aggregates?: boolean;
658
+ aggregates?: FeatureSwitch;
641
659
  /**
642
660
  * `<plural>GroupBy` root queries — the same aggregates, one row per group. Requires
643
661
  * `aggregates`, whose output types the grouped result reuses.
644
662
  *
645
663
  * @default true
646
664
  */
647
- groupBy?: boolean;
665
+ groupBy?: FeatureSwitch;
648
666
  /** `<relation>Aggregate` fields on object types, for to-many relations. @default true */
649
- relationAggregates?: boolean;
667
+ relationAggregates?: FeatureSwitch;
650
668
  /** The `distinct` argument on list queries. @default true */
651
- distinct?: boolean;
669
+ distinct?: FeatureSwitch;
652
670
  /** `create<Table>` / `create<Table>Single` mutations. @default true */
653
- insert?: boolean;
671
+ insert?: FeatureSwitch;
654
672
  /** `update<Table>` / `update<Table>Single` mutations. @default true */
655
- update?: boolean;
673
+ update?: FeatureSwitch;
656
674
  /**
657
675
  * `update<Table>Many` mutations — batch update with a per-entry `set` and `where`,
658
676
  * executed inside one transaction. Only generated when `update` is also enabled,
@@ -660,9 +678,9 @@ type SchemaFeatures = {
660
678
  *
661
679
  * @default true
662
680
  */
663
- updateMany?: boolean;
681
+ updateMany?: FeatureSwitch;
664
682
  /** `delete<Table>` / `delete<Table>Single` mutations. @default true */
665
- delete?: boolean;
683
+ delete?: FeatureSwitch;
666
684
  /**
667
685
  * `upsert<Table>` / `upsert<Table>Single` mutations — insert, or update the row that
668
686
  * already holds the same unique key. Off unless asked for, so an existing schema does
@@ -670,7 +688,7 @@ type SchemaFeatures = {
670
688
  *
671
689
  * @default false
672
690
  */
673
- upsert?: boolean;
691
+ upsert?: FeatureSwitch;
674
692
  /**
675
693
  * Relation fields on `Create<Table>Input` and `Update<Table>Input`, so a parent row and
676
694
  * the rows it is related to can be written in one mutation. Off unless asked for: it adds
@@ -728,7 +746,7 @@ type SchemaFeatures = {
728
746
  *
729
747
  * @default false
730
748
  */
731
- fieldUpdateOperations?: boolean;
749
+ fieldUpdateOperations?: FeatureSwitch;
732
750
  /**
733
751
  * `update<Table>Count` / `delete<Table>Count` mutations — the same write as the plural
734
752
  * `update<Table>` / `delete<Table>`, returning the number of rows affected instead of the
@@ -747,7 +765,7 @@ type SchemaFeatures = {
747
765
  *
748
766
  * @default false
749
767
  */
750
- countMutations?: boolean;
768
+ countMutations?: FeatureSwitch;
751
769
  /**
752
770
  * Makes `where` non-null on the plural `update<Table>` / `delete<Table>` mutations, and
753
771
  * rejects a `where` that collapses to no filters (e.g. `where: {}`), so a schema can rule
@@ -758,7 +776,7 @@ type SchemaFeatures = {
758
776
  *
759
777
  * @default false
760
778
  */
761
- requireWhere?: boolean;
779
+ requireWhere?: FeatureSwitch;
762
780
  };
763
781
  /**
764
782
  * Per-field cost hints for `graphql-query-complexity` (or any estimator that reads
@@ -912,11 +930,11 @@ type BuildSchemaConfig = {
912
930
  */
913
931
  complexity?: boolean | ComplexityConfig;
914
932
  /**
915
- * Turns individual generated features off.
933
+ * Turns individual generated features off, for the whole build or per table.
916
934
  *
917
- * Every flag defaults to `true` except `upsert`, which is opt-in: a build with no
918
- * `features` block generates exactly what it generated before this option existed.
919
- * Turning one off removes both the schema surface it adds and the work behind it.
935
+ * Every flag defaults to `true` except `upsert` and `nestedWrites`, which are opt-in: a
936
+ * build with no `features` block generates exactly what it generated before this option
937
+ * existed. Turning one off removes both the schema surface it adds and the work behind it.
920
938
  *
921
939
  * ```ts
922
940
  * buildSchema(db, {
@@ -930,9 +948,34 @@ type BuildSchemaConfig = {
930
948
  * });
931
949
  * ```
932
950
  *
933
- * Turning off every mutation feature omits the `Mutation` type entirely, exactly as
934
- * `mutations: false` does. Query-side features can all be off — the list and single
935
- * queries are always generated, so `Query` is never empty.
951
+ * Any flag may instead be a predicate, which is asked once per table with the table's key
952
+ * in the Drizzle schema — the usual case, where a handful of tables must not be written
953
+ * through the API and the rest want the full set:
954
+ *
955
+ * ```ts
956
+ * buildSchema(db, {
957
+ * features: {
958
+ * delete: (table) => table !== 'auditLog',
959
+ * update: (table) => !ownedByCustomResolver.has(table),
960
+ * upsert: (table) => table === 'settings',
961
+ * },
962
+ * });
963
+ * ```
964
+ *
965
+ * A table that generates nothing at all belongs in {@link BuildSchemaConfig.exclude}; this
966
+ * decides which operations a table that *is* in the schema offers. `nestedWrites` is the one
967
+ * flag that stays build-wide: its plans are computed once over the whole relation graph, and
968
+ * a nested write reaches a second table by definition.
969
+ *
970
+ * Some operations are built out of others — an upsert writes through insert and update, a
971
+ * batch update reuses the update input, a grouped query reuses the aggregate types. Flags
972
+ * that contradict those implications warn on the console at build time (naming the tables)
973
+ * rather than leaving a second write path past the operation you turned off to be found
974
+ * later.
975
+ *
976
+ * Turning off every mutation feature for every table omits the `Mutation` type entirely,
977
+ * exactly as `mutations: false` does. Query-side features can all be off — the list and
978
+ * single queries are always generated, so `Query` is never empty.
936
979
  */
937
980
  features?: SchemaFeatures;
938
981
  /**
@@ -1045,11 +1088,23 @@ type BuildSchemaConfig = {
1045
1088
  * Example: `(name) => name === 'users' ? { singular: 'user', plural: 'users' } : undefined`
1046
1089
  * produces type `User`, queries `users` / `user`, mutations `createUsers` / `createUser` for
1047
1090
  * the `users` table, and leaves other tables with their default names.
1091
+ *
1092
+ * Pass the string `'singularize'` for the built-in preset, which derives both forms of every
1093
+ * table key with the library's own `pluralize` — the mapper most schemas with plural table
1094
+ * keys would otherwise write by hand:
1095
+ *
1096
+ * ```ts
1097
+ * buildSchema(db, { typeNameMapper: 'singularize' });
1098
+ * // tasks -> type Task, queries `tasks` / `task`, mutations `createTasks` / `createTask`
1099
+ * ```
1100
+ *
1101
+ * The same function is exported as `singularizeMapper` for wrapping — e.g. to keep one
1102
+ * table's names as they are: `(t) => (t === 'audit_log' ? undefined : singularizeMapper(t))`.
1048
1103
  */
1049
- typeNameMapper?: (tableName: string) => {
1104
+ typeNameMapper?: 'singularize' | ((tableName: string) => {
1050
1105
  singular: string;
1051
1106
  plural: string;
1052
- } | undefined;
1107
+ } | undefined);
1053
1108
  /**
1054
1109
  * Controls whether a relation is eagerly pre-fetched via Drizzle's `with:` clause
1055
1110
  * when its parent is loaded through a generated query or mutation.
@@ -1247,6 +1302,129 @@ type BuildSchemaConfig = {
1247
1302
  enumNameMapper?: EnumNameMapper;
1248
1303
  };
1249
1304
 
1305
+ /**
1306
+ * The `typeNameMapper` preset behind `typeNameMapper: 'singularize'`.
1307
+ *
1308
+ * Derives the singular and plural forms of a table key with the library's own `pluralize`,
1309
+ * which is what nearly every consumer with plural table keys (`export const tasks =
1310
+ * pgTable('tasks', …)`) was writing by hand — and adding `pluralize` to their own
1311
+ * dependencies to write.
1312
+ *
1313
+ * `tasks` becomes type `Task`, queries `tasks` / `task`, mutations `createTasks` /
1314
+ * `createTask`. A key that is already singular is pluralized for the list side, so `task`
1315
+ * yields the same names, and a capitalized key (`Tasks`) yields them too — the field forms
1316
+ * are uncapitalized so the generated names follow GraphQL's camelCase convention whatever
1317
+ * the schema key looks like. The type name is capitalized by the generator either way.
1318
+ */
1319
+ declare const singularizeMapper: (tableName: string) => {
1320
+ singular: string;
1321
+ plural: string;
1322
+ };
1323
+
1324
+ /**
1325
+ * The identity a generated field publishes about itself, under `extensions.drizzle`.
1326
+ *
1327
+ * Every field this library generates carries one, so a wrapper — `graphql-middleware`,
1328
+ * envelop, `graphql-shield`, an audit log, a cache invalidator — can ask the schema what a
1329
+ * field is instead of reverse-engineering its name. Names are configurable (`prefixes`,
1330
+ * `suffixes`, `typeNameMapper`), so name parsing is ambiguous by construction; this is not.
1331
+ *
1332
+ * This shape is stable API: fields are only ever added to it, never removed or repurposed.
1333
+ */
1334
+ /** What family of field this is. */
1335
+ type DrizzleFieldKind = 'query' | 'mutation' | 'relation' | 'aggregate' | 'type';
1336
+ /**
1337
+ * What the field does. Arity is carried separately by `single`, so `select` covers both
1338
+ * the list query and its `…Single` sibling rather than being spelled twice.
1339
+ */
1340
+ type DrizzleFieldOperation = 'select' | 'insert' | 'update' | 'updateMany' | 'upsert' | 'delete' | 'aggregate' | 'groupBy' | 'relation' | 'relationAggregate';
1341
+ /** The argument holding the rows a field is about, when it has one. */
1342
+ type DrizzleTargetArg = 'where' | 'values' | 'updates';
1343
+ /** `extensions.drizzle` on a generated field. */
1344
+ type DrizzleFieldExtension = {
1345
+ /** The Drizzle schema key of the table the field reads or writes. */
1346
+ table: string;
1347
+ kind: Exclude<DrizzleFieldKind, 'type'>;
1348
+ operation: DrizzleFieldOperation;
1349
+ /** Whether the field resolves to one row (or one aggregate) rather than a list. */
1350
+ single: boolean;
1351
+ /**
1352
+ * Which argument names the rows: `where` for select/update/delete/aggregate, `values`
1353
+ * for insert and upsert, `updates` for the batch update's per-entry `{ where, set }`.
1354
+ * Absent when the field takes no such argument.
1355
+ */
1356
+ targetArg?: DrizzleTargetArg;
1357
+ /**
1358
+ * Primary-key property names of `table`, in schema order. Empty when the table declares
1359
+ * no primary key. Carried here so {@link identifyRows} works from the field alone.
1360
+ */
1361
+ primaryKey: readonly string[];
1362
+ /** For `kind: 'relation'` and `operation: 'relationAggregate'`: the relation's name. */
1363
+ relation?: string;
1364
+ /** For `kind: 'relation'` and `operation: 'relationAggregate'`: the table it hangs off. */
1365
+ parentTable?: string;
1366
+ };
1367
+ /** `extensions.drizzle` on a generated object type. */
1368
+ type DrizzleTypeExtension = {
1369
+ /** The Drizzle schema key this type was generated from. */
1370
+ table: string;
1371
+ kind: 'type';
1372
+ /** Primary-key property names of `table`, in schema order. Empty when there is none. */
1373
+ primaryKey: readonly string[];
1374
+ };
1375
+ type DrizzleExtension = DrizzleFieldExtension | DrizzleTypeExtension;
1376
+ /** Anything carrying GraphQL `extensions` — a field config, a field, or a named type. */
1377
+ type WithExtensions = {
1378
+ extensions?: {
1379
+ drizzle?: unknown;
1380
+ } | null;
1381
+ } | null | undefined;
1382
+ /**
1383
+ * Reads the `drizzle` extension off a field or type, typed. Returns `undefined` for
1384
+ * anything this library did not generate, which is the check a wrapper wants before it
1385
+ * treats a field as one of ours.
1386
+ */
1387
+ declare const drizzleExtension: (target: WithExtensions) => DrizzleExtension | undefined;
1388
+ /** Narrows to the field form — object types carry `kind: 'type'` and no operation. */
1389
+ declare const isDrizzleFieldExtension: (value: DrizzleExtension | undefined) => value is DrizzleFieldExtension;
1390
+ /** The rows a field's arguments name. */
1391
+ type IdentifiedRows = {
1392
+ /** The Drizzle schema key of the table being read or written. */
1393
+ table: string;
1394
+ /** Primary-key property names, in schema order. */
1395
+ primaryKey: readonly string[];
1396
+ /** One entry per identified row: the primary-key values, keyed by property name. */
1397
+ rows: Record<string, unknown>[];
1398
+ /**
1399
+ * Whether `rows` is exactly the set the operation affects.
1400
+ *
1401
+ * `false` means the arguments do not pin the rows down — an insert whose key the
1402
+ * database generates, a `where` that filters on something other than the primary key,
1403
+ * a plural update with no filter at all, or a table with no primary key. `rows` is then
1404
+ * a best-effort subset-free superset: every entry in it is a row the key values name,
1405
+ * but other filters may narrow the real set further, and it may be empty.
1406
+ */
1407
+ complete: boolean;
1408
+ };
1409
+ /**
1410
+ * The rows a generated field's arguments are about, as primary-key values.
1411
+ *
1412
+ * The row identity of a mutation lives in a different argument for every operation —
1413
+ * `where` for update and delete, `values` for insert and upsert, a per-entry `where` inside
1414
+ * `updates` for the batch update — and each of those has both a one-row and a many-row
1415
+ * spelling. This reads whichever one applies:
1416
+ *
1417
+ * ```ts
1418
+ * const field = info.parentType.getFields()[info.fieldName];
1419
+ * const target = identifyRows(field, args);
1420
+ * if (target?.complete) await invalidate(target.table, target.rows);
1421
+ * ```
1422
+ *
1423
+ * Returns `undefined` for a field this library did not generate, or an object type. Check
1424
+ * `complete` before treating `rows` as the affected set — see {@link IdentifiedRows}.
1425
+ */
1426
+ declare const identifyRows: (field: WithExtensions, args: Record<string, unknown> | undefined) => IdentifiedRows | undefined;
1427
+
1250
1428
  /**
1251
1429
  * A 64-bit integer. Always transported as a decimal string, in both directions, so no value
1252
1430
  * is ever silently rounded by JSON's double-precision numbers. `graphql-scalars`' own
@@ -1262,6 +1440,75 @@ declare const GraphQLBigIntString: GraphQLScalarType<string, string>;
1262
1440
  */
1263
1441
  declare const GraphQLDecimalString: GraphQLScalarType<string, string>;
1264
1442
 
1443
+ /** Options shared by {@link selectionToWith} and {@link resolveSelection}. */
1444
+ type SelectionOptions = {
1445
+ /** The drizzle instance the schema was built from. */
1446
+ db: AnyDrizzleDB<any>;
1447
+ /** The Drizzle schema key of the table the selection is rooted at. */
1448
+ table: string;
1449
+ /**
1450
+ * The `typeNameMapper` the schema was built with, when one was configured. The translation
1451
+ * matches selections against GraphQL type names, so a build that renames its types has to
1452
+ * pass the same mapper here or nothing will match.
1453
+ */
1454
+ typeNameMapper?: TypeNameMapper;
1455
+ };
1456
+ /**
1457
+ * Translates a GraphQL selection set into the relational query builder's `with` tree — the
1458
+ * same translation the generated resolvers use for their own queries.
1459
+ *
1460
+ * A resolver that replaces a generated one has to return a row shaped like the type it
1461
+ * replaced, and the field resolvers underneath only fill in relations the parent did not
1462
+ * already provide. Reproducing that by hand means resolving fragments against
1463
+ * `info.fragments`, intersecting selection names against the relations config (so scalars
1464
+ * and consumer-added fields drop out instead of becoming invalid `with` keys), following
1465
+ * aliases, compiling each relation's `where` / `orderBy` / `limit` / `offset` arguments into
1466
+ * its nested entry, and leaving `<relation>Aggregate` selections out — they are not `with`
1467
+ * entries at all. This does all of it.
1468
+ *
1469
+ * ```ts
1470
+ * const withTree = selectionToWith(info, { db, table: 'Users' });
1471
+ * const row = await tx.query.Users.findFirst({ where: { id: { eq } }, with: withTree });
1472
+ * ```
1473
+ *
1474
+ * Returns `undefined` when the selection asks for no relations, which is the value the
1475
+ * query builder wants for "no `with` clause".
1476
+ */
1477
+ declare const selectionToWith: (info: GraphQLResolveInfo | ResolveTree, options: SelectionOptions) => Record<string, any> | undefined;
1478
+ /** Options for {@link resolveSelection} — the selection options plus the read itself. */
1479
+ type ResolveSelectionOptions = SelectionOptions & {
1480
+ /**
1481
+ * The transaction (or other drizzle executor) to read on. Defaults to `db`, so an override
1482
+ * that has opened its own transaction passes it here to see its own uncommitted writes.
1483
+ */
1484
+ executor?: any;
1485
+ /** Whether to return one row or a list. A single read ignores `limit`. */
1486
+ single?: boolean;
1487
+ /** The generated `where` argument, in the schema's filter DSL — compiled the same way. */
1488
+ where?: any;
1489
+ /** The generated `orderBy` argument. */
1490
+ orderBy?: any;
1491
+ limit?: number;
1492
+ offset?: number;
1493
+ /** The generated `after` cursor argument, for keyset pagination. */
1494
+ after?: string;
1495
+ };
1496
+ /**
1497
+ * Runs the read a generated select resolver would have run, against whichever executor is
1498
+ * given — the whole body, not just the `with` tree: selected columns, relation arguments,
1499
+ * eager loading, cursor pagination, and the transport remapping of the result.
1500
+ *
1501
+ * This is what an override usually wants after it has done its own work:
1502
+ *
1503
+ * ```ts
1504
+ * const rows = await resolveSelection(info, { db, table: 'Users', where, executor: tx });
1505
+ * ```
1506
+ *
1507
+ * Relation arguments and aggregate fields keep behaving as they do in the generated schema,
1508
+ * because it is the generated schema's own code path.
1509
+ */
1510
+ declare const resolveSelection: (info: GraphQLResolveInfo | ResolveTree, options: ResolveSelectionOptions) => Promise<any>;
1511
+
1265
1512
  declare const buildSchema: <TDbClient extends AnyDrizzleDB<any>>(db: TDbClient, config?: BuildSchemaConfig) => GeneratedData<TDbClient>;
1266
1513
 
1267
- export { type AggregateResolver, type AnyDrizzleDB, type BuildSchemaConfig, type ColumnDeprecator, type ColumnDescriber, type ColumnDocInfo, type ColumnTypeMapper, type ColumnTypeMapperInfo, type ComplexityConfig, type DeleteResolver, type DeleteSingleResolver, type EnumNameInfo, type EnumNameMapper, type ExtractRelations, type ExtractTableByName, type ExtractTableRelations, type ExtractTables, type GeneratedData, type GeneratedEntities, type GeneratedInputs, type GeneratedOutputs, GraphQLBigIntString, GraphQLDecimalString, type InsertArrResolver, type InsertResolver, type LimitsConfig, type MutationReturnlessResult, type MutationsCore, type QueriesCore, type RelationDescriber, type RelationResolverFactory, type ScalarOverride, type ScalarOverridesConfig, type SchemaDocs, type SchemaExclusions, type SchemaFeatures, type SelectResolver, type SelectSingleResolver, type TableDescriber, type TableLimitPolicy, type TableNamedRelations, type UpdateManyArgs, type UpdateManyEntry, type UpdateManyResolver, type UpdateResolver, type UpdateSingleResolver, type UpsertArgs, type UpsertArrResolver, type UpsertConflictArgs, type UpsertResolver, buildSchema, createRelationResolverFactory, defaultErrorMapper, drizzleExecutorKey, extractFilters, extractOrderBy, extractRelationJoinColumns };
1514
+ export { type AggregateResolver, type AnyDrizzleDB, type BuildSchemaConfig, type ColumnDeprecator, type ColumnDescriber, type ColumnDocInfo, type ColumnTypeMapper, type ColumnTypeMapperInfo, type ComplexityConfig, type DeleteResolver, type DeleteSingleResolver, type DrizzleExtension, type DrizzleFieldExtension, type DrizzleFieldKind, type DrizzleFieldOperation, type DrizzleTargetArg, type DrizzleTypeExtension, type EnumNameInfo, type EnumNameMapper, type ExtractRelations, type ExtractTableByName, type ExtractTableRelations, type ExtractTables, type FeatureSwitch, type GeneratedData, type GeneratedEntities, type GeneratedInputs, type GeneratedOutputs, GraphQLBigIntString, GraphQLDecimalString, type IdentifiedRows, type InsertArrResolver, type InsertResolver, type LimitsConfig, type MutationReturnlessResult, type MutationsCore, type QueriesCore, type RelationDescriber, type RelationResolverFactory, type ResolveSelectionOptions, type ScalarOverride, type ScalarOverridesConfig, type SchemaDocs, type SchemaExclusions, type SchemaFeatures, type SelectResolver, type SelectSingleResolver, type SelectionOptions, type TableDescriber, type TableLimitPolicy, type TableNamedRelations, type UpdateManyArgs, type UpdateManyEntry, type UpdateManyResolver, type UpdateResolver, type UpdateSingleResolver, type UpsertArgs, type UpsertArrResolver, type UpsertConflictArgs, type UpsertResolver, buildSchema, createRelationResolverFactory, defaultErrorMapper, drizzleExecutorKey, drizzleExtension, extractFilters, extractOrderBy, extractRelationJoinColumns, identifyRows, isDrizzleFieldExtension, resolveSelection, selectionToWith, singularizeMapper };