@rebasepro/server 0.22.0 → 0.24.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/README.md +1 -1
- package/bin/rebase-server.js +4 -2
- package/dist/{GCSStorageController-CLIJXwGS.js → GCSStorageController-BSiP1c-f.js} +57 -29
- package/dist/GCSStorageController-BSiP1c-f.js.map +1 -0
- package/dist/{S3StorageController-Dcuf8lMA.js → S3StorageController-CAwFRgjV.js} +19 -7
- package/dist/S3StorageController-CAwFRgjV.js.map +1 -0
- package/dist/api/ast-schema-editor.d.ts +127 -1
- package/dist/api/errors.d.ts +9 -0
- package/dist/api/live-schema-routes.d.ts +52 -8
- package/dist/api/logs-routes.d.ts +39 -1
- package/dist/api/openapi-generator.d.ts +17 -0
- package/dist/api/rest/api-generator.d.ts +158 -42
- package/dist/api/rest/auth-collection-writes.d.ts +85 -0
- package/dist/api/rest/field-access-query.d.ts +6 -2
- package/dist/api/rest/idempotency.d.ts +7 -1
- package/dist/api/rest/nested-write-access.d.ts +46 -0
- package/dist/api/rest/write-validation.d.ts +34 -2
- package/dist/api/types.d.ts +17 -1
- package/dist/{ast-schema-editor-CslO8Oje.js → ast-schema-editor-CWqS_sLJ.js} +411 -13
- package/dist/ast-schema-editor-CWqS_sLJ.js.map +1 -0
- package/dist/auth/access.d.ts +105 -0
- package/dist/auth/adapter-middleware.d.ts +2 -1
- package/dist/auth/address-ownership.d.ts +68 -0
- package/dist/auth/admin-roles-route.d.ts +4 -2
- package/dist/auth/admin-roles.d.ts +17 -20
- package/dist/auth/admin-user-ops.d.ts +35 -2
- package/dist/auth/admin-users-route.d.ts +1 -0
- package/dist/auth/api-keys/api-key-middleware.d.ts +56 -55
- package/dist/auth/api-keys/api-key-routes.d.ts +41 -11
- package/dist/auth/api-keys/api-key-store.d.ts +31 -8
- package/dist/auth/api-keys/api-key-types.d.ts +14 -16
- package/dist/auth/api-keys/http-operation.d.ts +19 -0
- package/dist/auth/api-keys/index.d.ts +11 -11
- package/dist/auth/api-keys/key-grant.d.ts +41 -0
- package/dist/auth/api-keys/legacy-permissions.d.ts +33 -0
- package/dist/auth/auth-hooks.d.ts +50 -7
- package/dist/auth/builtin-auth-adapter.d.ts +8 -0
- package/dist/auth/captcha.d.ts +5 -0
- package/dist/auth/cookie-utils.d.ts +7 -0
- package/dist/auth/deliverable-address.d.ts +6 -0
- package/dist/auth/email-change-routes.d.ts +41 -0
- package/dist/auth/expired-token-sweep.d.ts +67 -0
- package/dist/auth/impersonation.d.ts +110 -0
- package/dist/auth/index.d.ts +4 -2
- package/dist/auth/interfaces.d.ts +146 -65
- package/dist/auth/jwt.d.ts +66 -3
- package/dist/auth/magic-link-routes.d.ts +2 -6
- package/dist/auth/mfa-routes.d.ts +2 -9
- package/dist/auth/middleware.d.ts +17 -5
- package/dist/auth/oauth-signin-policy.d.ts +25 -8
- package/dist/auth/otp-routes.d.ts +2 -6
- package/dist/auth/passwordless-signup.d.ts +27 -0
- package/dist/auth/platform-token.d.ts +122 -0
- package/dist/auth/rate-limiter.d.ts +72 -1
- package/dist/auth/routes.d.ts +45 -0
- package/dist/auth/scope-routes.d.ts +22 -0
- package/dist/auth/session-routes.d.ts +18 -6
- package/dist/auth/token-revocation.d.ts +53 -1
- package/dist/auth/verify-credential.d.ts +28 -0
- package/dist/{auth-CCDpk2rn.js → auth-DMLngxn_.js} +2712 -711
- package/dist/auth-DMLngxn_.js.map +1 -0
- package/dist/backend-DTAOsLQc.js +30 -0
- package/dist/backend-DTAOsLQc.js.map +1 -0
- package/dist/backup/backup-common.d.ts +29 -0
- package/dist/backup/backup-routes.d.ts +24 -4
- package/dist/backup/backup-schedule.d.ts +33 -0
- package/dist/backup/backup-storage.d.ts +14 -0
- package/dist/backup/index.d.ts +2 -0
- package/dist/backup-CN0s50D2.js +444 -0
- package/dist/backup-CN0s50D2.js.map +1 -0
- package/dist/boot/bundle.d.ts +19 -0
- package/dist/boot/driver.d.ts +10 -0
- package/dist/boot/env.d.ts +51 -6
- package/dist/boot/fetch-bundle.d.ts +18 -1
- package/dist/boot/rls-audit-option.d.ts +26 -0
- package/dist/boot/security-headers.d.ts +26 -0
- package/dist/boot/sources.d.ts +1 -0
- package/dist/boot/static-routing.d.ts +56 -0
- package/dist/collection_patch-BRu-BvDv.js +472 -0
- package/dist/collection_patch-BRu-BvDv.js.map +1 -0
- package/dist/{contract-routes-eLxV0le1.js → contract-routes-fz8i4pxs.js} +17 -4
- package/dist/contract-routes-fz8i4pxs.js.map +1 -0
- package/dist/cron/cron-routes.d.ts +7 -2
- package/dist/cron/cron-scheduler.d.ts +146 -21
- package/dist/cron/cron-store.d.ts +76 -8
- package/dist/cron/index.d.ts +1 -1
- package/dist/{cron-loader-CQjvjpEw.js → cron-loader-CwaANlOG.js} +4 -4
- package/dist/cron-loader-CwaANlOG.js.map +1 -0
- package/dist/cron-routes-Bc-SB0Se.js +96 -0
- package/dist/cron-routes-Bc-SB0Se.js.map +1 -0
- package/dist/{cron-scheduler-COPQxlEq.js → cron-scheduler-CYQgco86.js} +427 -83
- package/dist/cron-scheduler-CYQgco86.js.map +1 -0
- package/dist/{cron-store-BYGZFNWk.js → cron-store-D2Q9-Aco.js} +139 -23
- package/dist/cron-store-D2Q9-Aco.js.map +1 -0
- package/dist/{ddl-bootstrap-CfNvxMuK.js → ddl-bootstrap-BaqMSa4Y.js} +3 -26
- package/dist/ddl-bootstrap-BaqMSa4Y.js.map +1 -0
- package/dist/email/index.d.ts +2 -2
- package/dist/email/link-base.d.ts +5 -4
- package/dist/email/smtp-email-service.d.ts +13 -1
- package/dist/email/templates.d.ts +31 -0
- package/dist/email/types.d.ts +29 -2
- package/dist/env.d.ts +25 -7
- package/dist/{errors-DWsX4yTd.js → errors-D6_y86c5.js} +102 -8
- package/dist/errors-D6_y86c5.js.map +1 -0
- package/dist/{function-loader-xnbDAPfa.js → function-loader-D7o5Epjj.js} +2 -2
- package/dist/{function-loader-xnbDAPfa.js.map → function-loader-D7o5Epjj.js.map} +1 -1
- package/dist/{function-routes-Chet4-lB.js → function-routes-CaNG4waN.js} +24 -12
- package/dist/function-routes-CaNG4waN.js.map +1 -0
- package/dist/functions/context.d.ts +17 -6
- package/dist/functions/guards.d.ts +22 -5
- package/dist/functions/index.d.ts +2 -2
- package/dist/functions/index.js +90 -36
- package/dist/functions/index.js.map +1 -1
- package/dist/{history-recorder-BQmB0P_j.js → history-recorder-Nr8zLvoU.js} +9 -7
- package/dist/history-recorder-Nr8zLvoU.js.map +1 -0
- package/dist/{history-store-CetkrBBD.js → history-store-rcAm_xFR.js} +2 -2
- package/dist/{history-store-CetkrBBD.js.map → history-store-rcAm_xFR.js.map} +1 -1
- package/dist/index.d.ts +14 -4
- package/dist/index.es.js +5733 -1551
- package/dist/index.es.js.map +1 -1
- package/dist/init/docs.d.ts +5 -2
- package/dist/init/health.d.ts +17 -2
- package/dist/init/shutdown.d.ts +18 -3
- package/dist/init.d.ts +54 -0
- package/dist/jobs/index.d.ts +2 -2
- package/dist/jobs/job-queue.d.ts +23 -2
- package/dist/jobs/job-store.d.ts +37 -5
- package/dist/jobs/types.d.ts +8 -6
- package/dist/{jobs-Bjr8DZAi.js → jobs-DqYNfquG.js} +306 -167
- package/dist/jobs-DqYNfquG.js.map +1 -0
- package/dist/{jwt-C4OW-DNq.js → jwt-R6bSPMjk.js} +114 -38
- package/dist/{jwt-C4OW-DNq.js.map → jwt-R6bSPMjk.js.map} +1 -1
- package/dist/{keys-Qfc4XieN.js → keys-GAVZqbqx.js} +18 -17
- package/dist/{keys-Qfc4XieN.js.map → keys-GAVZqbqx.js.map} +1 -1
- package/dist/{logger-DO2PZc4i.js → logger-D-S-hO5e.js} +26 -3
- package/dist/logger-D-S-hO5e.js.map +1 -0
- package/dist/{logs-routes-3EEzPjhl.js → logs-routes-DAdv37GI.js} +54 -11
- package/dist/logs-routes-DAdv37GI.js.map +1 -0
- package/dist/mcp/consent-page.d.ts +1 -1
- package/dist/mcp/mcp-routes.d.ts +45 -2
- package/dist/mcp/mcp-tools.d.ts +22 -10
- package/dist/mcp/oauth-metadata.d.ts +21 -16
- package/dist/mcp/oauth-routes.d.ts +34 -1
- package/dist/mcp/oauth-store.d.ts +29 -13
- package/dist/metrics/history-recorder.d.ts +1 -1
- package/dist/{openapi-generator-D8uFz-LW.js → openapi-generator-DAq_XVDu.js} +135 -22
- package/dist/openapi-generator-DAq_XVDu.js.map +1 -0
- package/dist/{proxy-Czngl3p9.js → proxy-qRlqeUmO.js} +2 -2
- package/dist/{proxy-Czngl3p9.js.map → proxy-qRlqeUmO.js.map} +1 -1
- package/dist/{query-parser-BleZmY18.js → query-parser-BgiKJKvc.js} +41 -82
- package/dist/query-parser-BgiKJKvc.js.map +1 -0
- package/dist/{request-timeout-C_4C2BeR.js → request-timeout-DgH7j8qO.js} +3 -3
- package/dist/{request-timeout-C_4C2BeR.js.map → request-timeout-DgH7j8qO.js.map} +1 -1
- package/dist/rls-audit/index.d.ts +4 -0
- package/dist/schema-edit/apply-schema-change.d.ts +63 -3
- package/dist/schema-edit/project-root.d.ts +3 -2
- package/dist/schema-edit/remote-source.d.ts +9 -4
- package/dist/{schema-editor-routes-DdLihzp0.js → schema-editor-routes-oIyuWl3L.js} +12 -7
- package/dist/schema-editor-routes-oIyuWl3L.js.map +1 -0
- package/dist/serve-spa.d.ts +58 -0
- package/dist/services/routed-realtime-service.d.ts +11 -0
- package/dist/soft-delete-params-BWPilMPF.js +59 -0
- package/dist/soft-delete-params-BWPilMPF.js.map +1 -0
- package/dist/{src-Br6ARbs6.js → src-CatHFUym.js} +439 -45
- package/dist/src-CatHFUym.js.map +1 -0
- package/dist/{src-1vL-I1Po.js → src-I3aG1PcY.js} +371 -81
- package/dist/src-I3aG1PcY.js.map +1 -0
- package/dist/storage/GCSStorageController.d.ts +13 -1
- package/dist/storage/LocalStorageController.d.ts +2 -0
- package/dist/storage/S3StorageController.d.ts +2 -0
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/keys.d.ts +12 -0
- package/dist/storage/property-limits.d.ts +41 -6
- package/dist/storage/rendition-cache.d.ts +11 -1
- package/dist/storage/request-keys.d.ts +82 -0
- package/dist/storage/requested-object.d.ts +74 -0
- package/dist/storage/routes.d.ts +36 -18
- package/dist/storage/tus-handler.d.ts +30 -5
- package/dist/storage/types.d.ts +36 -1
- package/dist/types-BfKcm9do.js.map +1 -1
- package/dist/utils/logger.d.ts +12 -0
- package/package.json +9 -9
- package/dist/GCSStorageController-CLIJXwGS.js.map +0 -1
- package/dist/S3StorageController-Dcuf8lMA.js.map +0 -1
- package/dist/admin-roles-vYdp_Pil.js +0 -36
- package/dist/admin-roles-vYdp_Pil.js.map +0 -1
- package/dist/admin_block-DxKLmdiv.js +0 -206
- package/dist/admin_block-DxKLmdiv.js.map +0 -1
- package/dist/ast-schema-editor-CslO8Oje.js.map +0 -1
- package/dist/auth/api-keys/api-key-permission-guard.d.ts +0 -65
- package/dist/auth-CCDpk2rn.js.map +0 -1
- package/dist/backup-DzI9jLwc.js +0 -192
- package/dist/backup-DzI9jLwc.js.map +0 -1
- package/dist/contract-routes-eLxV0le1.js.map +0 -1
- package/dist/cron-loader-CQjvjpEw.js.map +0 -1
- package/dist/cron-routes-B7CRGfiq.js +0 -72
- package/dist/cron-routes-B7CRGfiq.js.map +0 -1
- package/dist/cron-scheduler-COPQxlEq.js.map +0 -1
- package/dist/cron-store-BYGZFNWk.js.map +0 -1
- package/dist/ddl-bootstrap-CfNvxMuK.js.map +0 -1
- package/dist/errors-DWsX4yTd.js.map +0 -1
- package/dist/function-routes-Chet4-lB.js.map +0 -1
- package/dist/history-recorder-BQmB0P_j.js.map +0 -1
- package/dist/jobs-Bjr8DZAi.js.map +0 -1
- package/dist/logger-DO2PZc4i.js.map +0 -1
- package/dist/logs-routes-3EEzPjhl.js.map +0 -1
- package/dist/openapi-generator-D8uFz-LW.js.map +0 -1
- package/dist/query-parser-BleZmY18.js.map +0 -1
- package/dist/schema-editor-routes-DdLihzp0.js.map +0 -1
- package/dist/src-1vL-I1Po.js.map +0 -1
- package/dist/src-Br6ARbs6.js.map +0 -1
|
@@ -44,6 +44,15 @@ declare class LogRingBuffer {
|
|
|
44
44
|
getLatest(count?: number): LogEntry[];
|
|
45
45
|
}
|
|
46
46
|
export declare const logBuffer: LogRingBuffer;
|
|
47
|
+
/**
|
|
48
|
+
* Which process this log belongs to.
|
|
49
|
+
*
|
|
50
|
+
* The ring is this process's memory, and nothing gathers the rings of a
|
|
51
|
+
* deployment's other replicas or of its split runtime roles. So a reader is
|
|
52
|
+
* told whose log it is: `HOSTNAME` is the pod name on Kubernetes and the
|
|
53
|
+
* container id under Docker; the pid tells two local processes apart.
|
|
54
|
+
*/
|
|
55
|
+
export declare function logInstanceName(): string;
|
|
47
56
|
/** Add a log entry */
|
|
48
57
|
export declare function addLog(level: LogEntry["level"], source: LogEntry["source"], message: string, metadata?: Record<string, unknown>): void;
|
|
49
58
|
export interface LogMiddlewareOptions {
|
|
@@ -55,7 +64,25 @@ export interface LogMiddlewareOptions {
|
|
|
55
64
|
* server it is also the thing evicting real entries out of the ring.
|
|
56
65
|
*/
|
|
57
66
|
ignorePaths?: string[];
|
|
67
|
+
/**
|
|
68
|
+
* Where the API is mounted (`/api`). The segment after it names the
|
|
69
|
+
* subsystem a request is filed under — see {@link sourceForRequestPath}.
|
|
70
|
+
* Unset, the first segment of the path is read.
|
|
71
|
+
*/
|
|
72
|
+
basePath?: string;
|
|
58
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* The `source` of a request entry: the subsystem its path addresses.
|
|
76
|
+
*
|
|
77
|
+
* `/api/auth/*` and the MCP authorization server under `/api/oauth/*` are
|
|
78
|
+
* `auth`, `/api/storage/*` is `storage`, and everything else is `api`. The
|
|
79
|
+
* Logs Explorer's Source filter matches this field, so a sign-in filed as
|
|
80
|
+
* `api` was invisible under Auth. The realtime socket is not an HTTP request
|
|
81
|
+
* and is not recorded here.
|
|
82
|
+
*
|
|
83
|
+
* Exported for its test.
|
|
84
|
+
*/
|
|
85
|
+
export declare function sourceForRequestPath(path: string, basePath?: string): LogEntry["source"];
|
|
59
86
|
/** Hono middleware to log API requests */
|
|
60
87
|
export declare function logMiddleware(options?: LogMiddlewareOptions): MiddlewareHandler<HonoEnv>;
|
|
61
88
|
/**
|
|
@@ -79,6 +106,17 @@ export interface LogStreamTiming {
|
|
|
79
106
|
heartbeatMs?: number;
|
|
80
107
|
maxPending?: number;
|
|
81
108
|
}
|
|
82
|
-
|
|
109
|
+
/** What ends the stream from the server's side. */
|
|
110
|
+
export interface LogsRoutesOptions {
|
|
111
|
+
/**
|
|
112
|
+
* Aborted when the backend shuts down. Every open stream ends then, rather
|
|
113
|
+
* than when its client happens to leave: `server.close()` waits on every
|
|
114
|
+
* open connection, so one Studio → Logs tab held a SIGTERM for the whole
|
|
115
|
+
* force timeout — longer than Cloud Run's or `docker stop`'s grace period,
|
|
116
|
+
* which killed the process before the pool was closed.
|
|
117
|
+
*/
|
|
118
|
+
closeSignal?: AbortSignal;
|
|
119
|
+
}
|
|
120
|
+
export declare function createLogsRoutes(timing?: LogStreamTiming, options?: LogsRoutesOptions): Hono<HonoEnv>;
|
|
83
121
|
declare const _default: Hono<HonoEnv, import("hono/types").BlankSchema, "/">;
|
|
84
122
|
export default _default;
|
|
@@ -28,3 +28,20 @@ export interface OpenApiGeneratorOptions {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
export declare function generateOpenApiSpec(collections: CollectionConfig[], options?: OpenApiGeneratorOptions): Record<string, unknown>;
|
|
31
|
+
/**
|
|
32
|
+
* Build the component schema for a collection (output / read shape).
|
|
33
|
+
*
|
|
34
|
+
* Every declared property except the ones {@link isDocumentedProperty} rules
|
|
35
|
+
* out, plus the foreign keys and relations {@link emitRelationProperties} adds.
|
|
36
|
+
*/
|
|
37
|
+
export declare function buildCollectionSchema(collection: CollectionConfig, registeredSchemas: ReadonlySet<string>): Record<string, unknown>;
|
|
38
|
+
/**
|
|
39
|
+
* Build an input schema (for POST/PUT) — excludes auto-generated fields.
|
|
40
|
+
*
|
|
41
|
+
* `excludeFromApi` columns are left out too, and the server now agrees: a write
|
|
42
|
+
* naming one is refused (`write-validation.ts`), which is what the flag's name
|
|
43
|
+
* and the generated SDK's own documentation always said. This comment used to
|
|
44
|
+
* record that such a write was "still accepted" — the document was right and
|
|
45
|
+
* the server was the thing that had not caught up.
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildCollectionInputSchema(collection: CollectionConfig): Record<string, unknown>;
|
|
@@ -1,7 +1,32 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { AuthAdapter, DataDriver, CollectionConfig } from "@rebasepro/types";
|
|
3
|
-
import { HonoEnv } from "../types.js";
|
|
3
|
+
import { QueryOptions, HonoEnv } from "../types.js";
|
|
4
4
|
import { type ListLimitOptions } from "./query-parser.js";
|
|
5
|
+
/**
|
|
6
|
+
* The row a single-row write addresses, as its route found it.
|
|
7
|
+
*
|
|
8
|
+
* `/comments/7` and `/posts/1/comments/7` address the same kind of row — a row
|
|
9
|
+
* of `comments` — two ways, and a write to it is one operation whichever way
|
|
10
|
+
* it arrived: the same body checks, the same `If-Match`, the same
|
|
11
|
+
* `Idempotency-Key`, the same answer. It was two pipelines, and the nested one
|
|
12
|
+
* re-listed two of the root's checks by hand and skipped the rest.
|
|
13
|
+
*/
|
|
14
|
+
export interface RowAddress {
|
|
15
|
+
/** The collection the row belongs to: what a body is checked against and an `ETag` computed for. */
|
|
16
|
+
collection: CollectionConfig;
|
|
17
|
+
/**
|
|
18
|
+
* The path the driver reads and writes at: the collection's own, or a
|
|
19
|
+
* nested one, which the driver resolves and scopes to its parent row.
|
|
20
|
+
*/
|
|
21
|
+
path: string;
|
|
22
|
+
/**
|
|
23
|
+
* Handed to the driver beside `path`. Unset for a nested path, whose
|
|
24
|
+
* collection the driver resolves from the path itself.
|
|
25
|
+
*/
|
|
26
|
+
driverCollection?: CollectionConfig;
|
|
27
|
+
/** What a 404 calls the address. */
|
|
28
|
+
name: string;
|
|
29
|
+
}
|
|
5
30
|
/**
|
|
6
31
|
* Lightweight REST API generator that leverages existing Rebase DataDriver.
|
|
7
32
|
* Supports `include` query parameter for eager-loading relations via Drizzle.
|
|
@@ -113,34 +138,64 @@ export declare class RestApiGenerator {
|
|
|
113
138
|
*/
|
|
114
139
|
private createUnmatchedRoute;
|
|
115
140
|
/**
|
|
116
|
-
* Check
|
|
117
|
-
*
|
|
118
|
-
*
|
|
141
|
+
* Check a narrowed credential's data scope for a collection operation:
|
|
142
|
+
* `data:<operation>` on the collection, or unqualified.
|
|
143
|
+
*
|
|
144
|
+
* Throws 403 when it is missing. A person's own session holds the whole
|
|
145
|
+
* data plane — their rows are RLS's to decide — so this only ever refuses
|
|
146
|
+
* an API key or a token.
|
|
119
147
|
*/
|
|
120
148
|
private enforceApiKeyPermission;
|
|
121
149
|
/**
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
150
|
+
* Data-scope check for nested paths.
|
|
151
|
+
*
|
|
152
|
+
* The operation lands on the collection the path addresses — the target
|
|
153
|
+
* of its last relation — so that is the collection the key must hold the
|
|
154
|
+
* route's operation for; checking only the parent would let a key scoped
|
|
155
|
+
* to `authors` write `posts`. The *collection*, not the path segment: a
|
|
156
|
+
* relation's name is not its target's slug, and a key for `notes` read
|
|
157
|
+
* `internal_notes` through `projects/1/notes` because the check read the
|
|
158
|
+
* name off the URL.
|
|
159
|
+
*
|
|
160
|
+
* Every collection the path passes through is addressed as well — "the
|
|
161
|
+
* posts of author 1" goes through a row of `authors` — so the key must be
|
|
162
|
+
* able to read each of them.
|
|
128
163
|
*/
|
|
129
164
|
private enforceSubcollectionApiKeyPermission;
|
|
130
165
|
/**
|
|
131
|
-
* The collection a
|
|
132
|
-
*
|
|
166
|
+
* The collection a path's first segment names.
|
|
167
|
+
*
|
|
168
|
+
* By the rule the driver's registry applies (`CollectionRegistry.get`),
|
|
169
|
+
* because the driver is what a nested path is forwarded to: the slug, then
|
|
170
|
+
* the slug written in kebab case — URLs are — then the table name. Matching
|
|
171
|
+
* the slug alone left the other two spellings resolving in the driver and
|
|
172
|
+
* in nothing here, so a nested request spelled either way was checked
|
|
173
|
+
* against no collection at all.
|
|
174
|
+
*/
|
|
175
|
+
private collectionNamed;
|
|
176
|
+
/**
|
|
177
|
+
* Walk a nested path to the collection it addresses — the target of the
|
|
178
|
+
* relation its last segment names.
|
|
133
179
|
*
|
|
134
|
-
* Needed so a nested
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
180
|
+
* Needed so a nested request can be checked against a schema at all: the
|
|
181
|
+
* fields a body may write and a query may read are the target's. A path
|
|
182
|
+
* that names nothing is a 404 here, before anything else happens. This
|
|
183
|
+
* used to return `undefined` instead and leave the refusal to the driver,
|
|
184
|
+
* and every check on these routes was written `if (target) …` — so a path
|
|
185
|
+
* the driver could resolve and this walk could not was a request with no
|
|
186
|
+
* checks on it, rather than a refused one.
|
|
138
187
|
*
|
|
139
|
-
*
|
|
140
|
-
* the
|
|
141
|
-
*
|
|
188
|
+
* The path handed back is spelled with the root's slug, which is what
|
|
189
|
+
* makes the two agree: whatever spelling arrived, the driver is asked about
|
|
190
|
+
* the collection this route checked the request against.
|
|
142
191
|
*/
|
|
143
|
-
private
|
|
192
|
+
private resolveNestedPath;
|
|
193
|
+
/**
|
|
194
|
+
* Refuse a write through `nested` that sets a relation this caller may not
|
|
195
|
+
* write — the parent's relation, or the parent's key on the child. See
|
|
196
|
+
* {@link assertNestedWriteAllowed}; the socket asks it the same question.
|
|
197
|
+
*/
|
|
198
|
+
private assertNestedRelationWritable;
|
|
144
199
|
/**
|
|
145
200
|
* `PATCH <collection>/<id>/<relation>/<targetId>` with a `_pivot` body: set
|
|
146
201
|
* the columns that one many-to-many link carries.
|
|
@@ -160,15 +215,30 @@ export declare class RestApiGenerator {
|
|
|
160
215
|
*/
|
|
161
216
|
private updateRelationPivotFromBody;
|
|
162
217
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
218
|
+
* Updates to rows of the auth collection, checked and put in stored form
|
|
219
|
+
* by the auth adapter — `AuthAdapter.prepareUserUpdates`. Those rows are
|
|
220
|
+
* the users, so an update is user administration and holds to what
|
|
221
|
+
* `/admin/users` holds to: the last administrator stays one, an email is
|
|
222
|
+
* stored the way sign-in looks it up. Any other collection's values, and
|
|
223
|
+
* an adapter with no such step, pass through unchanged.
|
|
224
|
+
*/
|
|
225
|
+
private prepareUserUpdates;
|
|
226
|
+
/**
|
|
227
|
+
* Delete rows of the auth collection as user administration: the adapter's
|
|
228
|
+
* checks and `beforeUserDelete` before (either may refuse), its session
|
|
229
|
+
* cleanup and `afterUserDelete` after. See {@link prepareUserUpdates}.
|
|
230
|
+
*/
|
|
231
|
+
private deletingUsers;
|
|
232
|
+
/**
|
|
233
|
+
* `POST /users/bulk`: every row a user creation, the rows written as one
|
|
234
|
+
* transaction, the invitations sent once it has committed.
|
|
166
235
|
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
236
|
+
* Answered in full whatever `Prefer` says, and never replayed from the
|
|
237
|
+
* idempotency store, as `POST /users` is: the answer can carry temporary
|
|
238
|
+
* passwords, which are shown once. Dropped from a minimal answer they are
|
|
239
|
+
* gone, and handed out again on a replayed key they are disclosed.
|
|
170
240
|
*/
|
|
171
|
-
private
|
|
241
|
+
private createUsers;
|
|
172
242
|
/**
|
|
173
243
|
* Get the request-scoped driver. Throws if none is set — never falls
|
|
174
244
|
* back to the unscoped `this.driver` to avoid bypassing RLS/auth.
|
|
@@ -199,16 +269,41 @@ export declare class RestApiGenerator {
|
|
|
199
269
|
*/
|
|
200
270
|
private runIdempotent;
|
|
201
271
|
/**
|
|
202
|
-
* The row
|
|
272
|
+
* The row an `ETag` is hashed from: the REST read of it, with no
|
|
273
|
+
* `?fields=` projection and no `?include=`.
|
|
274
|
+
*
|
|
275
|
+
* The read routes and the write routes both take their tag from here, so a
|
|
276
|
+
* tag a `GET` handed out and the tag a write compares it against are
|
|
277
|
+
* hashes of one rendering of the row. The write routes' own existence read
|
|
278
|
+
* is not that rendering, not even for its version column: `driver.fetchOne`
|
|
279
|
+
* returns the admin view model, where a date is a `{ __type: "date" }`
|
|
280
|
+
* envelope rather than the ISO string the REST row carries. Hashing it
|
|
281
|
+
* matched no tag any read ever handed out, so every conditional write to a
|
|
282
|
+
* collection with an `on_update` date was a 412.
|
|
283
|
+
*
|
|
284
|
+
* A driver without a REST read serves `GET /:id` from `fetchOne` too, so
|
|
285
|
+
* there the row already read is the rendering to hash.
|
|
286
|
+
*
|
|
287
|
+
* @param withDeleted how the row was read — a purge reads a trashed row,
|
|
288
|
+
* and a tag compared against a read that hides it compares against
|
|
289
|
+
* nothing.
|
|
290
|
+
*/
|
|
291
|
+
static rowForETag(driver: DataDriver, address: RowAddress, id: string, alreadyRead: Record<string, unknown> | undefined, withDeleted?: QueryOptions["withDeleted"]): Promise<Record<string, unknown> | undefined>;
|
|
292
|
+
/**
|
|
293
|
+
* `GET /:id`, at a row's own address or through a parent: the row as the
|
|
294
|
+
* query asked for it, and the `ETag` naming its version.
|
|
203
295
|
*
|
|
204
|
-
*
|
|
205
|
-
* the
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
296
|
+
* The tag names the row, not the read. `?fields=` and `?include=` change
|
|
297
|
+
* what the response carries, and hashing what they produced gave one row a
|
|
298
|
+
* different tag for every way of reading it — none of them the tag a write
|
|
299
|
+
* computes, so the `If-Match` of any caller that read a projection or a
|
|
300
|
+
* relation was a 412. A narrowed read therefore takes its tag from
|
|
301
|
+
* {@link rowForETag}, the read the write routes make.
|
|
210
302
|
*/
|
|
211
|
-
|
|
303
|
+
static readRow(driver: DataDriver, address: RowAddress, id: string, queryOptions: QueryOptions): Promise<{
|
|
304
|
+
row: Record<string, unknown>;
|
|
305
|
+
etag: string | undefined;
|
|
306
|
+
} | undefined>;
|
|
212
307
|
/**
|
|
213
308
|
* Answer a bulk write, honouring `Prefer: return=minimal`.
|
|
214
309
|
*
|
|
@@ -218,6 +313,28 @@ export declare class RestApiGenerator {
|
|
|
218
313
|
* the count never has to ask for the rows.
|
|
219
314
|
*/
|
|
220
315
|
private respondBulk;
|
|
316
|
+
/**
|
|
317
|
+
* Create one row: `POST /<collection>`, and the same create reached through
|
|
318
|
+
* a parent, `POST /<parent>/<id>/<relation>`.
|
|
319
|
+
*
|
|
320
|
+
* Errors from here are deliberately not re-classified. This layer cannot
|
|
321
|
+
* tell a constraint violation from an unreachable database, and it used to
|
|
322
|
+
* call both `BAD_REQUEST` — a claim that the caller sent something wrong and
|
|
323
|
+
* should not retry. The driver holds the SQLSTATE and raises an `ApiError`
|
|
324
|
+
* for what is genuinely the request's fault; anything still unclassified
|
|
325
|
+
* here is ours, and that is a 500.
|
|
326
|
+
*/
|
|
327
|
+
private createRow;
|
|
328
|
+
/**
|
|
329
|
+
* Update one row: `PATCH /<collection>/<id>`, and the same row reached
|
|
330
|
+
* through a parent. Errors are not re-classified; see {@link createRow}.
|
|
331
|
+
*/
|
|
332
|
+
private updateRow;
|
|
333
|
+
/**
|
|
334
|
+
* Delete one row: `DELETE /<collection>/<id>`, and the same row reached
|
|
335
|
+
* through a parent.
|
|
336
|
+
*/
|
|
337
|
+
private deleteRow;
|
|
221
338
|
/**
|
|
222
339
|
* Create REST routes for a collection using existing Rebase patterns
|
|
223
340
|
*/
|
|
@@ -258,17 +375,16 @@ export declare class RestApiGenerator {
|
|
|
258
375
|
* @param collectionPath the path to read — a nested listing passes its own
|
|
259
376
|
* `parent/:id/child` path, which the driver resolves.
|
|
260
377
|
*/
|
|
261
|
-
|
|
378
|
+
static readPage(driver: DataDriver, collection: CollectionConfig, queryOptions: QueryOptions, searchString?: string, searchExplain?: boolean, collectionPath?: string): Promise<{
|
|
379
|
+
rows: Record<string, unknown>[];
|
|
380
|
+
meta: Record<string, unknown>;
|
|
381
|
+
}>;
|
|
262
382
|
/**
|
|
263
383
|
* Fetch raw collection data without Entity wrapper (fallback for non-Postgres)
|
|
264
384
|
*/
|
|
265
|
-
|
|
385
|
+
static fetchRawCollection(driver: DataDriver, collection: CollectionConfig, queryOptions: QueryOptions, searchString?: string, searchExplain?: boolean, collectionPath?: string, startAfter?: Record<string, unknown>): Promise<Record<string, unknown>[]>;
|
|
266
386
|
/**
|
|
267
387
|
* Count raw entities for a collection
|
|
268
388
|
*/
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Fetch single entity raw data without Entity wrapper (fallback)
|
|
272
|
-
*/
|
|
273
|
-
private fetchRawEntity;
|
|
389
|
+
static countRawEntities(driver: DataDriver, collection: CollectionConfig, queryOptions: QueryOptions, searchString?: string, collectionPath?: string): Promise<number>;
|
|
274
390
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A write to the auth collection is user administration.
|
|
3
|
+
*
|
|
4
|
+
* The auth collection's rows are the users. `/admin/users` holds a change to
|
|
5
|
+
* one to the auth adapter's rules: a create goes through the adapter's user
|
|
6
|
+
* creation (the password hashed, the email normalized, the collection's
|
|
7
|
+
* `onCreateUser`, the invitation), an update may not leave the project without
|
|
8
|
+
* an administrator and stores an email the way sign-in looks it up, and a
|
|
9
|
+
* deletion runs `beforeUserDelete` (a veto), ends the sessions and runs
|
|
10
|
+
* `afterUserDelete`. The data doors that write the same rows — the REST routes
|
|
11
|
+
* and MCP's write tools — call these, so that no door writes a user by rules
|
|
12
|
+
* of its own. (The Postgres socket calls the adapter's methods directly.)
|
|
13
|
+
*
|
|
14
|
+
* Every function here leaves any other collection, and an adapter without the
|
|
15
|
+
* step, exactly as it was: the rows are then written as any table's.
|
|
16
|
+
*/
|
|
17
|
+
import type { AuthAdapter, CollectionConfig, UserCreationFinalizeResult, UserCreationPrepareResult } from "@rebasepro/types";
|
|
18
|
+
import type { FieldViewer } from "@rebasepro/common";
|
|
19
|
+
/** Whether `collection` is the auth collection — its rows are the users. */
|
|
20
|
+
export declare function isAuthCollection(collection: CollectionConfig): boolean;
|
|
21
|
+
/** An adapter that performs user creation (`prepareUserCreation`). */
|
|
22
|
+
export type UserCreatingAdapter = AuthAdapter & Required<Pick<AuthAdapter, "prepareUserCreation">>;
|
|
23
|
+
/**
|
|
24
|
+
* Whether a create on `collection` is a user creation `adapter` performs: the
|
|
25
|
+
* collection is the auth collection and the adapter has the step. When not,
|
|
26
|
+
* the door writes the row as any other.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createsUsers(adapter: AuthAdapter | undefined, collection: CollectionConfig): adapter is UserCreatingAdapter;
|
|
29
|
+
/**
|
|
30
|
+
* Check a create body for the auth collection against what the adapter says a
|
|
31
|
+
* create consumes (`describeUserCreationContract`).
|
|
32
|
+
*
|
|
33
|
+
* Signups carry credential fields (`password`) the collection does not declare
|
|
34
|
+
* as columns, so the body is checked for everything else. A custom
|
|
35
|
+
* `onCreateUser` owns the body's shape, and then the adapter asks for no check:
|
|
36
|
+
* the collection's fields do not describe what arrived.
|
|
37
|
+
*
|
|
38
|
+
* `rowIndex` names the row in a bulk write's message.
|
|
39
|
+
*/
|
|
40
|
+
export declare function assertUserCreationBodyValid(adapter: AuthAdapter | undefined, collection: CollectionConfig, body: Record<string, unknown>, viewer: FieldViewer, rowIndex?: number): void;
|
|
41
|
+
/**
|
|
42
|
+
* The first half of creating users, before anything is written: each body
|
|
43
|
+
* through the adapter's `prepareUserCreation` (the password hashed or
|
|
44
|
+
* generated, the email normalized, the collection's `onCreateUser`), in order.
|
|
45
|
+
* The `values` of each result are what the door writes.
|
|
46
|
+
*/
|
|
47
|
+
export declare function prepareUserCreations(adapter: UserCreatingAdapter, collection: CollectionConfig, bodies: readonly Record<string, unknown>[]): Promise<UserCreationPrepareResult[]>;
|
|
48
|
+
/**
|
|
49
|
+
* The second half, once the rows are written — and only then, so a write that
|
|
50
|
+
* fails invites nobody: the credentials' delivery through
|
|
51
|
+
* {@link completeUserCreation}, which is also what `POST /admin/users` calls.
|
|
52
|
+
*
|
|
53
|
+
* @returns For each row, the delivery fields a create response carries
|
|
54
|
+
* (`invitationSent`, and `temporaryPassword` when nobody was sent one).
|
|
55
|
+
*/
|
|
56
|
+
export declare function completeUserCreations(adapter: UserCreatingAdapter, prepared: readonly UserCreationPrepareResult[], rows: readonly Record<string, unknown>[]): Promise<UserCreationFinalizeResult[]>;
|
|
57
|
+
/**
|
|
58
|
+
* Create one user through the auth collection: {@link prepareUserCreations},
|
|
59
|
+
* then `save` (the door's own driver write), then
|
|
60
|
+
* {@link completeUserCreations}.
|
|
61
|
+
*
|
|
62
|
+
* `undefined` when the create is not a user creation ({@link createsUsers}) —
|
|
63
|
+
* the door then writes the row as any other.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createUserThroughAuthCollection(adapter: AuthAdapter | undefined, collection: CollectionConfig, body: Record<string, unknown>, save: (values: Record<string, unknown>) => Promise<Record<string, unknown>>): Promise<{
|
|
66
|
+
row: Record<string, unknown>;
|
|
67
|
+
delivery: UserCreationFinalizeResult;
|
|
68
|
+
} | undefined>;
|
|
69
|
+
/**
|
|
70
|
+
* Updates to rows of the auth collection, checked and put in stored form by
|
|
71
|
+
* the adapter's `prepareUserUpdates`: judged together, so a bulk demotion of
|
|
72
|
+
* every administrator is refused although each row alone would pass.
|
|
73
|
+
*
|
|
74
|
+
* @returns Each update's values, as they should be written, in order.
|
|
75
|
+
*/
|
|
76
|
+
export declare function prepareAuthCollectionUpdates(adapter: AuthAdapter | undefined, collection: CollectionConfig, updates: {
|
|
77
|
+
uid: string;
|
|
78
|
+
values: Record<string, unknown>;
|
|
79
|
+
}[]): Promise<Record<string, unknown>[]>;
|
|
80
|
+
/**
|
|
81
|
+
* Delete rows of the auth collection as user administration: the adapter's
|
|
82
|
+
* checks and `beforeUserDelete` before `remove` runs (either may refuse), its
|
|
83
|
+
* session cleanup and `afterUserDelete` after.
|
|
84
|
+
*/
|
|
85
|
+
export declare function deletingAuthCollectionUsers<T>(adapter: AuthAdapter | undefined, collection: CollectionConfig | undefined, uids: string[], remove: () => Promise<T>): Promise<T>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CollectionConfig } from "@rebasepro/types";
|
|
1
|
+
import type { CollectionConfig, IncludeSpec } from "@rebasepro/types";
|
|
2
2
|
import type { LogicalCondition } from "@rebasepro/types";
|
|
3
3
|
import { type FieldViewer } from "@rebasepro/common";
|
|
4
4
|
/**
|
|
@@ -39,7 +39,7 @@ export declare function requestViewer(c: {
|
|
|
39
39
|
get: (key: never) => unknown;
|
|
40
40
|
}): FieldViewer;
|
|
41
41
|
/** Which query parameter a refused field arrived in, for the message. */
|
|
42
|
-
type Where = "filter" | "orderBy" | "fields" | "select" | "groupBy";
|
|
42
|
+
type Where = "filter" | "orderBy" | "fields" | "select" | "groupBy" | "vector_search";
|
|
43
43
|
/**
|
|
44
44
|
* Refuse the request when any of `names` is a field this caller cannot read.
|
|
45
45
|
*
|
|
@@ -62,5 +62,9 @@ export declare function assertQueryFieldsReadable(options: {
|
|
|
62
62
|
field: string;
|
|
63
63
|
}[];
|
|
64
64
|
fields?: string[];
|
|
65
|
+
include?: IncludeSpec;
|
|
66
|
+
vectorSearch?: {
|
|
67
|
+
property: string;
|
|
68
|
+
};
|
|
65
69
|
}, collection: CollectionConfig, viewer: FieldViewer | undefined): void;
|
|
66
70
|
export {};
|
|
@@ -10,8 +10,14 @@ import { DataDriver } from "@rebasepro/types";
|
|
|
10
10
|
* carrying corrected rows from the retry of an unanswered one, which is the
|
|
11
11
|
* difference between "here is your earlier answer" and silently discarding a
|
|
12
12
|
* correction.
|
|
13
|
+
*
|
|
14
|
+
* The query string separates instructions sent to one path: `?hard=true` purges
|
|
15
|
+
* where the same `DELETE` without it soft-deletes, and `?on_conflict=` makes a
|
|
16
|
+
* create an upsert. Left out, a purge sent under the soft delete's key was
|
|
17
|
+
* answered with that delete's `204` and never ran. A request with no query
|
|
18
|
+
* string fingerprints exactly as it did before one could enter.
|
|
13
19
|
*/
|
|
14
|
-
export declare function requestFingerprint(method: string, path: string, body: unknown): Promise<string>;
|
|
20
|
+
export declare function requestFingerprint(method: string, path: string, body: unknown, query?: URLSearchParams): Promise<string>;
|
|
15
21
|
/**
|
|
16
22
|
* What a caller may do with a key.
|
|
17
23
|
*
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type CollectionConfig, type ResolvedRelation } from "@rebasepro/types";
|
|
2
|
+
import { type FieldViewer } from "@rebasepro/common";
|
|
3
|
+
/** The last hop of a nested path: the parent, the relation it names, and that relation's target. */
|
|
4
|
+
export interface NestedWriteHop {
|
|
5
|
+
parentCollection: CollectionConfig;
|
|
6
|
+
relation: ResolvedRelation;
|
|
7
|
+
targetCollection: CollectionConfig;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* What a write through a nested path does to the row the path addresses.
|
|
11
|
+
*
|
|
12
|
+
* - `create`: a new row under the parent (`POST /bands/7/members`);
|
|
13
|
+
* - `update`: an existing row reached through the parent (`PATCH …/members/3`);
|
|
14
|
+
* - `pivot`: the columns of one many-to-many link (`PATCH …/tags/5 { _pivot }`);
|
|
15
|
+
* - `unlink`: a `DELETE` through a nested path. Through a many-to-many one it
|
|
16
|
+
* drops the link; through an owning one it deletes the row, which writes
|
|
17
|
+
* no relation and is the row's own delete rules' to answer.
|
|
18
|
+
*/
|
|
19
|
+
export type NestedWriteKind = "create" | "update" | "pivot" | "unlink";
|
|
20
|
+
/**
|
|
21
|
+
* Refuse a write through a parent that sets a relation the caller may not write.
|
|
22
|
+
*
|
|
23
|
+
* A body is checked against the target's field rules, and that is all the
|
|
24
|
+
* nested routes used to check. But the address writes too, and what it writes
|
|
25
|
+
* is not in the body:
|
|
26
|
+
*
|
|
27
|
+
* - A create under a parent whose target carries the parent's key
|
|
28
|
+
* (`hasMany`/`hasOne`) has that key stamped from the URL — `POST
|
|
29
|
+
* /bands/7/members` sets `members.band_id = 7`, which `POST /members
|
|
30
|
+
* { bandId: 7 }` answers `FIELD_NOT_WRITABLE` for when `members.band` is
|
|
31
|
+
* closed to the caller.
|
|
32
|
+
* - A create is also a new member of the parent's relation, and every write
|
|
33
|
+
* through a many-to-many path is set membership: it links on create and on
|
|
34
|
+
* update, edits the link with `_pivot`, and drops it on delete. That is a
|
|
35
|
+
* write of the parent's relation property — `PATCH /posts/1 { tags: [5] }`
|
|
36
|
+
* when `posts.tags` is closed — and its rules hold here.
|
|
37
|
+
*
|
|
38
|
+
* An update through an owning parent changes neither: the driver refuses it
|
|
39
|
+
* unless the row is already the parent's, and does not write the key.
|
|
40
|
+
*
|
|
41
|
+
* One rule for every door that takes a nested path — the REST routes and the
|
|
42
|
+
* realtime socket — asked of the same fields `assertNoClosedFields` judges a
|
|
43
|
+
* body by, so the answer (`FIELD_NOT_WRITABLE`, or `VALIDATION_EXCLUDED_FIELDS`
|
|
44
|
+
* for an `excludeFromApi` relation) is the one the root route gives.
|
|
45
|
+
*/
|
|
46
|
+
export declare function assertNestedWriteAllowed(hop: NestedWriteHop, kind: NestedWriteKind, path: string, viewer: FieldViewer | undefined): void;
|
|
@@ -1,5 +1,37 @@
|
|
|
1
|
-
import { CollectionConfig } from "@rebasepro/types";
|
|
1
|
+
import { CollectionConfig, type IncludeSpec } from "@rebasepro/types";
|
|
2
2
|
import { type FieldViewer } from "@rebasepro/common";
|
|
3
|
+
/**
|
|
4
|
+
* Refuse a write naming a field this caller may not set.
|
|
5
|
+
*
|
|
6
|
+
* `excludeFromApi` means what the generated SDK already says it means: "absent
|
|
7
|
+
* from Row, Insert and Update — the API surface does not mention it, in either
|
|
8
|
+
* direction". The server only ever enforced the read half, so the column a
|
|
9
|
+
* collection had declared unservable was still writable by anyone whose
|
|
10
|
+
* policies let them write the row.
|
|
11
|
+
*
|
|
12
|
+
* On the user store that is the sharp one. `users_write_own` lets you update
|
|
13
|
+
* your own row, `password_hash` is a column on it, and setting it directly is a
|
|
14
|
+
* password change that does not need the old password — so a stolen access
|
|
15
|
+
* token, which expires within the hour, becomes a password the attacker chose.
|
|
16
|
+
* Everything else the flag protects is the same shape: a verification token, a
|
|
17
|
+
* rotation counter, a secret the server owns.
|
|
18
|
+
*
|
|
19
|
+
* `access.write: ["editor"]` is the same rule with a role list instead of an
|
|
20
|
+
* empty one, and answers `FIELD_NOT_WRITABLE` rather than
|
|
21
|
+
* `VALIDATION_EXCLUDED_FIELDS`. Both refuse rather than dropping the key: a
|
|
22
|
+
* write that silently discards a field reports success for an edit that did not
|
|
23
|
+
* happen, which is the one outcome a form cannot recover from.
|
|
24
|
+
*
|
|
25
|
+
* The empty-list half is refused for every caller, not only unprivileged ones,
|
|
26
|
+
* and regardless of `strictWrites`. The framework's own auth paths do not come
|
|
27
|
+
* through here — `prepareUserCreation` builds the row itself — so what is left
|
|
28
|
+
* is callers the rule was written to exclude.
|
|
29
|
+
*
|
|
30
|
+
* Exported for the history revert, which writes a stored version rather than a
|
|
31
|
+
* body and so asks this question of the fields the revert would change, not of
|
|
32
|
+
* every key the snapshot holds. `where` prefixes the message.
|
|
33
|
+
*/
|
|
34
|
+
export declare function assertNoClosedFields(values: Record<string, unknown>, collection: CollectionConfig, where: string, viewer: FieldViewer | undefined): void;
|
|
3
35
|
/**
|
|
4
36
|
* Reject a write naming a field the collection does not have.
|
|
5
37
|
*
|
|
@@ -119,7 +151,7 @@ export declare function assertWriteValuesValid(values: Record<string, unknown>,
|
|
|
119
151
|
* assuming one.
|
|
120
152
|
*/
|
|
121
153
|
export declare function projectResponseFields<T extends Record<string, unknown>>(rows: T[], fields: readonly string[] | undefined, collection: CollectionConfig, options?: {
|
|
122
|
-
include?:
|
|
154
|
+
include?: IncludeSpec;
|
|
123
155
|
}): T[];
|
|
124
156
|
/**
|
|
125
157
|
* Both write checks, as one call, for a transport that is not the REST router.
|
package/dist/api/types.d.ts
CHANGED
|
@@ -31,8 +31,24 @@ export type HonoEnv = {
|
|
|
31
31
|
*/
|
|
32
32
|
recipientEmail?: string;
|
|
33
33
|
driver?: DataDriver;
|
|
34
|
-
/** Set when the request is authenticated
|
|
34
|
+
/** Set when the request is authenticated with an API key (`rk_`). */
|
|
35
35
|
apiKey?: ApiKeyMasked;
|
|
36
|
+
/**
|
|
37
|
+
* What the caller may do, when its credential is narrower than its
|
|
38
|
+
* person — an API key or an MCP token. Absent for a person's own
|
|
39
|
+
* session, who holds what their roles hold. Read it through
|
|
40
|
+
* `callerScopes` / `hasScope`, which fill in that second case.
|
|
41
|
+
*/
|
|
42
|
+
scopes?: string[];
|
|
43
|
+
/**
|
|
44
|
+
* Who asked, when an administrator's request runs as another user
|
|
45
|
+
* (`x-rebase-impersonate`). `user` and `driver` are then the
|
|
46
|
+
* impersonated user's; this is the only place the administrator
|
|
47
|
+
* remains. See `auth/impersonation.ts`.
|
|
48
|
+
*/
|
|
49
|
+
impersonator?: {
|
|
50
|
+
uid: string;
|
|
51
|
+
};
|
|
36
52
|
/** Unique request correlation ID (generated or propagated from X-Request-ID header). */
|
|
37
53
|
requestId?: string;
|
|
38
54
|
/**
|