fraiseql 2.2.1 → 2.9.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.js CHANGED
@@ -48,7 +48,6 @@ __export(index_exports, {
48
48
  Subscription: () => Subscription,
49
49
  TimeoutError: () => TimeoutError,
50
50
  Type: () => Type,
51
- config: () => config,
52
51
  email: () => email,
53
52
  enum_: () => enum_,
54
53
  executeWithRetry: () => executeWithRetry,
@@ -227,10 +226,12 @@ function extractFunctionSignature(_name, params, returnType) {
227
226
 
228
227
  // src/registry.ts
229
228
  var VALID_REST_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
230
- function normaliseConfig(config2) {
229
+ var VALID_INPUT_STYLES = /* @__PURE__ */ new Set(["flatten", "jsonb"]);
230
+ var INJECT_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
231
+ var INJECT_SOURCE_RE = /^jwt:[A-Za-z_][A-Za-z0-9_]*$/;
232
+ function normaliseConfig(config, argNames) {
231
233
  const keyMap = {
232
234
  sqlSource: "sql_source",
233
- sqlSourceDispatch: "sql_source_dispatch",
234
235
  autoParams: "auto_params",
235
236
  jsonbColumn: "jsonb_column",
236
237
  cacheTtlSeconds: "cache_ttl_seconds",
@@ -239,32 +240,65 @@ function normaliseConfig(config2) {
239
240
  relayCursorColumn: "relay_cursor_column",
240
241
  relayCursorType: "relay_cursor_type",
241
242
  requiresRole: "requires_role",
242
- additionalViews: "additional_views"
243
+ additionalViews: "additional_views",
244
+ inputStyle: "input_style",
245
+ changelogPreImage: "changelog_pre_image"
243
246
  };
244
- const hasRestPath = "restPath" in config2 && config2.restPath != null;
245
- const hasRestMethod = "restMethod" in config2 && config2.restMethod != null;
247
+ const hasRestPath = "restPath" in config && config.restPath != null;
248
+ const hasRestMethod = "restMethod" in config && config.restMethod != null;
246
249
  if (hasRestMethod && !hasRestPath) {
247
250
  throw new Error("restMethod requires restPath to be set");
248
251
  }
249
252
  if (hasRestMethod) {
250
- const method = String(config2.restMethod).toUpperCase();
253
+ const method = String(config.restMethod).toUpperCase();
251
254
  if (!VALID_REST_METHODS.has(method)) {
252
255
  throw new Error(
253
- `Invalid REST method '${config2.restMethod}'. Must be one of: ${[...VALID_REST_METHODS].join(", ")}`
256
+ `Invalid REST method '${config.restMethod}'. Must be one of: ${[...VALID_REST_METHODS].join(", ")}`
257
+ );
258
+ }
259
+ }
260
+ if ("changelog" in config && typeof config.changelog !== "boolean") {
261
+ throw new Error(`changelog must be a boolean (got ${typeof config.changelog})`);
262
+ }
263
+ if ("changelogPreImage" in config && typeof config.changelogPreImage !== "boolean") {
264
+ throw new Error(
265
+ `changelogPreImage must be a boolean (got ${typeof config.changelogPreImage})`
266
+ );
267
+ }
268
+ if ("inputStyle" in config) {
269
+ if (typeof config.inputStyle !== "string") {
270
+ throw new Error(`inputStyle must be a string (got ${typeof config.inputStyle})`);
271
+ }
272
+ if (!VALID_INPUT_STYLES.has(config.inputStyle)) {
273
+ throw new Error(
274
+ `inputStyle must be one of ${[...VALID_INPUT_STYLES].join(", ")} (got '${config.inputStyle}')`
254
275
  );
255
276
  }
256
277
  }
257
278
  const result = {};
258
- for (const [key, value] of Object.entries(config2)) {
279
+ for (const [key, value] of Object.entries(config)) {
259
280
  if (key === "restPath" || key === "restMethod") {
260
281
  continue;
261
282
  } else if (key === "inject" && value !== null && typeof value === "object") {
262
283
  const injected = {};
263
284
  for (const [param, spec] of Object.entries(value)) {
264
- const colonIdx = spec.indexOf(":");
265
- if (colonIdx > 0) {
266
- injected[param] = { source: spec.slice(0, colonIdx), claim: spec.slice(colonIdx + 1) };
285
+ if (!INJECT_KEY_RE.test(param)) {
286
+ throw new Error(
287
+ `inject key '${param}' is not a valid identifier. Keys must start with a letter or underscore and contain only letters, digits, and underscores.`
288
+ );
289
+ }
290
+ if (argNames?.has(param)) {
291
+ throw new Error(
292
+ `inject key '${param}' conflicts with a declared GraphQL argument of the same name. Use a different parameter name.`
293
+ );
294
+ }
295
+ if (typeof spec !== "string" || !INJECT_SOURCE_RE.test(spec)) {
296
+ throw new Error(
297
+ `inject source '${String(spec)}' for param '${param}' is invalid. Supported format: 'jwt:<claim_name>' (e.g. 'jwt:org_id', 'jwt:sub').`
298
+ );
267
299
  }
300
+ const colonIdx = spec.indexOf(":");
301
+ injected[param] = { source: spec.slice(0, colonIdx), claim: spec.slice(colonIdx + 1) };
268
302
  }
269
303
  result["inject_params"] = injected;
270
304
  } else if (key === "deprecated" && typeof value === "string") {
@@ -274,9 +308,9 @@ function normaliseConfig(config2) {
274
308
  }
275
309
  }
276
310
  if (hasRestPath) {
277
- const method = hasRestMethod ? String(config2.restMethod).toUpperCase() : void 0;
311
+ const method = hasRestMethod ? String(config.restMethod).toUpperCase() : void 0;
278
312
  result["rest"] = {
279
- path: config2.restPath,
313
+ path: config.restPath,
280
314
  method
281
315
  };
282
316
  }
@@ -329,32 +363,32 @@ var SchemaRegistry = class {
329
363
  * @param description - Optional query description
330
364
  * @param config - Additional configuration (sql_source, etc.)
331
365
  */
332
- static registerQuery(name, returnType, returnsList, nullable, args, description, config2) {
366
+ static registerQuery(name, returnType, returnsList, nullable, args, description, config) {
333
367
  if (this.queries.has(name)) {
334
368
  throw new Error(
335
369
  `Query '${name}' is already registered. Each name must be unique within a schema.`
336
370
  );
337
371
  }
338
372
  const cleanType = returnsList ? returnType.replace(/[[\]!]/g, "") : returnType;
339
- if (config2?.relay) {
373
+ if (config?.relay) {
340
374
  if (!returnsList) {
341
375
  throw new Error(
342
376
  `registerQuery('${name}'): relay: true requires returns_list to be true. Relay connections only apply to list queries.`
343
377
  );
344
378
  }
345
- if (!config2.sqlSource) {
379
+ if (!config.sqlSource) {
346
380
  throw new Error(
347
381
  `registerQuery('${name}'): relay: true requires sqlSource to be set. The compiler needs the view name to derive the cursor column.`
348
382
  );
349
383
  }
350
- if (config2.autoParams) {
351
- const ap = { ...config2.autoParams };
384
+ if (config.autoParams) {
385
+ const ap = { ...config.autoParams };
352
386
  delete ap["limit"];
353
387
  delete ap["offset"];
354
- config2 = { ...config2, autoParams: ap };
388
+ config = { ...config, autoParams: ap };
355
389
  }
356
390
  }
357
- const normalisedConfig = config2 ? normaliseConfig(config2) : void 0;
391
+ const normalisedConfig = config ? normaliseConfig(config, new Set(args.map((a) => a.name))) : void 0;
358
392
  if (normalisedConfig?.rest) {
359
393
  const rest = normalisedConfig.rest;
360
394
  if (!rest.method) {
@@ -382,14 +416,14 @@ var SchemaRegistry = class {
382
416
  * @param description - Optional mutation description
383
417
  * @param config - Additional configuration (sql_source, operation, etc.)
384
418
  */
385
- static registerMutation(name, returnType, returnsList, nullable, args, description, config2) {
419
+ static registerMutation(name, returnType, returnsList, nullable, args, description, config) {
386
420
  if (this.mutations.has(name)) {
387
421
  throw new Error(
388
422
  `Mutation '${name}' is already registered. Each name must be unique within a schema.`
389
423
  );
390
424
  }
391
425
  const cleanType = returnsList ? returnType.replace(/[[\]!]/g, "") : returnType;
392
- const normalisedConfig = config2 ? normaliseConfig(config2) : void 0;
426
+ const normalisedConfig = config ? normaliseConfig(config, new Set(args.map((a) => a.name))) : void 0;
393
427
  if (normalisedConfig?.rest) {
394
428
  const rest = normalisedConfig.rest;
395
429
  if (!rest.method) {
@@ -419,7 +453,7 @@ var SchemaRegistry = class {
419
453
  * @param description - Optional subscription description
420
454
  * @param config - Additional configuration (topic, operation, etc.)
421
455
  */
422
- static registerSubscription(name, entityType, nullable, args, description, config2) {
456
+ static registerSubscription(name, entityType, nullable, args, description, config) {
423
457
  if (this.subscriptions.has(name)) {
424
458
  throw new Error(
425
459
  `Subscription '${name}' is already registered. Each name must be unique within a schema.`
@@ -431,7 +465,7 @@ var SchemaRegistry = class {
431
465
  nullable,
432
466
  arguments: args,
433
467
  description,
434
- ...config2
468
+ ...config
435
469
  });
436
470
  }
437
471
  /**
@@ -728,11 +762,11 @@ function generateCrudOperations(typeName, fields, crud, sqlSource, cascade) {
728
762
  nullable: f.nullable
729
763
  }));
730
764
  SchemaRegistry.registerInputType(inputName, inputFields, `Input for creating a new ${typeName}.`);
731
- const config2 = {
765
+ const config = {
732
766
  sql_source: `fn_create_${snake}`,
733
767
  operation: "INSERT"
734
768
  };
735
- if (cascade) config2.cascade = true;
769
+ if (cascade) config.cascade = true;
736
770
  SchemaRegistry.registerMutation(
737
771
  `create_${snake}`,
738
772
  typeName,
@@ -740,7 +774,7 @@ function generateCrudOperations(typeName, fields, crud, sqlSource, cascade) {
740
774
  false,
741
775
  [{ name: "input", type: inputName, nullable: false }],
742
776
  `Create a new ${typeName}.`,
743
- config2
777
+ config
744
778
  );
745
779
  }
746
780
  if (ops.has("update")) {
@@ -751,11 +785,11 @@ function generateCrudOperations(typeName, fields, crud, sqlSource, cascade) {
751
785
  ...writableFields.map((f) => ({ name: f.name, type: f.type, nullable: true }))
752
786
  ];
753
787
  SchemaRegistry.registerInputType(inputName, inputFields, `Input for updating an existing ${typeName}.`);
754
- const config2 = {
788
+ const config = {
755
789
  sql_source: `fn_update_${snake}`,
756
790
  operation: "UPDATE"
757
791
  };
758
- if (cascade) config2.cascade = true;
792
+ if (cascade) config.cascade = true;
759
793
  SchemaRegistry.registerMutation(
760
794
  `update_${snake}`,
761
795
  typeName,
@@ -763,15 +797,15 @@ function generateCrudOperations(typeName, fields, crud, sqlSource, cascade) {
763
797
  true,
764
798
  [{ name: "input", type: inputName, nullable: false }],
765
799
  `Update an existing ${typeName}.`,
766
- config2
800
+ config
767
801
  );
768
802
  }
769
803
  if (ops.has("delete")) {
770
- const config2 = {
804
+ const config = {
771
805
  sql_source: `fn_delete_${snake}`,
772
806
  operation: "DELETE"
773
807
  };
774
- if (cascade) config2.cascade = true;
808
+ if (cascade) config.cascade = true;
775
809
  SchemaRegistry.registerMutation(
776
810
  `delete_${snake}`,
777
811
  typeName,
@@ -779,7 +813,7 @@ function generateCrudOperations(typeName, fields, crud, sqlSource, cascade) {
779
813
  false,
780
814
  [{ name: pkField.name, type: pkField.type, nullable: false }],
781
815
  `Delete a ${typeName}.`,
782
- config2
816
+ config
783
817
  );
784
818
  }
785
819
  }
@@ -797,7 +831,7 @@ function Type(_config) {
797
831
  return constructor;
798
832
  };
799
833
  }
800
- function Query(config2) {
834
+ function Query(config) {
801
835
  return function(_target, propertyKey, descriptor) {
802
836
  const originalMethod = descriptor.value;
803
837
  const methodName = propertyKey;
@@ -812,12 +846,12 @@ function Query(config2) {
812
846
  [],
813
847
  // Placeholder
814
848
  originalMethod?.toString?.().split("\n")[0] ?? void 0,
815
- config2
849
+ config
816
850
  );
817
851
  return descriptor;
818
852
  };
819
853
  }
820
- function Mutation(config2) {
854
+ function Mutation(config) {
821
855
  return function(_target, propertyKey, descriptor) {
822
856
  const originalMethod = descriptor.value;
823
857
  const methodName = propertyKey;
@@ -832,28 +866,28 @@ function Mutation(config2) {
832
866
  [],
833
867
  // Placeholder
834
868
  originalMethod?.toString?.().split("\n")[0] ?? void 0,
835
- config2
869
+ config
836
870
  );
837
871
  return descriptor;
838
872
  };
839
873
  }
840
- function enum_(name, values, config2) {
874
+ function enum_(name, values, config) {
841
875
  const enumValues = Object.keys(values).map((key) => ({
842
876
  name: key
843
877
  }));
844
- SchemaRegistry.registerEnum(name, enumValues, config2?.description);
878
+ SchemaRegistry.registerEnum(name, enumValues, config?.description);
845
879
  return values;
846
880
  }
847
- function interface_(name, fields, config2) {
848
- SchemaRegistry.registerInterface(name, fields, config2?.description);
881
+ function interface_(name, fields, config) {
882
+ SchemaRegistry.registerInterface(name, fields, config?.description);
849
883
  return {};
850
884
  }
851
- function union(name, memberTypes, config2) {
852
- SchemaRegistry.registerUnion(name, memberTypes, config2?.description);
885
+ function union(name, memberTypes, config) {
886
+ SchemaRegistry.registerUnion(name, memberTypes, config?.description);
853
887
  return {};
854
888
  }
855
- function input(name, fields, config2) {
856
- SchemaRegistry.registerInputType(name, fields, config2?.description);
889
+ function input(name, fields, config) {
890
+ SchemaRegistry.registerInputType(name, fields, config?.description);
857
891
  return {};
858
892
  }
859
893
  function registerTypeFields(typeName, fields, description, options) {
@@ -862,55 +896,10 @@ function registerTypeFields(typeName, fields, description, options) {
862
896
  generateCrudOperations(typeName, fields, options.crud, options.sqlSource, options.cascade);
863
897
  }
864
898
  }
865
- var SAFE_SQL_IDENT = /^[A-Za-z_][A-Za-z0-9_$]*$/;
866
- function registerQuery(name, returnTypeOrConfig, returnsList, nullable, args, description, config2) {
867
- if (typeof returnTypeOrConfig === "object" && returnTypeOrConfig !== null) {
868
- const cfg = returnTypeOrConfig;
869
- const dispatch = cfg.sqlSourceDispatch;
870
- if (dispatch !== void 0) {
871
- if (cfg.sqlSource !== void 0) {
872
- throw new Error(
873
- `registerQuery('${name}'): sqlSource and sqlSourceDispatch are mutually exclusive.`
874
- );
875
- }
876
- const dispatchArg = dispatch.argument;
877
- const queryArgs = cfg.arguments ?? [];
878
- const argDef = queryArgs.find((a) => a.name === dispatchArg);
879
- if (argDef === void 0) {
880
- throw new Error(
881
- `registerQuery('${name}'): sqlSourceDispatch argument '${dispatchArg}' not found in arguments list.`
882
- );
883
- }
884
- if (argDef.nullable) {
885
- throw new Error(
886
- `registerQuery('${name}'): sqlSourceDispatch argument '${dispatchArg}' must be non-nullable.`
887
- );
888
- }
889
- if (dispatch.mapping !== void 0) {
890
- for (const [key, value] of Object.entries(dispatch.mapping)) {
891
- if (!SAFE_SQL_IDENT.test(value)) {
892
- throw new Error(
893
- `registerQuery('${name}'): sql_source_dispatch mapping value '${value}' for key '${key}' is not a safe SQL identifier.`
894
- );
895
- }
896
- }
897
- }
898
- }
899
- const { returnType, returnsList: rl, nullable: nl, arguments: qArgs, description: desc, ...rest } = cfg;
900
- SchemaRegistry.registerQuery(
901
- name,
902
- returnType,
903
- rl ?? false,
904
- nl ?? false,
905
- qArgs ?? [],
906
- desc,
907
- rest
908
- );
909
- return;
910
- }
911
- SchemaRegistry.registerQuery(name, returnTypeOrConfig, returnsList ?? false, nullable ?? false, args ?? [], description, config2);
899
+ function registerQuery(name, returnType, returnsList, nullable, args, description, config) {
900
+ SchemaRegistry.registerQuery(name, returnType, returnsList, nullable, args, description, config);
912
901
  }
913
- function registerMutation(name, returnType, returnsList, nullable, args, description, config2) {
902
+ function registerMutation(name, returnType, returnsList, nullable, args, description, config) {
914
903
  SchemaRegistry.registerMutation(
915
904
  name,
916
905
  returnType,
@@ -918,14 +907,14 @@ function registerMutation(name, returnType, returnsList, nullable, args, descrip
918
907
  nullable,
919
908
  args,
920
909
  description,
921
- config2
910
+ config
922
911
  );
923
912
  }
924
- function Subscription(config2) {
913
+ function Subscription(config) {
925
914
  return function(_target, propertyKey, descriptor) {
926
915
  const originalMethod = descriptor.value;
927
916
  const methodName = propertyKey;
928
- const entityType = config2?.entityType || "Subscription";
917
+ const entityType = config?.entityType || "Subscription";
929
918
  SchemaRegistry.registerSubscription(
930
919
  methodName,
931
920
  entityType,
@@ -934,13 +923,13 @@ function Subscription(config2) {
934
923
  [],
935
924
  // Placeholder for arguments
936
925
  originalMethod?.toString?.().split("\n")[0] ?? void 0,
937
- config2
926
+ config
938
927
  );
939
928
  return descriptor;
940
929
  };
941
930
  }
942
- function registerSubscription(name, entityType, nullable, args, description, config2) {
943
- SchemaRegistry.registerSubscription(name, entityType, nullable, args, description, config2);
931
+ function registerSubscription(name, entityType, nullable, args, description, config) {
932
+ SchemaRegistry.registerSubscription(name, entityType, nullable, args, description, config);
944
933
  }
945
934
  function Scalar(target) {
946
935
  if (!isCustomScalarSubclass(target)) {
@@ -1000,12 +989,6 @@ function validateSchemaBeforeExport(schema) {
1000
989
  );
1001
990
  }
1002
991
  }
1003
- var ConfigHolder = class {
1004
- static pendingConfig = null;
1005
- };
1006
- function config(configObj) {
1007
- ConfigHolder.pendingConfig = configObj;
1008
- }
1009
992
  function exportSchema(outputPath, options = {}) {
1010
993
  const { pretty = true } = options;
1011
994
  const schema = SchemaRegistry.getSchema();
@@ -1110,15 +1093,15 @@ var DEFAULT_RETRY_CONFIG = {
1110
1093
  initial_delay_ms: 100,
1111
1094
  max_delay_ms: 6e4
1112
1095
  };
1113
- function Observer(config2) {
1096
+ function Observer(config) {
1114
1097
  return function(_target, propertyKey, _descriptor) {
1115
1098
  SchemaRegistry.registerObserver(
1116
1099
  propertyKey,
1117
- config2.entity,
1118
- config2.event,
1119
- config2.actions,
1120
- config2.condition,
1121
- config2.retry
1100
+ config.entity,
1101
+ config.event,
1102
+ config.actions,
1103
+ config.condition,
1104
+ config.retry
1122
1105
  );
1123
1106
  };
1124
1107
  }
@@ -1211,7 +1194,7 @@ var RateLimitError = class extends FraiseQLError {
1211
1194
  };
1212
1195
 
1213
1196
  // src/http-retry.ts
1214
- async function executeWithRetry(fn, config2 = {}) {
1197
+ async function executeWithRetry(fn, config = {}) {
1215
1198
  const {
1216
1199
  maxAttempts = 1,
1217
1200
  baseDelayMs = 1e3,
@@ -1219,7 +1202,7 @@ async function executeWithRetry(fn, config2 = {}) {
1219
1202
  jitter = true,
1220
1203
  retryOn = [NetworkError, TimeoutError],
1221
1204
  onRetry
1222
- } = config2;
1205
+ } = config;
1223
1206
  let lastError;
1224
1207
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1225
1208
  try {
@@ -1252,13 +1235,13 @@ var FraiseQLClient = class {
1252
1235
  extraHeaders;
1253
1236
  fetchFn;
1254
1237
  constructor(urlOrConfig) {
1255
- const config2 = typeof urlOrConfig === "string" ? { url: urlOrConfig } : urlOrConfig;
1256
- this.url = config2.url;
1257
- this.authorization = config2.authorization;
1258
- this.timeoutMs = config2.timeoutMs ?? 3e4;
1259
- this.retry = config2.retry ?? {};
1260
- this.extraHeaders = config2.headers ?? {};
1261
- this.fetchFn = config2.fetch ?? globalThis.fetch.bind(globalThis);
1238
+ const config = typeof urlOrConfig === "string" ? { url: urlOrConfig } : urlOrConfig;
1239
+ this.url = config.url;
1240
+ this.authorization = config.authorization;
1241
+ this.timeoutMs = config.timeoutMs ?? 3e4;
1242
+ this.retry = config.retry ?? {};
1243
+ this.extraHeaders = config.headers ?? {};
1244
+ this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
1262
1245
  }
1263
1246
  async resolveAuth() {
1264
1247
  if (this.authorization === void 0) return void 0;
@@ -1347,7 +1330,7 @@ var FraiseQLClient = class {
1347
1330
  };
1348
1331
 
1349
1332
  // src/index.ts
1350
- var version = "2.0.0-alpha.1";
1333
+ var version = "2.9.0";
1351
1334
  // Annotate the CommonJS export names for ESM import in node:
1352
1335
  0 && (module.exports = {
1353
1336
  AuthenticationError,
@@ -1368,7 +1351,6 @@ var version = "2.0.0-alpha.1";
1368
1351
  Subscription,
1369
1352
  TimeoutError,
1370
1353
  Type,
1371
- config,
1372
1354
  email,
1373
1355
  enum_,
1374
1356
  executeWithRetry,