@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.
- package/dist/{admin_block-EmIHae6X.js → admin_block-H86dCPsj.js} +61 -2
- package/dist/admin_block-H86dCPsj.js.map +1 -0
- package/dist/api/ast-schema-editor.d.ts +74 -6
- package/dist/api/errors.d.ts +12 -0
- package/dist/api/rest/api-generator.d.ts +26 -1
- package/dist/api/rest/idempotency.d.ts +25 -4
- package/dist/api/rest/query-parser.d.ts +15 -1
- package/dist/api/rest/write-validation.d.ts +40 -4
- package/dist/auth/adapter-middleware.d.ts +3 -1
- package/dist/auth/admin-roles.d.ts +24 -0
- package/dist/auth/apple-oauth.d.ts +16 -11
- package/dist/auth/bitbucket-oauth.d.ts +2 -4
- package/dist/auth/builtin-auth-adapter.d.ts +11 -0
- package/dist/auth/discord-oauth.d.ts +5 -6
- package/dist/auth/facebook-oauth.d.ts +2 -4
- package/dist/auth/github-oauth.d.ts +2 -4
- package/dist/auth/gitlab-oauth.d.ts +6 -4
- package/dist/auth/google-oauth.d.ts +1 -0
- package/dist/auth/index.d.ts +7 -0
- package/dist/auth/interfaces.d.ts +81 -1
- package/dist/auth/jwt.d.ts +40 -0
- package/dist/auth/linkedin-oauth.d.ts +2 -4
- package/dist/auth/mfa-crypto.d.ts +54 -3
- package/dist/auth/mfa-gate.d.ts +42 -0
- package/dist/auth/mfa-routes.d.ts +26 -1
- package/dist/auth/mfa.d.ts +16 -0
- package/dist/auth/microsoft-oauth.d.ts +19 -4
- package/dist/auth/middleware.d.ts +26 -0
- package/dist/auth/oauth-code-flow.d.ts +66 -0
- package/dist/auth/oauth-signin-policy.d.ts +61 -0
- package/dist/auth/oidc-id-token.d.ts +61 -0
- package/dist/auth/rate-limiter.d.ts +62 -5
- package/dist/auth/registration-policy.d.ts +31 -0
- package/dist/auth/rls-scope.d.ts +25 -0
- package/dist/auth/routes.d.ts +15 -0
- package/dist/auth/slack-oauth.d.ts +2 -4
- package/dist/auth/spotify-oauth.d.ts +2 -4
- package/dist/auth/token-revocation.d.ts +38 -0
- package/dist/auth/twitter-oauth.d.ts +2 -5
- package/dist/{auth-DU4ShERo.js → auth-CYoPVf-E.js} +1985 -395
- package/dist/auth-CYoPVf-E.js.map +1 -0
- package/dist/{backup-BJiY_mM-.js → backup-C6ljYVTp.js} +2 -2
- package/dist/{backup-BJiY_mM-.js.map → backup-C6ljYVTp.js.map} +1 -1
- package/dist/boot/ddl-bootstrap.d.ts +70 -0
- package/dist/{contract-routes-Dj8i5AiM.js → contract-routes-Bet-eCNJ.js} +3 -3
- package/dist/{contract-routes-Dj8i5AiM.js.map → contract-routes-Bet-eCNJ.js.map} +1 -1
- package/dist/cron/cron-loader.d.ts +26 -1
- package/dist/cron/cron-routes.d.ts +1 -1
- package/dist/cron/cron-scheduler.d.ts +13 -3
- package/dist/cron/define-cron.d.ts +17 -3
- package/dist/cron/index.d.ts +2 -2
- package/dist/{cron-loader-B1S2MCSl.js → cron-loader-YhhQeVBM.js} +43 -8
- package/dist/cron-loader-YhhQeVBM.js.map +1 -0
- package/dist/{cron-routes-D5a9v1HY.js → cron-routes-maM_RlUu.js} +10 -4
- package/dist/cron-routes-maM_RlUu.js.map +1 -0
- package/dist/{cron-scheduler-BhFZWR0T.js → cron-scheduler-DIpYBmZP.js} +20 -8
- package/dist/cron-scheduler-DIpYBmZP.js.map +1 -0
- package/dist/{cron-store-Cu4sOseb.js → cron-store-Dvr4Y1sZ.js} +60 -51
- package/dist/cron-store-Dvr4Y1sZ.js.map +1 -0
- package/dist/ddl-bootstrap-BhXbTnBl.js +183 -0
- package/dist/ddl-bootstrap-BhXbTnBl.js.map +1 -0
- package/dist/email/html.d.ts +54 -0
- package/dist/email/index.d.ts +3 -0
- package/dist/email/link-base.d.ts +39 -0
- package/dist/email/smtp-email-service.d.ts +7 -1
- package/dist/email/templates.d.ts +9 -1
- package/dist/email/types.d.ts +11 -1
- package/dist/env.d.ts +5 -0
- package/dist/{errors-BpudWAVU.js → errors-EBYiaJ2E.js} +14 -2
- package/dist/errors-EBYiaJ2E.js.map +1 -0
- package/dist/function-loader-DDS1v7YX.js +139 -0
- package/dist/function-loader-DDS1v7YX.js.map +1 -0
- package/dist/{function-routes-C0cLIy3N.js → function-routes-Btcez1T-.js} +18 -6
- package/dist/function-routes-Btcez1T-.js.map +1 -0
- package/dist/functions/define-function.d.ts +25 -6
- package/dist/functions/function-loader.d.ts +23 -0
- package/dist/functions/function-routes.d.ts +7 -1
- package/dist/functions/request-timeout.d.ts +34 -0
- package/dist/history/history-routes.d.ts +6 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.es.js +1893 -714
- package/dist/index.es.js.map +1 -1
- package/dist/init/docs.d.ts +10 -1
- package/dist/init/process-safety.d.ts +25 -0
- package/dist/init.d.ts +38 -0
- package/dist/{jwt-CzeviDcB.js → jwt-_IFqfTOg.js} +57 -9
- package/dist/jwt-_IFqfTOg.js.map +1 -0
- package/dist/logger-DfvF_8r-.js +190 -0
- package/dist/logger-DfvF_8r-.js.map +1 -0
- package/dist/{openapi-generator-D5xsfVJY.js → openapi-generator-DPKtUC9X.js} +326 -55
- package/dist/openapi-generator-DPKtUC9X.js.map +1 -0
- package/dist/request-timeout-RivJsME0.js +65 -0
- package/dist/request-timeout-RivJsME0.js.map +1 -0
- package/dist/schema-editor-routes-CRcS3ArS.js +437 -0
- package/dist/schema-editor-routes-CRcS3ArS.js.map +1 -0
- package/dist/serve-spa.d.ts +5 -4
- package/dist/services/outbound-url-guard.d.ts +53 -0
- package/dist/services/routed-realtime-service.d.ts +10 -2
- package/dist/services/webhook-service.d.ts +76 -1
- package/dist/singleton.d.ts +25 -8
- package/dist/{src-CKOZBpeJ.js → src-C7rkDGxA.js} +619 -70
- package/dist/src-C7rkDGxA.js.map +1 -0
- package/dist/{src-_qQ3RNCK.js → src-Cz9nMgUR.js} +93 -3
- package/dist/src-Cz9nMgUR.js.map +1 -0
- package/dist/storage/LocalStorageController.d.ts +12 -1
- package/dist/storage/image-transform.d.ts +57 -0
- package/dist/storage/keys.d.ts +98 -0
- package/dist/storage/routes.d.ts +11 -0
- package/dist/storage/tus-handler.d.ts +22 -2
- package/dist/utils/logger.d.ts +18 -0
- package/dist/utils/sql.d.ts +11 -6
- package/package.json +6 -5
- package/dist/admin_block-EmIHae6X.js.map +0 -1
- package/dist/auth-DU4ShERo.js.map +0 -1
- package/dist/backend-CIxN4FVm.js +0 -15
- package/dist/backend-CIxN4FVm.js.map +0 -1
- package/dist/cron-loader-B1S2MCSl.js.map +0 -1
- package/dist/cron-routes-D5a9v1HY.js.map +0 -1
- package/dist/cron-scheduler-BhFZWR0T.js.map +0 -1
- package/dist/cron-store-Cu4sOseb.js.map +0 -1
- package/dist/errors-BpudWAVU.js.map +0 -1
- package/dist/function-loader-B_1fYfUY.js +0 -86
- package/dist/function-loader-B_1fYfUY.js.map +0 -1
- package/dist/function-routes-C0cLIy3N.js.map +0 -1
- package/dist/jwt-CzeviDcB.js.map +0 -1
- package/dist/logger-BYU66ENZ.js +0 -94
- package/dist/logger-BYU66ENZ.js.map +0 -1
- package/dist/openapi-generator-D5xsfVJY.js.map +0 -1
- package/dist/schema-editor-routes-C1DxDnDR.js +0 -248
- package/dist/schema-editor-routes-C1DxDnDR.js.map +0 -1
- package/dist/src-CKOZBpeJ.js.map +0 -1
- 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 {
|
|
5
|
-
import "./src-
|
|
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:
|
|
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: "
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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 =
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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 =
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
865
|
+
//# sourceMappingURL=openapi-generator-DPKtUC9X.js.map
|