@rebasepro/server 0.13.0 → 0.13.1-canary.g06dbe5b

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 (125) hide show
  1. package/dist/{admin_block-CcSGdH7M.js → admin_block-H86dCPsj.js} +62 -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 +0 -6
  5. package/dist/api/openapi-generator.d.ts +14 -0
  6. package/dist/api/rest/api-generator.d.ts +1 -1
  7. package/dist/api/rest/idempotency.d.ts +62 -4
  8. package/dist/api/rest/query-parser.d.ts +15 -1
  9. package/dist/api/rest/write-validation.d.ts +52 -9
  10. package/dist/auth/adapter-middleware.d.ts +3 -1
  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 +2 -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 +8 -1
  20. package/dist/auth/interfaces.d.ts +68 -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-gate.d.ts +42 -0
  24. package/dist/auth/mfa-routes.d.ts +26 -1
  25. package/dist/auth/mfa.d.ts +16 -0
  26. package/dist/auth/microsoft-oauth.d.ts +19 -4
  27. package/dist/auth/oauth-code-flow.d.ts +66 -0
  28. package/dist/auth/oauth-signin-policy.d.ts +61 -0
  29. package/dist/auth/oidc-id-token.d.ts +61 -0
  30. package/dist/auth/rate-limiter.d.ts +37 -2
  31. package/dist/auth/rls-scope.d.ts +25 -0
  32. package/dist/auth/routes.d.ts +8 -0
  33. package/dist/auth/slack-oauth.d.ts +2 -4
  34. package/dist/auth/spotify-oauth.d.ts +2 -4
  35. package/dist/auth/twitter-oauth.d.ts +2 -5
  36. package/dist/{auth-CuC9M2x6.js → auth-DkAHqvf5.js} +1604 -391
  37. package/dist/auth-DkAHqvf5.js.map +1 -0
  38. package/dist/{backup-CVggVhR2.js → backup-DLluVyuA.js} +2 -2
  39. package/dist/{backup-CVggVhR2.js.map → backup-DLluVyuA.js.map} +1 -1
  40. package/dist/boot/boot.d.ts +19 -0
  41. package/dist/boot/ddl-bootstrap.d.ts +70 -0
  42. package/dist/{contract-routes-Dj8i5AiM.js → contract-routes-BB1U05sS.js} +3 -3
  43. package/dist/{contract-routes-Dj8i5AiM.js.map → contract-routes-BB1U05sS.js.map} +1 -1
  44. package/dist/cron/cron-scheduler.d.ts +8 -3
  45. package/dist/cron/define-cron.d.ts +17 -3
  46. package/dist/{cron-loader-B1S2MCSl.js → cron-loader-3U5aGILy.js} +2 -2
  47. package/dist/{cron-loader-B1S2MCSl.js.map → cron-loader-3U5aGILy.js.map} +1 -1
  48. package/dist/{cron-routes-CrQ0tK-_.js → cron-routes-rZgwlOz4.js} +2 -2
  49. package/dist/{cron-routes-CrQ0tK-_.js.map → cron-routes-rZgwlOz4.js.map} +1 -1
  50. package/dist/{cron-scheduler-B3RFt0HS.js → cron-scheduler-FJAaCAXm.js} +30 -5
  51. package/dist/cron-scheduler-FJAaCAXm.js.map +1 -0
  52. package/dist/{cron-store-BywZsyfZ.js → cron-store-9NmUDfzL.js} +66 -47
  53. package/dist/cron-store-9NmUDfzL.js.map +1 -0
  54. package/dist/ddl-bootstrap-BhXbTnBl.js +183 -0
  55. package/dist/ddl-bootstrap-BhXbTnBl.js.map +1 -0
  56. package/dist/email/html.d.ts +54 -0
  57. package/dist/email/index.d.ts +3 -0
  58. package/dist/email/link-base.d.ts +39 -0
  59. package/dist/email/smtp-email-service.d.ts +7 -1
  60. package/dist/email/templates.d.ts +9 -1
  61. package/dist/email/types.d.ts +11 -1
  62. package/dist/{errors-CgkCzoj7.js → errors-B1WZEdsK.js} +3 -11
  63. package/dist/errors-B1WZEdsK.js.map +1 -0
  64. package/dist/function-loader-D1SwtCa5.js +139 -0
  65. package/dist/function-loader-D1SwtCa5.js.map +1 -0
  66. package/dist/{function-routes-C0cLIy3N.js → function-routes-Btcez1T-.js} +18 -6
  67. package/dist/function-routes-Btcez1T-.js.map +1 -0
  68. package/dist/functions/define-function.d.ts +25 -6
  69. package/dist/functions/function-loader.d.ts +23 -0
  70. package/dist/functions/function-routes.d.ts +7 -1
  71. package/dist/functions/request-timeout.d.ts +34 -0
  72. package/dist/history/history-routes.d.ts +6 -0
  73. package/dist/index.d.ts +2 -1
  74. package/dist/index.es.js +1960 -794
  75. package/dist/index.es.js.map +1 -1
  76. package/dist/init/docs.d.ts +10 -1
  77. package/dist/init/process-safety.d.ts +25 -0
  78. package/dist/init.d.ts +27 -0
  79. package/dist/{jwt-DD6EtpGj.js → jwt-Cuq6MwNy.js} +70 -8
  80. package/dist/jwt-Cuq6MwNy.js.map +1 -0
  81. package/dist/logger-DfvF_8r-.js +190 -0
  82. package/dist/logger-DfvF_8r-.js.map +1 -0
  83. package/dist/openapi-generator-BQxxxDpb.js +865 -0
  84. package/dist/openapi-generator-BQxxxDpb.js.map +1 -0
  85. package/dist/request-timeout-RivJsME0.js +65 -0
  86. package/dist/request-timeout-RivJsME0.js.map +1 -0
  87. package/dist/schema-editor-routes-DZFKGXgq.js +437 -0
  88. package/dist/schema-editor-routes-DZFKGXgq.js.map +1 -0
  89. package/dist/serve-spa.d.ts +5 -4
  90. package/dist/services/outbound-url-guard.d.ts +53 -0
  91. package/dist/services/routed-realtime-service.d.ts +10 -2
  92. package/dist/services/webhook-service.d.ts +76 -1
  93. package/dist/singleton.d.ts +25 -8
  94. package/dist/{src-Cum9kox5.js → src-By48Ffg0.js} +322 -70
  95. package/dist/src-By48Ffg0.js.map +1 -0
  96. package/dist/{src-_qQ3RNCK.js → src-Ca6NhxKs.js} +79 -2
  97. package/dist/src-Ca6NhxKs.js.map +1 -0
  98. package/dist/storage/LocalStorageController.d.ts +12 -1
  99. package/dist/storage/image-transform.d.ts +57 -0
  100. package/dist/storage/keys.d.ts +98 -0
  101. package/dist/storage/routes.d.ts +11 -0
  102. package/dist/storage/tus-handler.d.ts +7 -1
  103. package/dist/utils/logger.d.ts +18 -0
  104. package/dist/utils/logging.d.ts +0 -4
  105. package/dist/utils/sql.d.ts +11 -6
  106. package/package.json +7 -6
  107. package/dist/admin_block-CcSGdH7M.js.map +0 -1
  108. package/dist/auth-CuC9M2x6.js.map +0 -1
  109. package/dist/backend-CIxN4FVm.js +0 -15
  110. package/dist/backend-CIxN4FVm.js.map +0 -1
  111. package/dist/cron-scheduler-B3RFt0HS.js.map +0 -1
  112. package/dist/cron-store-BywZsyfZ.js.map +0 -1
  113. package/dist/errors-CgkCzoj7.js.map +0 -1
  114. package/dist/function-loader-B_1fYfUY.js +0 -86
  115. package/dist/function-loader-B_1fYfUY.js.map +0 -1
  116. package/dist/function-routes-C0cLIy3N.js.map +0 -1
  117. package/dist/jwt-DD6EtpGj.js.map +0 -1
  118. package/dist/logger-BYU66ENZ.js +0 -94
  119. package/dist/logger-BYU66ENZ.js.map +0 -1
  120. package/dist/openapi-generator-BEwyiaAr.js +0 -597
  121. package/dist/openapi-generator-BEwyiaAr.js.map +0 -1
  122. package/dist/schema-editor-routes-CbF20Mf1.js +0 -248
  123. package/dist/schema-editor-routes-CbF20Mf1.js.map +0 -1
  124. package/dist/src-Cum9kox5.js.map +0 -1
  125. package/dist/src-_qQ3RNCK.js.map +0 -1
@@ -0,0 +1,865 @@
1
+ import { createRequire as __createRequire } from "module";
2
+ import "process";
3
+ __createRequire(import.meta.url);
4
+ import { h as resolveCollectionRelations } from "./src-By48Ffg0.js";
5
+ import "./src-Ca6NhxKs.js";
6
+ //#region ../types/src/types/relations.ts
7
+ /** @group Models */
8
+ function isToMany(relation) {
9
+ return relation.cardinality === "many";
10
+ }
11
+ //#endregion
12
+ //#region src/api/openapi-generator.ts
13
+ function generateOpenApiSpec(collections, options = {}) {
14
+ const basePath = options.basePath ?? "/api";
15
+ const requireAuth = options.requireAuth ?? true;
16
+ const defaultLimit = options.listLimits?.defaultLimit ?? 50;
17
+ const maxLimit = options.listLimits?.maxLimit ?? 1e3;
18
+ /**
19
+ * The query parameters every list endpoint honours.
20
+ *
21
+ * Written once because it was written twice: the root listing named eight
22
+ * and the subcollection listing named four, though both go through the same
23
+ * `parseQueryOptions` and the same fetch. Four capabilities were therefore
24
+ * unreachable from a generated client on nested routes, and `or`/`and` were
25
+ * undocumented on both.
26
+ */
27
+ const listQueryParameters = () => [
28
+ {
29
+ name: "limit",
30
+ in: "query",
31
+ schema: {
32
+ type: "integer",
33
+ default: defaultLimit,
34
+ minimum: 1,
35
+ maximum: maxLimit
36
+ },
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\`.`
38
+ },
39
+ {
40
+ name: "offset",
41
+ in: "query",
42
+ schema: {
43
+ type: "integer",
44
+ default: 0
45
+ },
46
+ description: "Number of records to skip"
47
+ },
48
+ {
49
+ name: "page",
50
+ in: "query",
51
+ schema: {
52
+ type: "integer",
53
+ minimum: 1
54
+ },
55
+ description: "Page number (alternative to offset). Calculates offset as (page-1)*limit"
56
+ },
57
+ {
58
+ name: "orderBy",
59
+ in: "query",
60
+ schema: { type: "string" },
61
+ description: "Sort field and direction. Accepts `field:asc` or `field:desc`, or a JSON array `[{\"field\":\"name\",\"direction\":\"asc\"}]`",
62
+ example: "created_at:desc"
63
+ },
64
+ {
65
+ name: "where",
66
+ in: "query",
67
+ schema: { type: "string" },
68
+ description: "JSON object filter, mapping each field to a `[operator, value]` tuple. Combines with the per-field `?field=op.value` parameters below; on the same field, the per-field parameter wins.",
69
+ example: "{\"status\":[\"==\",\"active\"]}"
70
+ },
71
+ {
72
+ name: "or",
73
+ in: "query",
74
+ schema: { type: "string" },
75
+ description: "Disjunction of conditions, AND-ed with `where` and `searchString`.",
76
+ example: "(status.eq.draft,status.eq.review)"
77
+ },
78
+ {
79
+ name: "and",
80
+ in: "query",
81
+ schema: { type: "string" },
82
+ description: "Conjunction of conditions, AND-ed with `where` and `searchString`. Ignored when `or` is also present.",
83
+ example: "(views.gte.10,status.eq.draft)"
84
+ },
85
+ {
86
+ name: "include",
87
+ in: "query",
88
+ schema: { type: "string" },
89
+ description: "Comma-separated list of relations to include (eager-load). Use `*` for all relations.",
90
+ example: "author,tags"
91
+ },
92
+ {
93
+ name: "fields",
94
+ in: "query",
95
+ schema: { type: "string" },
96
+ description: "Comma-separated list of fields to return (field selection)",
97
+ example: "id,name,created_at"
98
+ },
99
+ {
100
+ name: "searchString",
101
+ in: "query",
102
+ schema: { type: "string" },
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."
138
+ }
139
+ ];
140
+ const spec = {
141
+ openapi: "3.0.3",
142
+ info: {
143
+ title: "Rebase API",
144
+ version: "1.0.0",
145
+ description: "Auto-generated REST API from Rebase collection definitions. This is the same API consumed by the `@rebasepro/client` SDK."
146
+ },
147
+ servers: [{
148
+ url: basePath,
149
+ description: "API Server"
150
+ }],
151
+ paths: {},
152
+ components: {
153
+ schemas: {
154
+ ErrorResponse: {
155
+ type: "object",
156
+ properties: { error: {
157
+ type: "object",
158
+ required: ["message", "code"],
159
+ properties: {
160
+ message: { type: "string" },
161
+ code: { type: "string" },
162
+ details: {}
163
+ }
164
+ } }
165
+ },
166
+ PaginationMeta: {
167
+ type: "object",
168
+ properties: {
169
+ total: {
170
+ type: "integer",
171
+ description: "Total number of matching records"
172
+ },
173
+ limit: {
174
+ type: "integer",
175
+ description: "Page size used for this query"
176
+ },
177
+ offset: {
178
+ type: "integer",
179
+ description: "Number of records skipped"
180
+ },
181
+ hasMore: {
182
+ type: "boolean",
183
+ description: "Whether more records exist beyond this page"
184
+ }
185
+ }
186
+ }
187
+ },
188
+ securitySchemes: {}
189
+ },
190
+ tags: []
191
+ };
192
+ if (requireAuth) {
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: [] }];
200
+ }
201
+ const paths = spec.paths;
202
+ const schemas = spec.components.schemas;
203
+ const tags = spec.tags;
204
+ const reservedParameterNames = new Set(listQueryParameters().map((p) => p.name));
205
+ for (const collection of collections || []) {
206
+ const schemaName = schemaNameFor(collection);
207
+ const slug = collection.slug;
208
+ tags.push({
209
+ name: collection.name,
210
+ description: collection.description || `CRUD operations for ${collection.name}`
211
+ });
212
+ schemas[schemaName] = buildCollectionSchema(collection);
213
+ schemas[`${schemaName}Input`] = buildCollectionInputSchema(collection);
214
+ schemas[`${schemaName}Update`] = buildCollectionUpdateSchema(collection);
215
+ const dataPath = `/data/${slug}`;
216
+ paths[dataPath] = {
217
+ get: {
218
+ tags: [collection.name],
219
+ summary: `List ${collection.name}`,
220
+ operationId: `list${schemaName}`,
221
+ parameters: [...listQueryParameters(), ...buildFilterParameters(collection, reservedParameterNames)],
222
+ responses: {
223
+ 200: {
224
+ description: "Paginated list of entities",
225
+ content: { "application/json": { schema: {
226
+ type: "object",
227
+ properties: {
228
+ data: {
229
+ type: "array",
230
+ items: { $ref: `#/components/schemas/${schemaName}` }
231
+ },
232
+ meta: { $ref: "#/components/schemas/PaginationMeta" }
233
+ }
234
+ } } }
235
+ },
236
+ ...errorResponses(requireAuth)
237
+ }
238
+ },
239
+ post: {
240
+ tags: [collection.name],
241
+ summary: `Create ${collection.singularName || collection.name}`,
242
+ operationId: `create${schemaName}`,
243
+ requestBody: {
244
+ required: true,
245
+ content: { "application/json": { schema: { $ref: `#/components/schemas/${schemaName}Input` } } }
246
+ },
247
+ responses: {
248
+ 201: {
249
+ description: "Created entity",
250
+ content: { "application/json": { schema: { $ref: `#/components/schemas/${schemaName}` } } }
251
+ },
252
+ ...errorResponses(requireAuth)
253
+ }
254
+ }
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
+ } };
409
+ const entityPath = `/data/${slug}/{id}`;
410
+ paths[entityPath] = {
411
+ get: {
412
+ tags: [collection.name],
413
+ summary: `Get ${collection.singularName || collection.name} by ID`,
414
+ operationId: `get${schemaName}ById`,
415
+ parameters: [{
416
+ name: "id",
417
+ in: "path",
418
+ required: true,
419
+ schema: { type: "string" },
420
+ description: "Entity ID"
421
+ }, {
422
+ name: "include",
423
+ in: "query",
424
+ schema: { type: "string" },
425
+ description: "Comma-separated list of relations to include",
426
+ example: "author,tags"
427
+ }],
428
+ responses: {
429
+ 200: {
430
+ description: "Entity found",
431
+ content: { "application/json": { schema: { $ref: `#/components/schemas/${schemaName}` } } }
432
+ },
433
+ 404: {
434
+ description: "Entity not found",
435
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
436
+ },
437
+ ...errorResponses(requireAuth)
438
+ }
439
+ },
440
+ patch: updateOperation(collection, schemaName, requireAuth),
441
+ put: {
442
+ ...updateOperation(collection, schemaName, requireAuth),
443
+ operationId: `update${schemaName}ViaPut`,
444
+ deprecated: true
445
+ },
446
+ delete: {
447
+ tags: [collection.name],
448
+ summary: `Delete ${collection.singularName || collection.name}`,
449
+ operationId: `delete${schemaName}`,
450
+ parameters: [{
451
+ name: "id",
452
+ in: "path",
453
+ required: true,
454
+ schema: { type: "string" },
455
+ description: "Entity ID"
456
+ }],
457
+ responses: {
458
+ 204: { description: "Deleted successfully" },
459
+ 404: {
460
+ description: "Entity not found",
461
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
462
+ },
463
+ ...errorResponses(requireAuth)
464
+ }
465
+ }
466
+ };
467
+ }
468
+ for (const collection of collections || []) {
469
+ const slug = collection.slug;
470
+ const schemaName = schemaNameFor(collection);
471
+ const relations = Object.values(resolveCollectionRelations(collection)).filter(isToMany);
472
+ for (const relation of relations) {
473
+ const relationName = relation.relationName;
474
+ const targetSchema = schemaNameFor(relation.target());
475
+ const subPath = `/data/${slug}/{parentId}/${relationName}`;
476
+ paths[subPath] = { get: {
477
+ tags: [collection.name],
478
+ summary: `List ${relationName} for ${withIndefiniteArticle(collection.singularName || collection.name)}`,
479
+ operationId: `list${schemaName}${toPascalCase(relationName)}`,
480
+ parameters: [{
481
+ name: "parentId",
482
+ in: "path",
483
+ required: true,
484
+ schema: { type: "string" },
485
+ description: `${collection.singularName || collection.name} ID`
486
+ }, ...listQueryParameters()],
487
+ responses: {
488
+ 200: {
489
+ description: `List of related ${relationName}`,
490
+ content: { "application/json": { schema: {
491
+ type: "object",
492
+ properties: {
493
+ data: {
494
+ type: "array",
495
+ items: schemas[targetSchema] ? { $ref: `#/components/schemas/${targetSchema}` } : { type: "object" }
496
+ },
497
+ meta: { $ref: "#/components/schemas/PaginationMeta" }
498
+ }
499
+ } } }
500
+ },
501
+ ...errorResponses(requireAuth)
502
+ }
503
+ } };
504
+ }
505
+ }
506
+ return spec;
507
+ }
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
+ /**
535
+ * Build the component schema for a collection (output / read shape).
536
+ *
537
+ * Every declared property except the ones {@link isDocumentedProperty} rules
538
+ * out.
539
+ */
540
+ function buildCollectionSchema(collection) {
541
+ const properties = { id: {
542
+ type: "string",
543
+ description: "Unique identifier"
544
+ } };
545
+ const required = ["id"];
546
+ for (const [key, property] of Object.entries(collection.properties)) {
547
+ if (!isDocumentedProperty(property)) continue;
548
+ properties[key] = convertPropertyToSchema(property);
549
+ if (property.validation?.required) required.push(key);
550
+ }
551
+ return {
552
+ type: "object",
553
+ required: required.length > 0 ? required : void 0,
554
+ properties
555
+ };
556
+ }
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
+ /**
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.
617
+ */
618
+ function buildCollectionInputSchema(collection) {
619
+ const properties = {};
620
+ const required = [];
621
+ for (const [key, property] of Object.entries(collection.properties)) {
622
+ if (!isDocumentedProperty(property)) continue;
623
+ if (property.type === "date" && property.autoValue) continue;
624
+ if ("isId" in property && property.isId && property.isId !== "manual" && property.isId !== true) continue;
625
+ properties[key] = convertPropertyToSchema(property);
626
+ if (property.validation?.required) required.push(key);
627
+ }
628
+ properties["id"] = {
629
+ type: "string",
630
+ description: "Optional: client-assigned ID. If omitted, the server generates one."
631
+ };
632
+ return {
633
+ type: "object",
634
+ required: required.length > 0 ? required : void 0,
635
+ properties
636
+ };
637
+ }
638
+ /**
639
+ * Convert a Rebase Property to an OpenAPI 3.0 schema object.
640
+ */
641
+ function convertPropertyToSchema(property) {
642
+ const base = {};
643
+ if (property.name) base.description = property.name;
644
+ switch (property.type) {
645
+ case "string": {
646
+ const sp = property;
647
+ base.type = "string";
648
+ if (sp.enum) {
649
+ const enumValues = resolveEnumValues(sp.enum);
650
+ if (enumValues.length > 0) base.enum = enumValues;
651
+ }
652
+ if (sp.validation) {
653
+ if (sp.validation.min !== void 0) base.minLength = sp.validation.min;
654
+ if (sp.validation.max !== void 0) base.maxLength = sp.validation.max;
655
+ if (sp.validation.length !== void 0) {
656
+ base.minLength = sp.validation.length;
657
+ base.maxLength = sp.validation.length;
658
+ }
659
+ if (sp.validation.matches !== void 0) base.pattern = String(sp.validation.matches);
660
+ }
661
+ if (sp.email) base.format = "email";
662
+ if (sp.url) base.format = "uri";
663
+ if (sp.storage) base.format = "uri";
664
+ return base;
665
+ }
666
+ case "number": {
667
+ const np = property;
668
+ base.type = np.validation?.integer || np.columnType === "integer" || np.columnType === "serial" || np.columnType === "bigserial" || np.columnType === "bigint" ? "integer" : "number";
669
+ if (np.enum) {
670
+ const enumValues = resolveEnumValues(np.enum);
671
+ if (enumValues.length > 0) base.enum = enumValues;
672
+ }
673
+ if (np.validation) {
674
+ if (np.validation.min !== void 0) base.minimum = np.validation.min;
675
+ if (np.validation.max !== void 0) base.maximum = np.validation.max;
676
+ if (np.validation.moreThan !== void 0) {
677
+ base.minimum = np.validation.moreThan;
678
+ base.exclusiveMinimum = true;
679
+ }
680
+ if (np.validation.lessThan !== void 0) {
681
+ base.maximum = np.validation.lessThan;
682
+ base.exclusiveMaximum = true;
683
+ }
684
+ }
685
+ return base;
686
+ }
687
+ case "boolean":
688
+ base.type = "boolean";
689
+ return base;
690
+ case "date":
691
+ base.type = "string";
692
+ if (property.mode === "date") base.format = "date";
693
+ else base.format = "date-time";
694
+ if (property.autoValue) {
695
+ base.readOnly = true;
696
+ base.description = (base.description || "") + (property.autoValue === "on_create" ? " (Auto-set on creation)" : " (Auto-updated)");
697
+ }
698
+ return base;
699
+ case "geopoint":
700
+ base.type = "object";
701
+ base.properties = {
702
+ latitude: { type: "number" },
703
+ longitude: { type: "number" }
704
+ };
705
+ base.required = ["latitude", "longitude"];
706
+ return base;
707
+ case "reference":
708
+ base.type = "string";
709
+ base.description = (base.description || "") + " (Reference ID)";
710
+ return base;
711
+ case "array": {
712
+ const ap = property;
713
+ base.type = "array";
714
+ if (ap.oneOf) {
715
+ const typeField = ap.oneOf.typeField || "type";
716
+ const valueField = ap.oneOf.valueField || "value";
717
+ const variants = [];
718
+ for (const [variantKey, variantProp] of Object.entries(ap.oneOf.properties)) variants.push({
719
+ type: "object",
720
+ properties: {
721
+ [typeField]: {
722
+ type: "string",
723
+ enum: [variantKey]
724
+ },
725
+ [valueField]: convertPropertyToSchema(variantProp)
726
+ },
727
+ required: [typeField, valueField]
728
+ });
729
+ base.items = { oneOf: variants };
730
+ } else if (ap.of) if (Array.isArray(ap.of)) base.items = { oneOf: ap.of.map((p) => convertPropertyToSchema(p)) };
731
+ else base.items = convertPropertyToSchema(ap.of);
732
+ else base.items = {};
733
+ if (ap.validation) {
734
+ if (ap.validation.min !== void 0) base.minItems = ap.validation.min;
735
+ if (ap.validation.max !== void 0) base.maxItems = ap.validation.max;
736
+ }
737
+ return base;
738
+ }
739
+ case "map": {
740
+ const mp = property;
741
+ base.type = "object";
742
+ if (mp.properties) {
743
+ const props = {};
744
+ const req = [];
745
+ for (const [key, subProp] of Object.entries(mp.properties)) {
746
+ props[key] = convertPropertyToSchema(subProp);
747
+ if (subProp.validation?.required) req.push(key);
748
+ }
749
+ base.properties = props;
750
+ if (req.length > 0) base.required = req;
751
+ } else if (mp.keyValue) base.additionalProperties = true;
752
+ return base;
753
+ }
754
+ case "vector": {
755
+ const vp = property;
756
+ base.type = "array";
757
+ base.items = { type: "number" };
758
+ base.description = (base.description || "") + ` (Vector(${vp.dimensions}))`;
759
+ return base;
760
+ }
761
+ case "binary":
762
+ base.type = "string";
763
+ base.description = (base.description || "") + " (Binary/Base64)";
764
+ return base;
765
+ default:
766
+ base.type = "string";
767
+ return base;
768
+ }
769
+ }
770
+ /**
771
+ * Resolve EnumValues (array or record) into a flat array of enum values.
772
+ */
773
+ function resolveEnumValues(enumDef) {
774
+ if (Array.isArray(enumDef)) return enumDef.map((e) => typeof e === "object" && e !== null && "id" in e ? e.id : e);
775
+ return Object.keys(enumDef).map((k) => {
776
+ const num = Number(k);
777
+ return isNaN(num) ? k : num;
778
+ });
779
+ }
780
+ /**
781
+ * Build PostgREST-style filter parameters for a collection.
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.
788
+ */
789
+ function buildFilterParameters(collection, reservedNames = /* @__PURE__ */ new Set()) {
790
+ const params = [];
791
+ for (const [key, property] of Object.entries(collection.properties)) {
792
+ if (!isDocumentedProperty(property)) continue;
793
+ if (property.type === "map" || property.type === "array" || property.type === "geopoint") continue;
794
+ if (reservedNames.has(key)) continue;
795
+ params.push({
796
+ name: key,
797
+ in: "query",
798
+ required: false,
799
+ schema: { type: "string" },
800
+ description: `Filter by \`${key}\`. Supports PostgREST operators: \`eq.value\`, \`neq.value\`, \`gt.value\`, \`gte.value\`, \`lt.value\`, \`lte.value\`, \`in.(a,b,c)\`, \`nin.(a,b,c)\`, \`cs.value\` (array-contains), \`csa.(a,b)\` (array-contains-any). Plain values imply equality.`,
801
+ example: property.type === "string" ? "eq.active" : property.type === "number" ? "gte.100" : void 0
802
+ });
803
+ }
804
+ return params;
805
+ }
806
+ /**
807
+ * Standard error responses included on every endpoint.
808
+ */
809
+ function errorResponses(requireAuth) {
810
+ const responses = {
811
+ 400: {
812
+ description: "Bad request",
813
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
814
+ },
815
+ 500: {
816
+ description: "Internal server error",
817
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
818
+ }
819
+ };
820
+ if (requireAuth) {
821
+ responses[401] = {
822
+ description: "Authentication required or invalid token",
823
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
824
+ };
825
+ responses[403] = {
826
+ description: "Insufficient permissions",
827
+ content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } }
828
+ };
829
+ }
830
+ return responses;
831
+ }
832
+ /**
833
+ * Prefix a noun with "a" or "an" based on its leading sound.
834
+ */
835
+ function withIndefiniteArticle(noun) {
836
+ return `${/^[aeiou]/i.test(noun) ? "an" : "a"} ${noun}`;
837
+ }
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
+ /**
857
+ * Convert a string to PascalCase for schema names.
858
+ */
859
+ function toPascalCase(str) {
860
+ return str.replace(/[^a-zA-Z0-9]+/g, " ").split(" ").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
861
+ }
862
+ //#endregion
863
+ export { generateOpenApiSpec };
864
+
865
+ //# sourceMappingURL=openapi-generator-BQxxxDpb.js.map