@rebasepro/server 0.13.1-canary.gef9608c → 0.14.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.
Files changed (132) hide show
  1. package/dist/{admin_block-EmIHae6X.js → admin_block-H86dCPsj.js} +61 -2
  2. package/dist/admin_block-H86dCPsj.js.map +1 -0
  3. package/dist/api/ast-schema-editor.d.ts +74 -6
  4. package/dist/api/errors.d.ts +12 -0
  5. package/dist/api/rest/api-generator.d.ts +26 -1
  6. package/dist/api/rest/idempotency.d.ts +25 -4
  7. package/dist/api/rest/query-parser.d.ts +15 -1
  8. package/dist/api/rest/write-validation.d.ts +40 -4
  9. package/dist/auth/adapter-middleware.d.ts +3 -1
  10. package/dist/auth/admin-roles.d.ts +24 -0
  11. package/dist/auth/apple-oauth.d.ts +16 -11
  12. package/dist/auth/bitbucket-oauth.d.ts +2 -4
  13. package/dist/auth/builtin-auth-adapter.d.ts +11 -0
  14. package/dist/auth/discord-oauth.d.ts +5 -6
  15. package/dist/auth/facebook-oauth.d.ts +2 -4
  16. package/dist/auth/github-oauth.d.ts +2 -4
  17. package/dist/auth/gitlab-oauth.d.ts +6 -4
  18. package/dist/auth/google-oauth.d.ts +1 -0
  19. package/dist/auth/index.d.ts +7 -0
  20. package/dist/auth/interfaces.d.ts +81 -1
  21. package/dist/auth/jwt.d.ts +40 -0
  22. package/dist/auth/linkedin-oauth.d.ts +2 -4
  23. package/dist/auth/mfa-crypto.d.ts +54 -3
  24. package/dist/auth/mfa-gate.d.ts +42 -0
  25. package/dist/auth/mfa-routes.d.ts +26 -1
  26. package/dist/auth/mfa.d.ts +16 -0
  27. package/dist/auth/microsoft-oauth.d.ts +19 -4
  28. package/dist/auth/middleware.d.ts +26 -0
  29. package/dist/auth/oauth-code-flow.d.ts +66 -0
  30. package/dist/auth/oauth-signin-policy.d.ts +61 -0
  31. package/dist/auth/oidc-id-token.d.ts +61 -0
  32. package/dist/auth/rate-limiter.d.ts +62 -5
  33. package/dist/auth/registration-policy.d.ts +31 -0
  34. package/dist/auth/rls-scope.d.ts +25 -0
  35. package/dist/auth/routes.d.ts +15 -0
  36. package/dist/auth/slack-oauth.d.ts +2 -4
  37. package/dist/auth/spotify-oauth.d.ts +2 -4
  38. package/dist/auth/token-revocation.d.ts +38 -0
  39. package/dist/auth/twitter-oauth.d.ts +2 -5
  40. package/dist/{auth-DU4ShERo.js → auth-CYoPVf-E.js} +1985 -395
  41. package/dist/auth-CYoPVf-E.js.map +1 -0
  42. package/dist/{backup-BJiY_mM-.js → backup-C6ljYVTp.js} +2 -2
  43. package/dist/{backup-BJiY_mM-.js.map → backup-C6ljYVTp.js.map} +1 -1
  44. package/dist/boot/ddl-bootstrap.d.ts +70 -0
  45. package/dist/{contract-routes-Dj8i5AiM.js → contract-routes-Bet-eCNJ.js} +3 -3
  46. package/dist/{contract-routes-Dj8i5AiM.js.map → contract-routes-Bet-eCNJ.js.map} +1 -1
  47. package/dist/cron/cron-loader.d.ts +26 -1
  48. package/dist/cron/cron-routes.d.ts +1 -1
  49. package/dist/cron/cron-scheduler.d.ts +13 -3
  50. package/dist/cron/define-cron.d.ts +17 -3
  51. package/dist/cron/index.d.ts +2 -2
  52. package/dist/{cron-loader-B1S2MCSl.js → cron-loader-YhhQeVBM.js} +43 -8
  53. package/dist/cron-loader-YhhQeVBM.js.map +1 -0
  54. package/dist/{cron-routes-D5a9v1HY.js → cron-routes-maM_RlUu.js} +10 -4
  55. package/dist/cron-routes-maM_RlUu.js.map +1 -0
  56. package/dist/{cron-scheduler-BhFZWR0T.js → cron-scheduler-DIpYBmZP.js} +20 -8
  57. package/dist/cron-scheduler-DIpYBmZP.js.map +1 -0
  58. package/dist/{cron-store-Cu4sOseb.js → cron-store-Dvr4Y1sZ.js} +60 -51
  59. package/dist/cron-store-Dvr4Y1sZ.js.map +1 -0
  60. package/dist/ddl-bootstrap-BhXbTnBl.js +183 -0
  61. package/dist/ddl-bootstrap-BhXbTnBl.js.map +1 -0
  62. package/dist/email/html.d.ts +54 -0
  63. package/dist/email/index.d.ts +3 -0
  64. package/dist/email/link-base.d.ts +39 -0
  65. package/dist/email/smtp-email-service.d.ts +7 -1
  66. package/dist/email/templates.d.ts +9 -1
  67. package/dist/email/types.d.ts +11 -1
  68. package/dist/env.d.ts +5 -0
  69. package/dist/{errors-BpudWAVU.js → errors-EBYiaJ2E.js} +14 -2
  70. package/dist/errors-EBYiaJ2E.js.map +1 -0
  71. package/dist/function-loader-DDS1v7YX.js +139 -0
  72. package/dist/function-loader-DDS1v7YX.js.map +1 -0
  73. package/dist/{function-routes-C0cLIy3N.js → function-routes-Btcez1T-.js} +18 -6
  74. package/dist/function-routes-Btcez1T-.js.map +1 -0
  75. package/dist/functions/define-function.d.ts +25 -6
  76. package/dist/functions/function-loader.d.ts +23 -0
  77. package/dist/functions/function-routes.d.ts +7 -1
  78. package/dist/functions/request-timeout.d.ts +34 -0
  79. package/dist/history/history-routes.d.ts +6 -0
  80. package/dist/index.d.ts +3 -2
  81. package/dist/index.es.js +1893 -714
  82. package/dist/index.es.js.map +1 -1
  83. package/dist/init/docs.d.ts +10 -1
  84. package/dist/init/process-safety.d.ts +25 -0
  85. package/dist/init.d.ts +38 -0
  86. package/dist/{jwt-CzeviDcB.js → jwt-_IFqfTOg.js} +57 -9
  87. package/dist/jwt-_IFqfTOg.js.map +1 -0
  88. package/dist/logger-DfvF_8r-.js +190 -0
  89. package/dist/logger-DfvF_8r-.js.map +1 -0
  90. package/dist/{openapi-generator-D5xsfVJY.js → openapi-generator-DPKtUC9X.js} +326 -55
  91. package/dist/openapi-generator-DPKtUC9X.js.map +1 -0
  92. package/dist/request-timeout-RivJsME0.js +65 -0
  93. package/dist/request-timeout-RivJsME0.js.map +1 -0
  94. package/dist/schema-editor-routes-CRcS3ArS.js +437 -0
  95. package/dist/schema-editor-routes-CRcS3ArS.js.map +1 -0
  96. package/dist/serve-spa.d.ts +5 -4
  97. package/dist/services/outbound-url-guard.d.ts +53 -0
  98. package/dist/services/routed-realtime-service.d.ts +10 -2
  99. package/dist/services/webhook-service.d.ts +76 -1
  100. package/dist/singleton.d.ts +25 -8
  101. package/dist/{src-CKOZBpeJ.js → src-C7rkDGxA.js} +619 -70
  102. package/dist/src-C7rkDGxA.js.map +1 -0
  103. package/dist/{src-_qQ3RNCK.js → src-Cz9nMgUR.js} +93 -3
  104. package/dist/src-Cz9nMgUR.js.map +1 -0
  105. package/dist/storage/LocalStorageController.d.ts +12 -1
  106. package/dist/storage/image-transform.d.ts +57 -0
  107. package/dist/storage/keys.d.ts +98 -0
  108. package/dist/storage/routes.d.ts +11 -0
  109. package/dist/storage/tus-handler.d.ts +22 -2
  110. package/dist/utils/logger.d.ts +18 -0
  111. package/dist/utils/sql.d.ts +11 -6
  112. package/package.json +6 -5
  113. package/dist/admin_block-EmIHae6X.js.map +0 -1
  114. package/dist/auth-DU4ShERo.js.map +0 -1
  115. package/dist/backend-CIxN4FVm.js +0 -15
  116. package/dist/backend-CIxN4FVm.js.map +0 -1
  117. package/dist/cron-loader-B1S2MCSl.js.map +0 -1
  118. package/dist/cron-routes-D5a9v1HY.js.map +0 -1
  119. package/dist/cron-scheduler-BhFZWR0T.js.map +0 -1
  120. package/dist/cron-store-Cu4sOseb.js.map +0 -1
  121. package/dist/errors-BpudWAVU.js.map +0 -1
  122. package/dist/function-loader-B_1fYfUY.js +0 -86
  123. package/dist/function-loader-B_1fYfUY.js.map +0 -1
  124. package/dist/function-routes-C0cLIy3N.js.map +0 -1
  125. package/dist/jwt-CzeviDcB.js.map +0 -1
  126. package/dist/logger-BYU66ENZ.js +0 -94
  127. package/dist/logger-BYU66ENZ.js.map +0 -1
  128. package/dist/openapi-generator-D5xsfVJY.js.map +0 -1
  129. package/dist/schema-editor-routes-C1DxDnDR.js +0 -248
  130. package/dist/schema-editor-routes-C1DxDnDR.js.map +0 -1
  131. package/dist/src-CKOZBpeJ.js.map +0 -1
  132. package/dist/src-_qQ3RNCK.js.map +0 -1
@@ -1,8 +1,8 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  import "process";
3
3
  __createRequire(import.meta.url);
4
- import { h as resolveCollectionRelations } from "./src-CKOZBpeJ.js";
5
- import "./src-_qQ3RNCK.js";
4
+ import { _ as resolveCollectionRelations } from "./src-C7rkDGxA.js";
5
+ import "./src-Cz9nMgUR.js";
6
6
  //#region ../types/src/types/relations.ts
7
7
  /** @group Models */
8
8
  function isToMany(relation) {
@@ -31,9 +31,10 @@ function generateOpenApiSpec(collections, options = {}) {
31
31
  schema: {
32
32
  type: "integer",
33
33
  default: defaultLimit,
34
+ minimum: 1,
34
35
  maximum: maxLimit
35
36
  },
36
- description: "Maximum number of records to return"
37
+ description: `Maximum number of records to return. Must be a whole number between 1 and ${maxLimit}; a larger one is rejected with 400 INVALID_LIMIT rather than trimmed, so a short page always means a short collection. Page past the ceiling with \`offset\`.`
37
38
  },
38
39
  {
39
40
  name: "offset",
@@ -99,7 +100,41 @@ function generateOpenApiSpec(collections, options = {}) {
99
100
  name: "searchString",
100
101
  in: "query",
101
102
  schema: { type: "string" },
102
- description: "Full-text search query"
103
+ description: "Text search. By default a case-insensitive substring match OR-ed across the collection's top-level string properties. A collection declaring a `search` block gets ranked full-text matching over the fields it names, and rows carry a `_score`."
104
+ },
105
+ {
106
+ name: "vector_search",
107
+ in: "query",
108
+ schema: { type: "string" },
109
+ description: "Name of the `vector` property to run a nearest-neighbour search against. Requires `vector`.",
110
+ example: "embedding"
111
+ },
112
+ {
113
+ name: "vector",
114
+ in: "query",
115
+ schema: { type: "string" },
116
+ description: "The query embedding, as a JSON array of numbers. Its length must match the property's declared `dimensions`.",
117
+ example: "[0.12,-0.04,0.98]"
118
+ },
119
+ {
120
+ name: "vector_distance",
121
+ in: "query",
122
+ schema: {
123
+ type: "string",
124
+ enum: [
125
+ "cosine",
126
+ "l2",
127
+ "inner_product"
128
+ ],
129
+ default: "cosine"
130
+ },
131
+ description: "Distance function used for ordering."
132
+ },
133
+ {
134
+ name: "vector_threshold",
135
+ in: "query",
136
+ schema: { type: "number" },
137
+ description: "Drop rows farther than this distance. Rows are returned closest-first with a `_distance` field."
103
138
  }
104
139
  ];
105
140
  const spec = {
@@ -155,27 +190,20 @@ function generateOpenApiSpec(collections, options = {}) {
155
190
  tags: []
156
191
  };
157
192
  if (requireAuth) {
158
- spec.components.securitySchemes = {
159
- bearerAuth: {
160
- type: "http",
161
- scheme: "bearer",
162
- bearerFormat: "JWT",
163
- description: "JWT access token obtained from `POST /auth/login` or `POST /auth/register`. Can also be a static service key for server-to-server authentication."
164
- },
165
- queryToken: {
166
- type: "apiKey",
167
- in: "query",
168
- name: "token",
169
- description: "Alternative: pass the JWT or service key as a `token` query parameter."
170
- }
171
- };
172
- spec.security = [{ bearerAuth: [] }, { queryToken: [] }];
193
+ spec.components.securitySchemes = { bearerAuth: {
194
+ type: "http",
195
+ scheme: "bearer",
196
+ bearerFormat: "JWT",
197
+ description: "JWT access token obtained from `POST /auth/login` or `POST /auth/register`. Can also be a static service key for server-to-server authentication."
198
+ } };
199
+ spec.security = [{ bearerAuth: [] }];
173
200
  }
174
201
  const paths = spec.paths;
175
202
  const schemas = spec.components.schemas;
176
203
  const tags = spec.tags;
204
+ const reservedParameterNames = new Set(listQueryParameters().map((p) => p.name));
177
205
  for (const collection of collections || []) {
178
- const schemaName = toPascalCase(collection.singularName || collection.name);
206
+ const schemaName = schemaNameFor(collection);
179
207
  const slug = collection.slug;
180
208
  tags.push({
181
209
  name: collection.name,
@@ -183,13 +211,14 @@ function generateOpenApiSpec(collections, options = {}) {
183
211
  });
184
212
  schemas[schemaName] = buildCollectionSchema(collection);
185
213
  schemas[`${schemaName}Input`] = buildCollectionInputSchema(collection);
214
+ schemas[`${schemaName}Update`] = buildCollectionUpdateSchema(collection);
186
215
  const dataPath = `/data/${slug}`;
187
216
  paths[dataPath] = {
188
217
  get: {
189
218
  tags: [collection.name],
190
219
  summary: `List ${collection.name}`,
191
220
  operationId: `list${schemaName}`,
192
- parameters: [...listQueryParameters(), ...buildFilterParameters(collection)],
221
+ parameters: [...listQueryParameters(), ...buildFilterParameters(collection, reservedParameterNames)],
193
222
  responses: {
194
223
  200: {
195
224
  description: "Paginated list of entities",
@@ -224,6 +253,159 @@ function generateOpenApiSpec(collections, options = {}) {
224
253
  }
225
254
  }
226
255
  };
256
+ const idempotencyHeader = {
257
+ name: "Idempotency-Key",
258
+ in: "header",
259
+ required: false,
260
+ schema: { type: "string" },
261
+ description: "Names this write so a retry is recognised instead of repeated. Without it a client that lost the response cannot distinguish a replay from a second genuine batch, and the whole batch is written twice. A key names one request: re-send the identical request to replay its answer, and use a new key for a different one."
262
+ };
263
+ const bulkErrors = {
264
+ 400: {
265
+ description: "Malformed body, an unknown field, or more rows than the per-batch limit",
266
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
267
+ },
268
+ 409: {
269
+ description: "A request with the same Idempotency-Key is still in flight. Retry it: the first attempt's result is replayed once it lands",
270
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
271
+ },
272
+ 422: {
273
+ description: "The Idempotency-Key was already used for a different request",
274
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
275
+ },
276
+ ...errorResponses(requireAuth)
277
+ };
278
+ paths[`/data/${slug}/bulk`] = {
279
+ post: {
280
+ tags: [collection.name],
281
+ summary: `Create many ${collection.name} in one transaction`,
282
+ description: "All-or-nothing: if any row is rejected none of them land, and the error names the offending index. Every row still runs callbacks, relations and row-level security. Capped server-side because one batch holds its locks for its whole duration.",
283
+ operationId: `createMany${schemaName}`,
284
+ parameters: [idempotencyHeader],
285
+ requestBody: {
286
+ required: true,
287
+ content: { "application/json": { schema: {
288
+ type: "object",
289
+ required: ["rows"],
290
+ properties: {
291
+ rows: {
292
+ type: "array",
293
+ items: { $ref: `#/components/schemas/${schemaName}Input` }
294
+ },
295
+ upsert: {
296
+ type: "boolean",
297
+ description: "Write each row as INSERT ... ON CONFLICT DO UPDATE on the primary key."
298
+ }
299
+ }
300
+ } } }
301
+ },
302
+ responses: {
303
+ 200: {
304
+ description: "The written rows, in the order given",
305
+ content: { "application/json": { schema: {
306
+ type: "object",
307
+ properties: {
308
+ data: {
309
+ type: "array",
310
+ items: { $ref: `#/components/schemas/${schemaName}` }
311
+ },
312
+ meta: {
313
+ type: "object",
314
+ properties: { written: { type: "integer" } }
315
+ }
316
+ }
317
+ } } }
318
+ },
319
+ ...bulkErrors
320
+ }
321
+ },
322
+ patch: {
323
+ tags: [collection.name],
324
+ summary: `Update many ${collection.name} in one transaction`,
325
+ description: "Each entry names its row and the fields to change. `{ id, data }` rather than flat rows carrying their own key, because on a table keyed on something other than `id` a flat row cannot say whether a column is the address or a value to write. An id matching no row fails the batch.",
326
+ operationId: `updateMany${schemaName}`,
327
+ parameters: [idempotencyHeader],
328
+ requestBody: {
329
+ required: true,
330
+ content: { "application/json": { schema: {
331
+ type: "object",
332
+ required: ["updates"],
333
+ properties: { updates: {
334
+ type: "array",
335
+ items: {
336
+ type: "object",
337
+ required: ["id", "data"],
338
+ properties: {
339
+ id: {
340
+ type: "string",
341
+ description: "The row to update"
342
+ },
343
+ data: { $ref: `#/components/schemas/${schemaName}Update` }
344
+ }
345
+ }
346
+ } }
347
+ } } }
348
+ },
349
+ responses: {
350
+ 200: {
351
+ description: "The updated rows, in the order given",
352
+ content: { "application/json": { schema: {
353
+ type: "object",
354
+ properties: {
355
+ data: {
356
+ type: "array",
357
+ items: { $ref: `#/components/schemas/${schemaName}` }
358
+ },
359
+ meta: {
360
+ type: "object",
361
+ properties: { written: { type: "integer" } }
362
+ }
363
+ }
364
+ } } }
365
+ },
366
+ 404: {
367
+ description: "One of the ids matches no row; nothing was written",
368
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
369
+ },
370
+ ...bulkErrors
371
+ }
372
+ }
373
+ };
374
+ paths[`/data/${slug}/bulk/delete`] = { post: {
375
+ tags: [collection.name],
376
+ summary: `Delete many ${collection.name} in one transaction`,
377
+ description: "A POST, not `DELETE /bulk` with a body. Bodies on DELETE are permitted but widely dropped by proxies and CDNs, and several generators ignore `requestBody` on a DELETE operation — a generated client would send the request with no ids at all. Takes ids rather than a filter: a mistyped condition that empties a table cannot be reviewed at the call site the way an explicit list can. `beforeDelete`/`afterDelete` fire per row.",
378
+ operationId: `deleteMany${schemaName}`,
379
+ parameters: [idempotencyHeader],
380
+ requestBody: {
381
+ required: true,
382
+ content: { "application/json": { schema: {
383
+ type: "object",
384
+ required: ["ids"],
385
+ properties: { ids: {
386
+ type: "array",
387
+ items: { oneOf: [{ type: "string" }, { type: "integer" }] }
388
+ } }
389
+ } } }
390
+ },
391
+ responses: {
392
+ 200: {
393
+ description: "How many rows were deleted",
394
+ content: { "application/json": { schema: {
395
+ type: "object",
396
+ properties: { meta: {
397
+ type: "object",
398
+ properties: { deleted: { type: "integer" } }
399
+ } }
400
+ } } }
401
+ },
402
+ 404: {
403
+ description: "One of the ids matches no row; nothing was deleted",
404
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
405
+ },
406
+ ...bulkErrors
407
+ }
408
+ } };
227
409
  const entityPath = `/data/${slug}/{id}`;
228
410
  paths[entityPath] = {
229
411
  get: {
@@ -255,32 +437,11 @@ function generateOpenApiSpec(collections, options = {}) {
255
437
  ...errorResponses(requireAuth)
256
438
  }
257
439
  },
440
+ patch: updateOperation(collection, schemaName, requireAuth),
258
441
  put: {
259
- tags: [collection.name],
260
- summary: `Update ${collection.singularName || collection.name}`,
261
- operationId: `update${schemaName}`,
262
- parameters: [{
263
- name: "id",
264
- in: "path",
265
- required: true,
266
- schema: { type: "string" },
267
- description: "Entity ID"
268
- }],
269
- requestBody: {
270
- required: true,
271
- content: { "application/json": { schema: { $ref: `#/components/schemas/${schemaName}Input` } } }
272
- },
273
- responses: {
274
- 200: {
275
- description: "Updated entity",
276
- content: { "application/json": { schema: { $ref: `#/components/schemas/${schemaName}` } } }
277
- },
278
- 404: {
279
- description: "Entity not found",
280
- content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
281
- },
282
- ...errorResponses(requireAuth)
283
- }
442
+ ...updateOperation(collection, schemaName, requireAuth),
443
+ operationId: `update${schemaName}ViaPut`,
444
+ deprecated: true
284
445
  },
285
446
  delete: {
286
447
  tags: [collection.name],
@@ -306,12 +467,11 @@ function generateOpenApiSpec(collections, options = {}) {
306
467
  }
307
468
  for (const collection of collections || []) {
308
469
  const slug = collection.slug;
309
- const schemaName = toPascalCase(collection.singularName || collection.name);
470
+ const schemaName = schemaNameFor(collection);
310
471
  const relations = Object.values(resolveCollectionRelations(collection)).filter(isToMany);
311
472
  for (const relation of relations) {
312
473
  const relationName = relation.relationName;
313
- const targetCollection = relation.target();
314
- const targetSchema = toPascalCase(targetCollection.singularName || targetCollection.name);
474
+ const targetSchema = schemaNameFor(relation.target());
315
475
  const subPath = `/data/${slug}/{parentId}/${relationName}`;
316
476
  paths[subPath] = { get: {
317
477
  tags: [collection.name],
@@ -346,8 +506,36 @@ function generateOpenApiSpec(collections, options = {}) {
346
506
  return spec;
347
507
  }
348
508
  /**
509
+ * Is this property part of the shape the document may describe?
510
+ *
511
+ * Two exclusions, and both are the document telling the truth about what the
512
+ * server does:
513
+ *
514
+ * - a `relation` property is virtual: the row carries the owning side's foreign
515
+ * key column, never the property itself, so it is not a field of any payload.
516
+ * (The FK column *is* readable, writable and filterable and is still absent
517
+ * from every schema here — a separate gap, not this rule.)
518
+ * - `excludeFromApi` is a server-side guarantee that the column "is stripped
519
+ * from every row the API serves, for every caller, including admins and
520
+ * service keys" — `stripExcluded` in the row pipeline enforces it. A schema
521
+ * that lists such a column describes a field that is never present, and it
522
+ * describes it to everyone: `/docs` is mounted on the app, not on the data
523
+ * router, so it carries none of the auth middleware `{basePath}/data` does.
524
+ * Every project scaffolded by `rebase init` published its `users`
525
+ * collection's `passwordHash` and `emailVerificationToken` this way.
526
+ *
527
+ * Written as one predicate rather than three `continue`s because it kept being
528
+ * fixed in one loop at a time: this is the same rule the SDK generator applies
529
+ * to its `Row` type (`packages/codegen/src/generate-types.ts`).
530
+ */
531
+ function isDocumentedProperty(property) {
532
+ return property.type !== "relation" && !property.excludeFromApi;
533
+ }
534
+ /**
349
535
  * Build the component schema for a collection (output / read shape).
350
- * All fields are included (including relation foreign keys).
536
+ *
537
+ * Every declared property except the ones {@link isDocumentedProperty} rules
538
+ * out.
351
539
  */
352
540
  function buildCollectionSchema(collection) {
353
541
  const properties = { id: {
@@ -356,7 +544,7 @@ function buildCollectionSchema(collection) {
356
544
  } };
357
545
  const required = ["id"];
358
546
  for (const [key, property] of Object.entries(collection.properties)) {
359
- if (property.type === "relation") continue;
547
+ if (!isDocumentedProperty(property)) continue;
360
548
  properties[key] = convertPropertyToSchema(property);
361
549
  if (property.validation?.required) required.push(key);
362
550
  }
@@ -367,13 +555,71 @@ function buildCollectionSchema(collection) {
367
555
  };
368
556
  }
369
557
  /**
558
+ * The PATCH/PUT operation for `/data/{slug}/{id}`.
559
+ *
560
+ * Split out because both verbs serve it and they must not drift: the update
561
+ * body is a **partial**, and describing it with the create schema was the bug
562
+ * this replaces. `<Name>Input` marks every `validation.required` property as
563
+ * required — correct for POST, wrong for an update, where omitting a field
564
+ * means "leave it alone" rather than "I forgot it". A client generated from
565
+ * that spec demanded fields the server does not, and a spec-validating gateway
566
+ * would have rejected partial updates the server accepts.
567
+ */
568
+ function updateOperation(collection, schemaName, requireAuth) {
569
+ return {
570
+ tags: [collection.name],
571
+ summary: `Update ${collection.singularName || collection.name}`,
572
+ description: "Partial update: only the properties present in the body are written; the rest are left unchanged.",
573
+ operationId: `update${schemaName}`,
574
+ parameters: [{
575
+ name: "id",
576
+ in: "path",
577
+ required: true,
578
+ schema: { type: "string" },
579
+ description: "Entity ID"
580
+ }],
581
+ requestBody: {
582
+ required: true,
583
+ content: { "application/json": { schema: { $ref: `#/components/schemas/${schemaName}Update` } } }
584
+ },
585
+ responses: {
586
+ 200: {
587
+ description: "Updated entity",
588
+ content: { "application/json": { schema: { $ref: `#/components/schemas/${schemaName}` } } }
589
+ },
590
+ 404: {
591
+ description: "Entity not found",
592
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
593
+ },
594
+ ...errorResponses(requireAuth)
595
+ }
596
+ };
597
+ }
598
+ /**
599
+ * The update body: the create schema with `required` dropped.
600
+ *
601
+ * Derived rather than rebuilt so the two cannot describe different columns —
602
+ * the only difference between creating and updating is which fields you must
603
+ * supply, and that is exactly the one thing removed here.
604
+ */
605
+ function buildCollectionUpdateSchema(collection) {
606
+ const { required: _required, ...rest } = buildCollectionInputSchema(collection);
607
+ return rest;
608
+ }
609
+ /**
370
610
  * Build an input schema (for POST/PUT) — excludes auto-generated fields.
611
+ *
612
+ * `excludeFromApi` columns are left out too, though a write naming one is still
613
+ * accepted: a create the document invites and whose result it then cannot show
614
+ * is not a round trip anyone can verify, and naming the column here discloses
615
+ * it just as loudly as the read schema does — this document is served
616
+ * unauthenticated.
371
617
  */
372
618
  function buildCollectionInputSchema(collection) {
373
619
  const properties = {};
374
620
  const required = [];
375
621
  for (const [key, property] of Object.entries(collection.properties)) {
376
- if (property.type === "relation") continue;
622
+ if (!isDocumentedProperty(property)) continue;
377
623
  if (property.type === "date" && property.autoValue) continue;
378
624
  if ("isId" in property && property.isId && property.isId !== "manual" && property.isId !== true) continue;
379
625
  properties[key] = convertPropertyToSchema(property);
@@ -534,11 +780,18 @@ function resolveEnumValues(enumDef) {
534
780
  /**
535
781
  * Build PostgREST-style filter parameters for a collection.
536
782
  * These are additional query parameters like `?status=eq.active&price=gte.100`.
783
+ *
784
+ * `excludeFromApi` columns are not offered: the server does filter on them, and
785
+ * that is exactly the problem — a filter on a column no response can contain
786
+ * answers questions about the value one row at a time, which is a worse
787
+ * disclosure than the column name alone.
537
788
  */
538
- function buildFilterParameters(collection) {
789
+ function buildFilterParameters(collection, reservedNames = /* @__PURE__ */ new Set()) {
539
790
  const params = [];
540
791
  for (const [key, property] of Object.entries(collection.properties)) {
541
- if (property.type === "relation" || property.type === "map" || property.type === "array" || property.type === "geopoint") continue;
792
+ if (!isDocumentedProperty(property)) continue;
793
+ if (property.type === "map" || property.type === "array" || property.type === "geopoint") continue;
794
+ if (reservedNames.has(key)) continue;
542
795
  params.push({
543
796
  name: key,
544
797
  in: "query",
@@ -583,6 +836,24 @@ function withIndefiniteArticle(noun) {
583
836
  return `${/^[aeiou]/i.test(noun) ? "an" : "a"} ${noun}`;
584
837
  }
585
838
  /**
839
+ * The component-schema name for a collection — and the stem of every
840
+ * `operationId` and `$ref` that mentions it.
841
+ *
842
+ * `toPascalCase` keeps ASCII letters and digits and nothing else, so a name
843
+ * written in a script that has none of them — a Cyrillic or Japanese
844
+ * `singularName`, which the docs' six locales make ordinary rather than exotic
845
+ * — reduced to the empty string. The schema was then stored under `""` and
846
+ * every reference to it read `#/components/schemas/`, an unresolvable pointer:
847
+ * Swagger UI renders the model empty and a strict generator fails outright. So
848
+ * fall through the names until one survives, and keep a constant as the floor.
849
+ *
850
+ * Two collections whose names PascalCase identically still share one component;
851
+ * that needs a disambiguation rule, not a fallback.
852
+ */
853
+ function schemaNameFor(collection) {
854
+ return toPascalCase(collection.singularName || "") || toPascalCase(collection.name || "") || toPascalCase(collection.slug || "") || "Collection";
855
+ }
856
+ /**
586
857
  * Convert a string to PascalCase for schema names.
587
858
  */
588
859
  function toPascalCase(str) {
@@ -591,4 +862,4 @@ function toPascalCase(str) {
591
862
  //#endregion
592
863
  export { generateOpenApiSpec };
593
864
 
594
- //# sourceMappingURL=openapi-generator-D5xsfVJY.js.map
865
+ //# sourceMappingURL=openapi-generator-DPKtUC9X.js.map