json-rest-api 1.0.14 → 1.0.15

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.
Files changed (118) hide show
  1. package/connectors_ideas/fastify.js +66 -68
  2. package/connectors_ideas/koa-plugin.js +50 -51
  3. package/eslint.config.js +11 -2
  4. package/examples/url-override-example.js +63 -63
  5. package/index.js +13 -13
  6. package/lib/enhanced-logger.js +51 -51
  7. package/lib/error-formatter.js +61 -61
  8. package/lib/rest-api-errors.js +28 -28
  9. package/package.json +1 -1
  10. package/plugins/core/bulk-operations-plugin.js +142 -145
  11. package/plugins/core/connectors/express-plugin.js +210 -217
  12. package/plugins/core/connectors/lib/busboy-detector.js +84 -84
  13. package/plugins/core/connectors/lib/formidable-detector.js +50 -50
  14. package/plugins/core/connectors/lib/request-helpers.js +74 -74
  15. package/plugins/core/file-handling-plugin.js +125 -125
  16. package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
  17. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
  18. package/plugins/core/lib/anyapi/schema-utils.js +101 -101
  19. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
  20. package/plugins/core/lib/dbTablesOperations.js +134 -135
  21. package/plugins/core/lib/fractional-positioning.js +74 -76
  22. package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
  23. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
  24. package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
  25. package/plugins/core/lib/querying/knex-process-includes.js +50 -50
  26. package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
  27. package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
  28. package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
  29. package/plugins/core/lib/querying/knex-window-queries.js +89 -89
  30. package/plugins/core/lib/querying/url-helpers.js +35 -35
  31. package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
  32. package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
  33. package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
  34. package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
  35. package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
  36. package/plugins/core/lib/querying-writing/field-utils.js +44 -42
  37. package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
  38. package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
  39. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
  40. package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
  41. package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
  42. package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
  43. package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
  44. package/plugins/core/lib/storage/storage-adapter.js +68 -68
  45. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
  46. package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
  47. package/plugins/core/lib/writing/relationship-processor.js +58 -58
  48. package/plugins/core/rest-api-access.js +8 -8
  49. package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
  50. package/plugins/core/rest-api-cors-plugin.js +170 -171
  51. package/plugins/core/rest-api-knex-plugin.js +482 -493
  52. package/plugins/core/rest-api-multihome-plugin.js +247 -251
  53. package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
  54. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
  55. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
  56. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
  57. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
  58. package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
  59. package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
  60. package/plugins/core/rest-api-plugin-methods/common.js +295 -297
  61. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
  62. package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
  63. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
  64. package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
  65. package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
  66. package/plugins/core/rest-api-plugin-methods/get.js +86 -86
  67. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
  68. package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
  69. package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
  70. package/plugins/core/rest-api-plugin-methods/post.js +61 -62
  71. package/plugins/core/rest-api-plugin-methods/put.js +86 -87
  72. package/plugins/core/rest-api-plugin-methods/query.js +88 -88
  73. package/plugins/core/rest-api-plugin-methods/release.js +5 -6
  74. package/plugins/core/rest-api-plugin.js +76 -78
  75. package/plugins/core/rest-api-positioning-plugin.js +251 -252
  76. package/plugins/core/socketio-plugin.js +240 -236
  77. package/plugins/storage/local-storage.js +103 -103
  78. package/plugins/storage/s3-storage.js +41 -42
  79. package/quickTest.js +37 -37
  80. package/scripts/debug-anyapi-review-filter.mjs +18 -18
  81. package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
  82. package/scripts/inspect-anyapi-descriptor.mjs +10 -10
  83. package/scripts/run-docs.js +112 -113
  84. package/test-debug.js +28 -28
  85. package/tests/TEST_TEMPLATE.test.js +51 -51
  86. package/tests/anyapi-basic.test.js +42 -42
  87. package/tests/anyapi-cursor-pagination.test.js +48 -48
  88. package/tests/bulk-operations.test.js +208 -208
  89. package/tests/computed-fields.test.js +204 -204
  90. package/tests/cors.test.js +412 -413
  91. package/tests/curl.test.js +275 -276
  92. package/tests/custom-idproperty-relationships.test.js +266 -266
  93. package/tests/field-getters.test.js +132 -132
  94. package/tests/field-setters.test.js +110 -110
  95. package/tests/fixtures/api-configs.js +699 -699
  96. package/tests/helpers/storage-mode.js +32 -32
  97. package/tests/helpers/test-utils.js +76 -76
  98. package/tests/include-depth-validation.test.js +76 -76
  99. package/tests/includes.test.js +145 -145
  100. package/tests/multihome.test.js +175 -175
  101. package/tests/nested-includes.test.js +255 -255
  102. package/tests/pagination-cursor-multifield.test.js +164 -164
  103. package/tests/pagination-enhanced.test.js +225 -225
  104. package/tests/polymorphic-relationships.test.js +96 -96
  105. package/tests/polymorphic-simple.test.js +44 -44
  106. package/tests/positioning-advanced.test.js +226 -226
  107. package/tests/positioning-extended.test.js +399 -399
  108. package/tests/positioning-multifilter.test.js +204 -204
  109. package/tests/positioning.test.js +266 -266
  110. package/tests/queries.test.js +184 -184
  111. package/tests/query-limits.test.js +154 -155
  112. package/tests/relationship-endpoints.test.js +127 -127
  113. package/tests/relationships.test.js +178 -179
  114. package/tests/rest-api-access.test.js +86 -86
  115. package/tests/rest-api.test.js +103 -103
  116. package/tests/searchschema-merge.test.js +93 -93
  117. package/tests/socketio.test.js +326 -326
  118. package/tests/virtual-fields.test.js +164 -164
@@ -1,21 +1,21 @@
1
- import { DEFAULT_CANONICAL_CONFIG, SLOT_LIMITS, SLOT_POOLS, TYPE_TO_POOL } from './schema-utils.js';
1
+ import { DEFAULT_CANONICAL_CONFIG, SLOT_LIMITS, SLOT_POOLS, TYPE_TO_POOL } from './schema-utils.js'
2
2
 
3
3
  const clone = (value) => {
4
4
  if (typeof structuredClone === 'function') {
5
- return structuredClone(value);
5
+ return structuredClone(value)
6
6
  }
7
- return JSON.parse(JSON.stringify(value));
8
- };
7
+ return JSON.parse(JSON.stringify(value))
8
+ }
9
9
 
10
- const SUPPORTED_TYPES = new Set([...TYPE_TO_POOL.keys(), 'id']);
10
+ const SUPPORTED_TYPES = new Set([...TYPE_TO_POOL.keys(), 'id'])
11
11
 
12
12
  const BELONGS_TO_ALIAS = (fieldName, fieldDef) => {
13
- if (fieldDef.as) return fieldDef.as;
13
+ if (fieldDef.as) return fieldDef.as
14
14
  if (fieldName.endsWith('_id')) {
15
- return fieldName.slice(0, -3);
15
+ return fieldName.slice(0, -3)
16
16
  }
17
- return fieldName;
18
- };
17
+ return fieldName
18
+ }
19
19
 
20
20
  const SLOT_PATTERNS = {
21
21
  string: /^string_(\d+)$/,
@@ -23,104 +23,104 @@ const SLOT_PATTERNS = {
23
23
  boolean: /^boolean_(\d+)$/,
24
24
  date: /^date_(\d+)$/,
25
25
  json: /^json_(\d+)$/,
26
- };
26
+ }
27
27
 
28
- const BELONGS_TO_ID_PATTERN = /^rel_(\d+)_id$/;
29
- const BELONGS_TO_TYPE_PATTERN = /^rel_(\d+)_type$/;
28
+ const BELONGS_TO_ID_PATTERN = /^rel_(\d+)_id$/
29
+ const BELONGS_TO_TYPE_PATTERN = /^rel_(\d+)_type$/
30
30
 
31
31
  const parseSimpleSlotColumn = (column) => {
32
32
  for (const [type, pattern] of Object.entries(SLOT_PATTERNS)) {
33
- const match = pattern.exec(column);
33
+ const match = pattern.exec(column)
34
34
  if (match) {
35
- return { slotType: type, slotIndex: Number(match[1]) };
35
+ return { slotType: type, slotIndex: Number(match[1]) }
36
36
  }
37
37
  }
38
- throw new Error(`Invalid canonical slot column '${column}'`);
39
- };
38
+ throw new Error(`Invalid canonical slot column '${column}'`)
39
+ }
40
40
 
41
41
  const parseBelongsToSlots = ({ idSlot, typeSlot }) => {
42
42
  if (!idSlot || typeof idSlot !== 'string') {
43
- throw new Error('BelongsTo canonical mapping requires idSlot');
43
+ throw new Error('BelongsTo canonical mapping requires idSlot')
44
44
  }
45
- const idMatch = BELONGS_TO_ID_PATTERN.exec(idSlot);
45
+ const idMatch = BELONGS_TO_ID_PATTERN.exec(idSlot)
46
46
  if (!idMatch) {
47
- throw new Error(`Invalid belongsTo id slot '${idSlot}'`);
47
+ throw new Error(`Invalid belongsTo id slot '${idSlot}'`)
48
48
  }
49
- const index = Number(idMatch[1]);
50
- const expectedTypeSlot = `rel_${index}_type`;
51
- const actualTypeSlot = typeSlot || expectedTypeSlot;
52
- const typeMatch = BELONGS_TO_TYPE_PATTERN.exec(actualTypeSlot);
49
+ const index = Number(idMatch[1])
50
+ const expectedTypeSlot = `rel_${index}_type`
51
+ const actualTypeSlot = typeSlot || expectedTypeSlot
52
+ const typeMatch = BELONGS_TO_TYPE_PATTERN.exec(actualTypeSlot)
53
53
  if (!typeMatch || Number(typeMatch[1]) !== index) {
54
- throw new Error(`BelongsTo type slot '${actualTypeSlot}' must correspond to id slot '${idSlot}'`);
54
+ throw new Error(`BelongsTo type slot '${actualTypeSlot}' must correspond to id slot '${idSlot}'`)
55
55
  }
56
- return { slotType: 'belongsTo', slotIndex: index, idColumn: idSlot, typeColumn: actualTypeSlot };
57
- };
56
+ return { slotType: 'belongsTo', slotIndex: index, idColumn: idSlot, typeColumn: actualTypeSlot }
57
+ }
58
58
 
59
59
  export class AnyapiRegistry {
60
- constructor({ knex, log } = {}) {
60
+ constructor ({ knex, log } = {}) {
61
61
  if (!knex) {
62
- throw new Error('AnyapiRegistry requires a knex instance');
62
+ throw new Error('AnyapiRegistry requires a knex instance')
63
63
  }
64
- this.knex = knex;
65
- this.log = log || console;
66
- this.cache = new Map();
64
+ this.knex = knex
65
+ this.log = log || console
66
+ this.cache = new Map()
67
67
  }
68
68
 
69
- #key(tenant, resource) {
70
- return `${tenant}::${resource}`;
69
+ #key (tenant, resource) {
70
+ return `${tenant}::${resource}`
71
71
  }
72
72
 
73
- async registerResource(definition, options = {}) {
74
- this.#validateDefinition(definition);
75
- const descriptor = await this.#register(definition, options.transaction);
76
- const key = this.#key(descriptor.tenant, descriptor.resource);
77
- this.cache.set(key, descriptor);
78
- return clone(descriptor);
73
+ async registerResource (definition, options = {}) {
74
+ this.#validateDefinition(definition)
75
+ const descriptor = await this.#register(definition, options.transaction)
76
+ const key = this.#key(descriptor.tenant, descriptor.resource)
77
+ this.cache.set(key, descriptor)
78
+ return clone(descriptor)
79
79
  }
80
80
 
81
- async allocateField({ tenant, resource, fieldName, definition, canonicalField }, options = {}) {
81
+ async allocateField ({ tenant, resource, fieldName, definition, canonicalField }, options = {}) {
82
82
  if (!tenant || !resource || !fieldName || !definition) {
83
- throw new Error('allocateField requires tenant, resource, fieldName, and definition');
83
+ throw new Error('allocateField requires tenant, resource, fieldName, and definition')
84
84
  }
85
85
 
86
- const descriptor = await this.getDescriptor(tenant, resource, { bypassCache: true });
86
+ const descriptor = await this.getDescriptor(tenant, resource, { bypassCache: true })
87
87
  if (!descriptor) {
88
- throw new Error(`Resource '${resource}' not registered for tenant '${tenant}'`);
88
+ throw new Error(`Resource '${resource}' not registered for tenant '${tenant}'`)
89
89
  }
90
90
 
91
- const trx = options.transaction || await this.knex.transaction();
92
- const managed = !options.transaction;
91
+ const trx = options.transaction || await this.knex.transaction()
92
+ const managed = !options.transaction
93
93
 
94
- descriptor.belongsTo = descriptor.belongsTo || {};
95
- descriptor.canonicalFieldMap = descriptor.canonicalFieldMap || {};
94
+ descriptor.belongsTo = descriptor.belongsTo || {}
95
+ descriptor.canonicalFieldMap = descriptor.canonicalFieldMap || {}
96
96
 
97
- const usedSlots = new Set();
97
+ const usedSlots = new Set()
98
98
  Object.values(descriptor.fields || {}).forEach((info) => {
99
99
  if (info?.slot) {
100
- usedSlots.add(info.slot);
100
+ usedSlots.add(info.slot)
101
101
  }
102
- });
102
+ })
103
103
  Object.values(descriptor.belongsTo || {}).forEach((info) => {
104
104
  if (info?.typeColumn) {
105
- usedSlots.add(info.typeColumn);
105
+ usedSlots.add(info.typeColumn)
106
106
  }
107
107
  if (info?.idColumn) {
108
- usedSlots.add(info.idColumn);
108
+ usedSlots.add(info.idColumn)
109
109
  }
110
- });
110
+ })
111
111
 
112
112
  try {
113
113
  const resourceRow = await trx('any_resource_configs')
114
114
  .where({ tenant_id: tenant, resource })
115
- .first();
115
+ .first()
116
116
 
117
117
  if (!resourceRow) {
118
- throw new Error(`Resource '${resource}' not found in metadata`);
118
+ throw new Error(`Resource '${resource}' not found in metadata`)
119
119
  }
120
120
 
121
121
  if (definition?.belongsToPolymorphic) {
122
- const alias = definition.as || fieldName;
123
- const { typeField, idField, types = [] } = definition.belongsToPolymorphic;
122
+ const alias = definition.as || fieldName
123
+ const { typeField, idField, types = [] } = definition.belongsToPolymorphic
124
124
 
125
125
  await trx('any_relationship_configs').insert({
126
126
  resource_config_id: resourceRow.id,
@@ -136,13 +136,13 @@ export class AnyapiRegistry {
136
136
  other_key: null,
137
137
  alias,
138
138
  meta_json: JSON.stringify({ types }),
139
- });
139
+ })
140
140
 
141
141
  if (managed) {
142
- await trx.commit();
142
+ await trx.commit()
143
143
  }
144
144
 
145
- descriptor.polymorphicBelongsTo = descriptor.polymorphicBelongsTo || {};
145
+ descriptor.polymorphicBelongsTo = descriptor.polymorphicBelongsTo || {}
146
146
  descriptor.polymorphicBelongsTo[alias] = {
147
147
  alias,
148
148
  typeField,
@@ -150,44 +150,44 @@ export class AnyapiRegistry {
150
150
  types,
151
151
  typeColumn: descriptor.fields?.[typeField]?.slot || null,
152
152
  idColumn: descriptor.fields?.[idField]?.slot || null,
153
- };
154
- this.cache.set(this.#key(tenant, resource), descriptor);
155
- return clone(descriptor);
153
+ }
154
+ this.cache.set(this.#key(tenant, resource), descriptor)
155
+ return clone(descriptor)
156
156
  }
157
157
 
158
- let override = null;
158
+ let override = null
159
159
  if (canonicalField) {
160
160
  if (definition.belongsTo) {
161
161
  const normalized = typeof canonicalField === 'string'
162
162
  ? { idSlot: canonicalField }
163
- : canonicalField;
163
+ : canonicalField
164
164
  if (!normalized || !normalized.idSlot) {
165
- throw new Error(`canonicalFieldsMap for belongsTo field '${fieldName}' requires idSlot`);
165
+ throw new Error(`canonicalFieldsMap for belongsTo field '${fieldName}' requires idSlot`)
166
166
  }
167
- const parsed = parseBelongsToSlots(normalized);
167
+ const parsed = parseBelongsToSlots(normalized)
168
168
  if (usedSlots.has(parsed.idColumn) || usedSlots.has(parsed.typeColumn)) {
169
- throw new Error(`Canonical slots '${parsed.idColumn}'/'${parsed.typeColumn}' already in use`);
169
+ throw new Error(`Canonical slots '${parsed.idColumn}'/'${parsed.typeColumn}' already in use`)
170
170
  }
171
- usedSlots.add(parsed.idColumn);
172
- usedSlots.add(parsed.typeColumn);
173
- override = normalized;
171
+ usedSlots.add(parsed.idColumn)
172
+ usedSlots.add(parsed.typeColumn)
173
+ override = normalized
174
174
  } else {
175
175
  const slotColumn = typeof canonicalField === 'string'
176
176
  ? canonicalField
177
- : canonicalField?.slot || canonicalField?.slotColumn;
177
+ : canonicalField?.slot || canonicalField?.slotColumn
178
178
  if (!slotColumn) {
179
- throw new Error(`canonicalFieldsMap for field '${fieldName}' must provide a slot column`);
179
+ throw new Error(`canonicalFieldsMap for field '${fieldName}' must provide a slot column`)
180
180
  }
181
181
  if (usedSlots.has(slotColumn)) {
182
- throw new Error(`Canonical slot '${slotColumn}' already in use`);
182
+ throw new Error(`Canonical slot '${slotColumn}' already in use`)
183
183
  }
184
- usedSlots.add(slotColumn);
185
- override = { slotColumn };
184
+ usedSlots.add(slotColumn)
185
+ override = { slotColumn }
186
186
  }
187
187
  }
188
188
 
189
- const fieldSlot = this.#assignFieldSlot({ ...definition, fieldName }, descriptor.slotState, override);
190
- const meta = definition.meta ? JSON.stringify(definition.meta) : null;
189
+ const fieldSlot = this.#assignFieldSlot({ ...definition, fieldName }, descriptor.slotState, override)
190
+ const meta = definition.meta ? JSON.stringify(definition.meta) : null
191
191
 
192
192
  await trx('any_field_configs').insert({
193
193
  resource_config_id: resourceRow.id,
@@ -200,7 +200,7 @@ export class AnyapiRegistry {
200
200
  target_resource: definition.belongsTo || null,
201
201
  alias: definition.as || null,
202
202
  meta_json: meta,
203
- });
203
+ })
204
204
 
205
205
  if (fieldSlot.relationshipRow) {
206
206
  const relationshipRow = {
@@ -217,12 +217,12 @@ export class AnyapiRegistry {
217
217
  other_key: fieldSlot.relationshipRow.otherKey,
218
218
  alias: fieldSlot.relationshipRow.alias,
219
219
  meta_json: fieldSlot.relationshipRow.meta ? JSON.stringify(fieldSlot.relationshipRow.meta) : null,
220
- };
221
- await trx('any_relationship_configs').insert(relationshipRow);
220
+ }
221
+ await trx('any_relationship_configs').insert(relationshipRow)
222
222
  }
223
223
 
224
224
  if (managed) {
225
- await trx.commit();
225
+ await trx.commit()
226
226
  }
227
227
 
228
228
  descriptor.fields[fieldName] = {
@@ -233,89 +233,89 @@ export class AnyapiRegistry {
233
233
  required: definition.required === true,
234
234
  target: definition.belongsTo || null,
235
235
  alias: definition.as || null,
236
- };
236
+ }
237
237
 
238
238
  if (fieldSlot.belongsToInfo) {
239
- descriptor.belongsTo[fieldSlot.belongsToInfo.alias] = fieldSlot.belongsToInfo;
240
- descriptor.canonicalFieldMap = descriptor.canonicalFieldMap || {};
239
+ descriptor.belongsTo[fieldSlot.belongsToInfo.alias] = fieldSlot.belongsToInfo
240
+ descriptor.canonicalFieldMap = descriptor.canonicalFieldMap || {}
241
241
  descriptor.canonicalFieldMap[fieldName] = {
242
242
  idSlot: fieldSlot.slotColumn,
243
243
  typeSlot: fieldSlot.belongsToInfo.typeColumn,
244
- };
244
+ }
245
245
  } else {
246
- descriptor.canonicalFieldMap = descriptor.canonicalFieldMap || {};
247
- descriptor.canonicalFieldMap[fieldName] = fieldSlot.slotColumn;
246
+ descriptor.canonicalFieldMap = descriptor.canonicalFieldMap || {}
247
+ descriptor.canonicalFieldMap[fieldName] = fieldSlot.slotColumn
248
248
  }
249
249
 
250
- usedSlots.add(fieldSlot.slotColumn);
250
+ usedSlots.add(fieldSlot.slotColumn)
251
251
  if (fieldSlot.relationshipRow?.typeColumn) {
252
- usedSlots.add(fieldSlot.relationshipRow.typeColumn);
252
+ usedSlots.add(fieldSlot.relationshipRow.typeColumn)
253
253
  }
254
254
 
255
- descriptor.slotState = fieldSlot.updatedState;
256
- this.cache.set(this.#key(tenant, resource), descriptor);
257
- return clone(descriptor);
255
+ descriptor.slotState = fieldSlot.updatedState
256
+ this.cache.set(this.#key(tenant, resource), descriptor)
257
+ return clone(descriptor)
258
258
  } catch (error) {
259
259
  if (managed) {
260
- await trx.rollback();
260
+ await trx.rollback()
261
261
  }
262
- throw error;
262
+ throw error
263
263
  }
264
264
  }
265
265
 
266
- async getDescriptor(tenant, resource, options = {}) {
267
- if (!tenant || !resource) return null;
268
- const key = this.#key(tenant, resource);
266
+ async getDescriptor (tenant, resource, options = {}) {
267
+ if (!tenant || !resource) return null
268
+ const key = this.#key(tenant, resource)
269
269
  if (!options.bypassCache && this.cache.has(key)) {
270
- return clone(this.cache.get(key));
270
+ return clone(this.cache.get(key))
271
271
  }
272
272
 
273
- const descriptor = await this.#loadDescriptor(tenant, resource, options.transaction);
274
- if (!descriptor) return null;
273
+ const descriptor = await this.#loadDescriptor(tenant, resource, options.transaction)
274
+ if (!descriptor) return null
275
275
 
276
- this.cache.set(key, descriptor);
277
- return clone(descriptor);
276
+ this.cache.set(key, descriptor)
277
+ return clone(descriptor)
278
278
  }
279
279
 
280
- async listResources(tenant) {
281
- const query = this.knex('any_resource_configs');
280
+ async listResources (tenant) {
281
+ const query = this.knex('any_resource_configs')
282
282
  if (tenant) {
283
- query.where({ tenant_id: tenant });
283
+ query.where({ tenant_id: tenant })
284
284
  }
285
- const rows = await query.select('tenant_id', 'resource');
286
- return rows.map((row) => ({ tenant: row.tenant_id, resource: row.resource }));
285
+ const rows = await query.select('tenant_id', 'resource')
286
+ return rows.map((row) => ({ tenant: row.tenant_id, resource: row.resource }))
287
287
  }
288
288
 
289
- invalidateDescriptor(tenant, resource) {
290
- this.cache.delete(this.#key(tenant, resource));
289
+ invalidateDescriptor (tenant, resource) {
290
+ this.cache.delete(this.#key(tenant, resource))
291
291
  }
292
292
 
293
- #validateDefinition(definition) {
294
- const { tenant, resource, schema } = definition || {};
293
+ #validateDefinition (definition) {
294
+ const { tenant, resource, schema } = definition || {}
295
295
  if (!tenant || typeof tenant !== 'string') {
296
- throw new Error('AnyapiRegistry.registerResource requires a tenant string');
296
+ throw new Error('AnyapiRegistry.registerResource requires a tenant string')
297
297
  }
298
298
  if (!resource || typeof resource !== 'string') {
299
- throw new Error('AnyapiRegistry.registerResource requires a resource string');
299
+ throw new Error('AnyapiRegistry.registerResource requires a resource string')
300
300
  }
301
301
  if (!schema || typeof schema !== 'object') {
302
- throw new Error('AnyapiRegistry.registerResource requires a schema object');
302
+ throw new Error('AnyapiRegistry.registerResource requires a schema object')
303
303
  }
304
304
  }
305
305
 
306
- async #register(definition, externalTrx) {
307
- const { tenant, resource, schema, relationships = {}, canonicalFieldMap = null } = definition;
308
- const trx = externalTrx || await this.knex.transaction();
309
- const managed = !externalTrx;
306
+ async #register (definition, externalTrx) {
307
+ const { tenant, resource, schema, relationships = {}, canonicalFieldMap = null } = definition
308
+ const trx = externalTrx || await this.knex.transaction()
309
+ const managed = !externalTrx
310
310
 
311
311
  try {
312
- const now = this.knex.fn.now();
312
+ const now = this.knex.fn.now()
313
313
  let resourceRow = await trx('any_resource_configs')
314
314
  .where({ tenant_id: tenant, resource })
315
- .first();
315
+ .first()
316
316
 
317
- const schemaJson = JSON.stringify(schema);
318
- const relationshipsJson = JSON.stringify(relationships || {});
317
+ const schemaJson = JSON.stringify(schema)
318
+ const relationshipsJson = JSON.stringify(relationships || {})
319
319
 
320
320
  if (resourceRow) {
321
321
  await trx('any_resource_configs')
@@ -324,7 +324,7 @@ export class AnyapiRegistry {
324
324
  schema_json: schemaJson,
325
325
  relationships_json: relationshipsJson,
326
326
  updated_at: now,
327
- });
327
+ })
328
328
  } else {
329
329
  const [insertedId] = await trx('any_resource_configs').insert({
330
330
  tenant_id: tenant,
@@ -333,7 +333,7 @@ export class AnyapiRegistry {
333
333
  relationships_json: relationshipsJson,
334
334
  created_at: now,
335
335
  updated_at: now,
336
- });
336
+ })
337
337
 
338
338
  resourceRow = {
339
339
  id: insertedId,
@@ -341,35 +341,35 @@ export class AnyapiRegistry {
341
341
  resource,
342
342
  schema_json: schemaJson,
343
343
  relationships_json: relationshipsJson,
344
- };
344
+ }
345
345
  }
346
346
 
347
- await trx('any_field_configs').where({ resource_config_id: resourceRow.id }).delete();
348
- await trx('any_relationship_configs').where({ resource_config_id: resourceRow.id }).delete();
347
+ await trx('any_field_configs').where({ resource_config_id: resourceRow.id }).delete()
348
+ await trx('any_relationship_configs').where({ resource_config_id: resourceRow.id }).delete()
349
349
 
350
- const slotState = this.#initializeSlotState();
351
- const usedSlotColumns = new Set();
352
- let canonicalOverrides = null;
350
+ const slotState = this.#initializeSlotState()
351
+ const usedSlotColumns = new Set()
352
+ let canonicalOverrides = null
353
353
  if (canonicalFieldMap) {
354
- canonicalOverrides = new Map(Object.entries(canonicalFieldMap));
354
+ canonicalOverrides = new Map(Object.entries(canonicalFieldMap))
355
355
  }
356
- const fieldInserts = [];
357
- const relationshipInserts = [];
356
+ const fieldInserts = []
357
+ const relationshipInserts = []
358
358
 
359
359
  for (const [fieldName, fieldDef] of Object.entries(schema)) {
360
360
  if (fieldName === 'id') {
361
361
  if (canonicalOverrides) {
362
- canonicalOverrides.delete(fieldName);
362
+ canonicalOverrides.delete(fieldName)
363
363
  }
364
- continue;
364
+ continue
365
365
  }
366
366
 
367
367
  if (fieldDef?.belongsToPolymorphic) {
368
368
  if (canonicalOverrides?.has(fieldName)) {
369
- throw new Error(`canonicalFieldsMap should not include polymorphic field '${fieldName}'`);
369
+ throw new Error(`canonicalFieldsMap should not include polymorphic field '${fieldName}'`)
370
370
  }
371
- const alias = fieldDef.as || fieldName;
372
- const { typeField, idField, types = [] } = fieldDef.belongsToPolymorphic;
371
+ const alias = fieldDef.as || fieldName
372
+ const { typeField, idField, types = [] } = fieldDef.belongsToPolymorphic
373
373
 
374
374
  relationshipInserts.push({
375
375
  resource_config_id: resourceRow.id,
@@ -387,66 +387,66 @@ export class AnyapiRegistry {
387
387
  meta_json: JSON.stringify({ types }),
388
388
  created_at: this.knex.fn.now(),
389
389
  updated_at: this.knex.fn.now(),
390
- });
391
- continue;
390
+ })
391
+ continue
392
392
  }
393
393
 
394
394
  if (fieldDef?.computed) {
395
395
  if (canonicalOverrides?.has(fieldName)) {
396
- throw new Error(`canonicalFieldsMap should not include computed field '${fieldName}'`);
396
+ throw new Error(`canonicalFieldsMap should not include computed field '${fieldName}'`)
397
397
  }
398
- continue;
398
+ continue
399
399
  }
400
400
 
401
401
  if (fieldDef?.virtual === true) {
402
402
  if (canonicalOverrides?.has(fieldName)) {
403
- throw new Error(`canonicalFieldsMap should not include virtual field '${fieldName}'`);
403
+ throw new Error(`canonicalFieldsMap should not include virtual field '${fieldName}'`)
404
404
  }
405
- continue;
405
+ continue
406
406
  }
407
407
 
408
- let override = null;
408
+ let override = null
409
409
  if (canonicalOverrides) {
410
410
  if (!canonicalOverrides.has(fieldName)) {
411
- throw new Error(`canonicalFieldsMap missing entry for field '${fieldName}'`);
411
+ throw new Error(`canonicalFieldsMap missing entry for field '${fieldName}'`)
412
412
  }
413
- const rawOverride = canonicalOverrides.get(fieldName);
414
- canonicalOverrides.delete(fieldName);
413
+ const rawOverride = canonicalOverrides.get(fieldName)
414
+ canonicalOverrides.delete(fieldName)
415
415
 
416
416
  if (fieldDef.belongsTo) {
417
417
  const normalized = typeof rawOverride === 'string'
418
418
  ? { idSlot: rawOverride }
419
- : rawOverride;
419
+ : rawOverride
420
420
  if (!normalized || !normalized.idSlot) {
421
- throw new Error(`canonicalFieldsMap for belongsTo field '${fieldName}' requires idSlot`);
421
+ throw new Error(`canonicalFieldsMap for belongsTo field '${fieldName}' requires idSlot`)
422
422
  }
423
- const parsed = parseBelongsToSlots(normalized);
423
+ const parsed = parseBelongsToSlots(normalized)
424
424
  if (usedSlotColumns.has(parsed.idColumn) || usedSlotColumns.has(parsed.typeColumn)) {
425
- throw new Error(`Canonical slots '${parsed.idColumn}'/'${parsed.typeColumn}' already in use`);
425
+ throw new Error(`Canonical slots '${parsed.idColumn}'/'${parsed.typeColumn}' already in use`)
426
426
  }
427
- usedSlotColumns.add(parsed.idColumn);
428
- usedSlotColumns.add(parsed.typeColumn);
429
- override = normalized;
427
+ usedSlotColumns.add(parsed.idColumn)
428
+ usedSlotColumns.add(parsed.typeColumn)
429
+ override = normalized
430
430
  } else {
431
431
  const slotColumn = typeof rawOverride === 'string'
432
432
  ? rawOverride
433
- : rawOverride?.slot || rawOverride?.slotColumn;
433
+ : rawOverride?.slot || rawOverride?.slotColumn
434
434
  if (!slotColumn) {
435
- throw new Error(`canonicalFieldsMap for field '${fieldName}' must provide a slot column`);
435
+ throw new Error(`canonicalFieldsMap for field '${fieldName}' must provide a slot column`)
436
436
  }
437
- const parsed = parseSimpleSlotColumn(slotColumn);
437
+ const parsed = parseSimpleSlotColumn(slotColumn)
438
438
  if (usedSlotColumns.has(slotColumn)) {
439
- throw new Error(`Canonical slot '${slotColumn}' already in use`);
439
+ throw new Error(`Canonical slot '${slotColumn}' already in use`)
440
440
  }
441
- usedSlotColumns.add(slotColumn);
442
- override = { slotColumn, slotType: parsed.slotType, slotIndex: parsed.slotIndex };
441
+ usedSlotColumns.add(slotColumn)
442
+ override = { slotColumn, slotType: parsed.slotType, slotIndex: parsed.slotIndex }
443
443
  }
444
444
  }
445
445
 
446
- const allocation = this.#assignFieldSlot({ ...fieldDef, fieldName }, slotState, override);
447
- if (!allocation) continue;
446
+ const allocation = this.#assignFieldSlot({ ...fieldDef, fieldName }, slotState, override)
447
+ if (!allocation) continue
448
448
 
449
- const meta = fieldDef.meta ? JSON.stringify(fieldDef.meta) : null;
449
+ const meta = fieldDef.meta ? JSON.stringify(fieldDef.meta) : null
450
450
  fieldInserts.push({
451
451
  resource_config_id: resourceRow.id,
452
452
  field_name: fieldName,
@@ -460,7 +460,7 @@ export class AnyapiRegistry {
460
460
  meta_json: meta,
461
461
  created_at: this.knex.fn.now(),
462
462
  updated_at: this.knex.fn.now(),
463
- });
463
+ })
464
464
 
465
465
  if (allocation.relationshipRow) {
466
466
  const relationshipRow = {
@@ -479,19 +479,19 @@ export class AnyapiRegistry {
479
479
  meta_json: allocation.relationshipRow.meta ? JSON.stringify(allocation.relationshipRow.meta) : null,
480
480
  created_at: this.knex.fn.now(),
481
481
  updated_at: this.knex.fn.now(),
482
- };
483
- relationshipInserts.push(relationshipRow);
482
+ }
483
+ relationshipInserts.push(relationshipRow)
484
484
  }
485
485
 
486
- usedSlotColumns.add(allocation.slotColumn);
486
+ usedSlotColumns.add(allocation.slotColumn)
487
487
  if (allocation.relationshipRow?.typeColumn) {
488
- usedSlotColumns.add(allocation.relationshipRow.typeColumn);
488
+ usedSlotColumns.add(allocation.relationshipRow.typeColumn)
489
489
  }
490
490
  }
491
491
 
492
492
  if (canonicalOverrides && canonicalOverrides.size > 0) {
493
- const unknownFields = [...canonicalOverrides.keys()].join(', ');
494
- throw new Error(`canonicalFieldsMap contains unknown fields: ${unknownFields}`);
493
+ const unknownFields = [...canonicalOverrides.keys()].join(', ')
494
+ throw new Error(`canonicalFieldsMap contains unknown fields: ${unknownFields}`)
495
495
  }
496
496
 
497
497
  for (const [relName, relDef] of Object.entries(relationships || {})) {
@@ -509,54 +509,54 @@ export class AnyapiRegistry {
509
509
  meta_json: JSON.stringify(relDef.meta || {}),
510
510
  created_at: this.knex.fn.now(),
511
511
  updated_at: this.knex.fn.now(),
512
- });
512
+ })
513
513
  }
514
514
  }
515
515
 
516
516
  if (fieldInserts.length > 0) {
517
- await trx('any_field_configs').insert(fieldInserts);
517
+ await trx('any_field_configs').insert(fieldInserts)
518
518
  }
519
519
  if (relationshipInserts.length > 0) {
520
- await trx('any_relationship_configs').insert(relationshipInserts);
520
+ await trx('any_relationship_configs').insert(relationshipInserts)
521
521
  }
522
522
 
523
- const descriptor = await this.#loadDescriptor(tenant, resource, trx);
523
+ const descriptor = await this.#loadDescriptor(tenant, resource, trx)
524
524
 
525
525
  if (managed) {
526
- await trx.commit();
526
+ await trx.commit()
527
527
  }
528
528
 
529
- return descriptor;
529
+ return descriptor
530
530
  } catch (error) {
531
531
  if (managed) {
532
- await trx.rollback();
532
+ await trx.rollback()
533
533
  }
534
- throw error;
534
+ throw error
535
535
  }
536
536
  }
537
537
 
538
- async #loadDescriptor(tenant, resource, trx) {
538
+ async #loadDescriptor (tenant, resource, trx) {
539
539
  const query = (trx || this.knex)('any_resource_configs')
540
540
  .where({ tenant_id: tenant, resource })
541
- .first();
541
+ .first()
542
542
 
543
- const resourceRow = await query;
544
- if (!resourceRow) return null;
543
+ const resourceRow = await query
544
+ if (!resourceRow) return null
545
545
 
546
546
  const fieldRows = await (trx || this.knex)('any_field_configs')
547
547
  .where({ resource_config_id: resourceRow.id })
548
- .select();
548
+ .select()
549
549
 
550
550
  const relationshipRows = await (trx || this.knex)('any_relationship_configs')
551
551
  .where({ resource_config_id: resourceRow.id })
552
- .select();
552
+ .select()
553
553
 
554
- const slotState = this.#initializeSlotState();
554
+ const slotState = this.#initializeSlotState()
555
555
  for (const row of fieldRows) {
556
556
  if (row.slot_type === 'belongsTo') {
557
- slotState.belongsTo = Math.max(slotState.belongsTo, row.slot_index);
557
+ slotState.belongsTo = Math.max(slotState.belongsTo, row.slot_index)
558
558
  } else if (row.slot_type in slotState) {
559
- slotState[row.slot_type] = Math.max(slotState[row.slot_type], row.slot_index);
559
+ slotState[row.slot_type] = Math.max(slotState[row.slot_type], row.slot_index)
560
560
  }
561
561
  }
562
562
 
@@ -568,33 +568,33 @@ export class AnyapiRegistry {
568
568
  slotState,
569
569
  fieldRows,
570
570
  relationshipRows,
571
- });
571
+ })
572
572
 
573
- const canonicalFieldMap = {};
573
+ const canonicalFieldMap = {}
574
574
  for (const [fieldName, fieldInfo] of Object.entries(descriptor.fields || {})) {
575
575
  if (fieldInfo.slotType === 'belongsTo') {
576
- const alias = fieldInfo.alias || BELONGS_TO_ALIAS(fieldName, descriptor.schema?.[fieldName] || {});
577
- const belongsToInfo = descriptor.belongsTo?.[alias];
576
+ const alias = fieldInfo.alias || BELONGS_TO_ALIAS(fieldName, descriptor.schema?.[fieldName] || {})
577
+ const belongsToInfo = descriptor.belongsTo?.[alias]
578
578
  canonicalFieldMap[fieldName] = {
579
579
  idSlot: fieldInfo.slot,
580
580
  typeSlot: belongsToInfo?.typeColumn || `rel_${fieldInfo.slotIndex}_type`,
581
- };
581
+ }
582
582
  } else {
583
- canonicalFieldMap[fieldName] = fieldInfo.slot;
583
+ canonicalFieldMap[fieldName] = fieldInfo.slot
584
584
  }
585
585
  }
586
586
 
587
- descriptor.canonicalFieldMap = canonicalFieldMap;
587
+ descriptor.canonicalFieldMap = canonicalFieldMap
588
588
 
589
- return descriptor;
589
+ return descriptor
590
590
  }
591
591
 
592
- #buildDescriptor({ tenant, resource, schema, relationships, slotState, fieldRows, relationshipRows }) {
593
- const fields = {};
594
- const reverseAttributes = {};
595
- const belongsTo = {};
596
- const manyToMany = {};
597
- const polymorphicBelongsTo = {};
592
+ #buildDescriptor ({ tenant, resource, schema, relationships, slotState, fieldRows, relationshipRows }) {
593
+ const fields = {}
594
+ const reverseAttributes = {}
595
+ const belongsTo = {}
596
+ const manyToMany = {}
597
+ const polymorphicBelongsTo = {}
598
598
 
599
599
  for (const row of fieldRows) {
600
600
  fields[row.field_name] = {
@@ -606,14 +606,14 @@ export class AnyapiRegistry {
606
606
  target: row.target_resource,
607
607
  alias: row.alias,
608
608
  meta: row.meta_json ? JSON.parse(row.meta_json) : undefined,
609
- };
609
+ }
610
610
 
611
- reverseAttributes[row.slot_column] = row.field_name;
611
+ reverseAttributes[row.slot_column] = row.field_name
612
612
  }
613
613
 
614
614
  for (const row of relationshipRows) {
615
615
  if (row.relationship_type === 'belongsTo') {
616
- const alias = row.alias || row.relationship_name;
616
+ const alias = row.alias || row.relationship_name
617
617
  belongsTo[alias] = {
618
618
  alias,
619
619
  field: fields[row.relationship_name]?.field || row.relationship_name,
@@ -621,7 +621,7 @@ export class AnyapiRegistry {
621
621
  slotIndex: row.slot_index,
622
622
  idColumn: row.id_column,
623
623
  typeColumn: row.type_column,
624
- };
624
+ }
625
625
  } else if (row.relationship_type === 'manyToMany') {
626
626
  manyToMany[row.relationship_name] = {
627
627
  alias: row.alias || row.relationship_name,
@@ -631,17 +631,17 @@ export class AnyapiRegistry {
631
631
  foreignKey: row.foreign_key,
632
632
  otherKey: row.other_key,
633
633
  meta: row.meta_json ? JSON.parse(row.meta_json) : undefined,
634
- };
634
+ }
635
635
  }
636
636
  }
637
637
 
638
- const schemaObj = schema || {};
638
+ const schemaObj = schema || {}
639
639
  for (const [fieldName, fieldDef] of Object.entries(schemaObj)) {
640
- if (!fieldDef?.belongsToPolymorphic) continue;
641
- const alias = fieldDef.as || fieldName;
642
- const { typeField, idField, types = [] } = fieldDef.belongsToPolymorphic;
643
- const typeEntry = fields[typeField] || null;
644
- const idEntry = fields[idField] || null;
640
+ if (!fieldDef?.belongsToPolymorphic) continue
641
+ const alias = fieldDef.as || fieldName
642
+ const { typeField, idField, types = [] } = fieldDef.belongsToPolymorphic
643
+ const typeEntry = fields[typeField] || null
644
+ const idEntry = fields[idField] || null
645
645
  polymorphicBelongsTo[alias] = {
646
646
  alias,
647
647
  typeField,
@@ -649,15 +649,15 @@ export class AnyapiRegistry {
649
649
  typeColumn: typeEntry?.slot || null,
650
650
  idColumn: idEntry?.slot || null,
651
651
  types,
652
- };
652
+ }
653
653
  }
654
654
 
655
655
  for (const [relName, relDef] of Object.entries(relationships || {})) {
656
- if (!relDef?.belongsToPolymorphic) continue;
657
- const alias = relDef.as || relName;
658
- const { typeField, idField, types = [] } = relDef.belongsToPolymorphic;
659
- const typeEntry = fields[typeField] || null;
660
- const idEntry = fields[idField] || null;
656
+ if (!relDef?.belongsToPolymorphic) continue
657
+ const alias = relDef.as || relName
658
+ const { typeField, idField, types = [] } = relDef.belongsToPolymorphic
659
+ const typeEntry = fields[typeField] || null
660
+ const idEntry = fields[idField] || null
661
661
  polymorphicBelongsTo[alias] = {
662
662
  alias,
663
663
  typeField,
@@ -665,7 +665,7 @@ export class AnyapiRegistry {
665
665
  typeColumn: typeEntry?.slot || null,
666
666
  idColumn: idEntry?.slot || null,
667
667
  types,
668
- };
668
+ }
669
669
  }
670
670
 
671
671
  return {
@@ -680,10 +680,10 @@ export class AnyapiRegistry {
680
680
  polymorphicBelongsTo,
681
681
  reverseAttributes,
682
682
  slotState,
683
- };
683
+ }
684
684
  }
685
685
 
686
- #initializeSlotState() {
686
+ #initializeSlotState () {
687
687
  return {
688
688
  string: 0,
689
689
  number: 0,
@@ -691,75 +691,75 @@ export class AnyapiRegistry {
691
691
  date: 0,
692
692
  json: 0,
693
693
  belongsTo: 0,
694
- };
694
+ }
695
695
  }
696
696
 
697
- #assignFieldSlot(fieldDef, slotState, canonicalOverride = null) {
697
+ #assignFieldSlot (fieldDef, slotState, canonicalOverride = null) {
698
698
  if (fieldDef.belongsTo) {
699
- return this.#assignBelongsTo(fieldDef, slotState, canonicalOverride);
699
+ return this.#assignBelongsTo(fieldDef, slotState, canonicalOverride)
700
700
  }
701
701
 
702
- const { type } = fieldDef;
702
+ const { type } = fieldDef
703
703
  if (!type || type === 'id') {
704
- return null;
704
+ return null
705
705
  }
706
706
  if (!SUPPORTED_TYPES.has(type)) {
707
- throw new Error(`Unsupported field type '${type}'`);
707
+ throw new Error(`Unsupported field type '${type}'`)
708
708
  }
709
709
 
710
- const pool = TYPE_TO_POOL.get(type);
710
+ const pool = TYPE_TO_POOL.get(type)
711
711
  if (!pool) {
712
- throw new Error(`No slot pool defined for type '${type}'`);
712
+ throw new Error(`No slot pool defined for type '${type}'`)
713
713
  }
714
714
 
715
715
  if (canonicalOverride) {
716
716
  const slotColumn = typeof canonicalOverride === 'string'
717
717
  ? canonicalOverride
718
- : canonicalOverride.slot || canonicalOverride.slotColumn;
718
+ : canonicalOverride.slot || canonicalOverride.slotColumn
719
719
  if (!slotColumn) {
720
- throw new Error(`Canonical mapping for field '${fieldDef.fieldName}' must provide a slot column`);
720
+ throw new Error(`Canonical mapping for field '${fieldDef.fieldName}' must provide a slot column`)
721
721
  }
722
- const { slotType, slotIndex } = parseSimpleSlotColumn(slotColumn);
722
+ const { slotType, slotIndex } = parseSimpleSlotColumn(slotColumn)
723
723
  if (slotType !== pool) {
724
724
  throw new Error(
725
- `Canonical slot '${slotColumn}' does not match expected type '${pool}' for field '${fieldDef.fieldName}'`,
726
- );
725
+ `Canonical slot '${slotColumn}' does not match expected type '${pool}' for field '${fieldDef.fieldName}'`
726
+ )
727
727
  }
728
- slotState[pool] = Math.max(slotState[pool], slotIndex);
728
+ slotState[pool] = Math.max(slotState[pool], slotIndex)
729
729
  return {
730
730
  slotType: pool,
731
731
  slotIndex,
732
732
  slotColumn,
733
733
  updatedState: slotState,
734
- };
734
+ }
735
735
  }
736
736
 
737
- const nextIndex = slotState[pool] + 1;
737
+ const nextIndex = slotState[pool] + 1
738
738
  if (nextIndex > SLOT_LIMITS[pool]) {
739
- throw new Error(`No available ${pool} slots remaining`);
739
+ throw new Error(`No available ${pool} slots remaining`)
740
740
  }
741
741
 
742
- slotState[pool] = nextIndex;
743
- const slotColumn = SLOT_POOLS[pool][nextIndex - 1];
742
+ slotState[pool] = nextIndex
743
+ const slotColumn = SLOT_POOLS[pool][nextIndex - 1]
744
744
 
745
745
  return {
746
746
  slotType: pool,
747
747
  slotIndex: nextIndex,
748
748
  slotColumn,
749
749
  updatedState: slotState,
750
- };
750
+ }
751
751
  }
752
752
 
753
- #assignBelongsTo(fieldDef, slotState, canonicalOverride = null) {
754
- const alias = BELONGS_TO_ALIAS(fieldDef.fieldName || '', fieldDef);
755
- const relationshipKey = fieldDef.relationshipKey || null;
753
+ #assignBelongsTo (fieldDef, slotState, canonicalOverride = null) {
754
+ const alias = BELONGS_TO_ALIAS(fieldDef.fieldName || '', fieldDef)
755
+ const relationshipKey = fieldDef.relationshipKey || null
756
756
 
757
757
  if (canonicalOverride) {
758
758
  const normalized = typeof canonicalOverride === 'string'
759
759
  ? { idSlot: canonicalOverride }
760
- : canonicalOverride;
761
- const { slotIndex, idColumn, typeColumn } = parseBelongsToSlots(normalized || {});
762
- slotState.belongsTo = Math.max(slotState.belongsTo, slotIndex);
760
+ : canonicalOverride
761
+ const { slotIndex, idColumn, typeColumn } = parseBelongsToSlots(normalized || {})
762
+ slotState.belongsTo = Math.max(slotState.belongsTo, slotIndex)
763
763
  return {
764
764
  slotType: 'belongsTo',
765
765
  slotIndex,
@@ -783,15 +783,15 @@ export class AnyapiRegistry {
783
783
  nullable: fieldDef.nullable === true,
784
784
  },
785
785
  updatedState: slotState,
786
- };
786
+ }
787
787
  }
788
788
 
789
- const nextIndex = slotState.belongsTo + 1;
789
+ const nextIndex = slotState.belongsTo + 1
790
790
  if (nextIndex > SLOT_LIMITS.belongsTo) {
791
- throw new Error('No available belongsTo slots remaining');
791
+ throw new Error('No available belongsTo slots remaining')
792
792
  }
793
793
 
794
- slotState.belongsTo = nextIndex;
794
+ slotState.belongsTo = nextIndex
795
795
 
796
796
  return {
797
797
  slotType: 'belongsTo',
@@ -816,6 +816,6 @@ export class AnyapiRegistry {
816
816
  nullable: fieldDef.nullable === true,
817
817
  },
818
818
  updatedState: slotState,
819
- };
819
+ }
820
820
  }
821
821
  }