@rebasepro/types 0.17.3 → 0.18.1

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 (71) hide show
  1. package/README.md +4 -0
  2. package/dist/call_context.d.ts +20 -0
  3. package/dist/controllers/client.d.ts +36 -4
  4. package/dist/controllers/data.d.ts +120 -10
  5. package/dist/errors.d.ts +83 -4
  6. package/dist/index.es.js +522 -160
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/types/admin_block.d.ts +2 -2
  9. package/dist/types/auth_adapter.d.ts +41 -6
  10. package/dist/types/backend.d.ts +48 -0
  11. package/dist/types/collections.d.ts +25 -1
  12. package/dist/types/cron.d.ts +34 -0
  13. package/dist/types/database_adapter.d.ts +39 -0
  14. package/dist/types/entity_callbacks.d.ts +14 -1
  15. package/dist/types/filter-operators.d.ts +24 -1
  16. package/dist/types/policy.d.ts +29 -1
  17. package/dist/types/properties.d.ts +216 -3
  18. package/dist/types/relations.d.ts +65 -7
  19. package/dist/types/resource_kinds.d.ts +173 -17
  20. package/dist/types/resources.d.ts +108 -7
  21. package/dist/types/rls-functions.d.ts +11 -0
  22. package/dist/types/storage_source.d.ts +12 -23
  23. package/package.json +24 -23
  24. package/src/call_context.ts +0 -120
  25. package/src/controllers/auth_state.ts +0 -24
  26. package/src/controllers/client.ts +0 -494
  27. package/src/controllers/collection_registry.ts +0 -62
  28. package/src/controllers/data.ts +0 -1012
  29. package/src/controllers/data_driver.ts +0 -576
  30. package/src/controllers/effective_role.ts +0 -4
  31. package/src/controllers/email.ts +0 -91
  32. package/src/controllers/index.ts +0 -11
  33. package/src/controllers/storage.ts +0 -252
  34. package/src/errors.ts +0 -119
  35. package/src/index.ts +0 -5
  36. package/src/types/admin_block.ts +0 -209
  37. package/src/types/api_keys.ts +0 -108
  38. package/src/types/auth_adapter.ts +0 -580
  39. package/src/types/backend.ts +0 -987
  40. package/src/types/backup.ts +0 -26
  41. package/src/types/channel_bus.ts +0 -202
  42. package/src/types/chips.ts +0 -34
  43. package/src/types/collection_contract.ts +0 -278
  44. package/src/types/collections.ts +0 -763
  45. package/src/types/component_ref.ts +0 -92
  46. package/src/types/cron.ts +0 -213
  47. package/src/types/data_source.ts +0 -357
  48. package/src/types/database_adapter.ts +0 -267
  49. package/src/types/entities.ts +0 -226
  50. package/src/types/entity_callbacks.ts +0 -229
  51. package/src/types/filter-operators.ts +0 -444
  52. package/src/types/history.ts +0 -66
  53. package/src/types/index.ts +0 -36
  54. package/src/types/indexes.ts +0 -180
  55. package/src/types/policy.ts +0 -328
  56. package/src/types/postgres_introspection.ts +0 -101
  57. package/src/types/project_manifest.ts +0 -598
  58. package/src/types/properties.ts +0 -1368
  59. package/src/types/relations.ts +0 -417
  60. package/src/types/resource_kinds.ts +0 -390
  61. package/src/types/resources.ts +0 -368
  62. package/src/types/rls-functions.ts +0 -98
  63. package/src/types/schema_editing.ts +0 -157
  64. package/src/types/schema_version.ts +0 -112
  65. package/src/types/search.ts +0 -247
  66. package/src/types/security_rules.ts +0 -344
  67. package/src/types/storage_authorize.ts +0 -77
  68. package/src/types/storage_source.ts +0 -248
  69. package/src/types/websockets.ts +0 -117
  70. package/src/users/index.ts +0 -2
  71. package/src/users/user.ts +0 -69
@@ -1,77 +0,0 @@
1
- /**
2
- * The storage access-control contract.
3
- *
4
- * Lives here rather than in `@rebasepro/server` because a project declares its
5
- * `storageAuthorize` hook from its **config package**, and a config package
6
- * depends on `@rebasepro/types` alone. A type the contract requires but the
7
- * contract's author cannot import is not much of a contract.
8
- *
9
- * `@rebasepro/server` re-exports all of this, so existing imports keep working.
10
- */
11
-
12
- export type StorageOperation = "read" | "write" | "delete" | "list";
13
-
14
- /** The caller, as resolved by whichever auth middleware ran. */
15
- export interface StorageAuthorizeUser {
16
- uid: string;
17
- email?: string;
18
- roles?: string[];
19
- }
20
-
21
- export interface StorageAuthorizeContext {
22
- /** Object key, bucket prefix stripped and traversal already sanitized. */
23
- key: string;
24
- bucket: string;
25
- operation: StorageOperation;
26
- /** Null when the route allows unauthenticated access. */
27
- user: StorageAuthorizeUser | null;
28
- /** Named backend the request targeted, when one was given. */
29
- storageId?: string;
30
- /**
31
- * Trusted, RLS-bypassing data access, so the hook can answer the question it
32
- * actually has to answer: *who owns this object?*
33
- *
34
- * Without it the hook can only do prefix arithmetic on the key, which
35
- * expresses no real multi-tenant rule — ownership lives in a row, not in a
36
- * string. And it cannot simply import the server to get one: a project
37
- * declares this hook from its **config** package, which depends on
38
- * `@rebasepro/types` alone and cannot resolve `@rebasepro/server` at
39
- * runtime. So the accessor is handed in.
40
- *
41
- * It bypasses row-level security on purpose. The hook IS the authorization
42
- * decision; asking it to make that decision through a reader that has
43
- * already been narrowed by the caller's own permissions is circular.
44
- */
45
- data?: StorageAuthorizeData;
46
- }
47
-
48
- /**
49
- * The slice of the data API a storage hook needs: read a collection, in the
50
- * trusted server context.
51
- *
52
- * Deliberately read-only and deliberately tiny. A hook that can write is a hook
53
- * that can be tricked into writing, and an authorization check has no business
54
- * mutating anything.
55
- */
56
- export interface StorageAuthorizeData {
57
- collection(slug: string): {
58
- find(query?: Record<string, unknown>): Promise<{ data: Record<string, unknown>[] }>;
59
- findById(id: string): Promise<Record<string, unknown> | null>;
60
- };
61
- }
62
-
63
- /**
64
- * Per-object access control for storage, the analogue of an RLS policy on a
65
- * collection.
66
- *
67
- * Without it, storage routes authenticate but do not authorize: `requireAuth`
68
- * and `publicRead` are global switches, so any signed-in user could read any
69
- * key they could name. That is fine for a single-tenant app and useless for a
70
- * multi-tenant one, where the only thing separating two tenants' files would be
71
- * key unguessability — not an access-control model. Multi-tenant apps were
72
- * left to route every byte through a custom function to get an ownership check
73
- * in, and each of them had to invent it.
74
- *
75
- * Return false (or throw) to deny. Denials surface as 403.
76
- */
77
- export type StorageAuthorize = (ctx: StorageAuthorizeContext) => boolean | Promise<boolean>;
@@ -1,248 +0,0 @@
1
- /**
2
- * Describes a named storage backend — a place files live.
3
- *
4
- * Declared once and shared front + back: the frontend uses it to decide
5
- * transport (HTTP proxy vs direct SDK), the backend uses the same `key`
6
- * to resolve a StorageController, and collection properties reference
7
- * a definition by its `key` via `StorageConfig.storageSource`.
8
- *
9
- * This mirrors the {@link DataSourceDefinition} pattern used for databases.
10
- *
11
- * @group Models
12
- */
13
-
14
- /**
15
- * The default storage source key, used when a property does not specify
16
- * a `storageSource`. Shared by the frontend and backend registries so
17
- * both agree on "the default storage backend".
18
- * @group Models
19
- */
20
- export const DEFAULT_STORAGE_SOURCE_KEY = "(default)";
21
-
22
- /**
23
- * How the *frontend* reaches a storage backend.
24
- *
25
- * - `"server"` — through the Rebase backend REST API (`/api/storage`).
26
- * The backend holds the actual `StorageController` and routes by
27
- * storage-source key. This is the default and covers Local, S3, GCS,
28
- * and any other server-mediated engine.
29
- * - `"direct"` — straight from the client to the external backend via
30
- * its own SDK (e.g. Firebase Storage via `@firebase/storage`).
31
- * The Rebase backend is **not** in the upload/download path.
32
- *
33
- * @group Models
34
- */
35
- export type StorageSourceTransport = "server" | "direct";
36
-
37
- /**
38
- * Declarative definition of a storage source — a named place files live.
39
- *
40
- * Declared once and shared front and back: the frontend uses it to decide
41
- * transport (client HTTP proxy vs direct provider SDK), the backend uses
42
- * the same `key` to resolve a `StorageController`, and collection
43
- * properties reference a definition by its `key` via
44
- * `StorageConfig.storageSource`.
45
- *
46
- * @group Models
47
- */
48
- export interface StorageSourceDefinition {
49
- /**
50
- * Unique identifier for this storage source. Collection properties
51
- * point at it via `StorageConfig.storageSource`.
52
- * Defaults to {@link DEFAULT_STORAGE_SOURCE_KEY}.
53
- */
54
- key: string;
55
-
56
- /**
57
- * The engine backing this storage source (e.g. `"local"`, `"s3"`,
58
- * `"gcs"`, `"firebase"`, `"azure"`, or a custom id).
59
- */
60
- engine: string;
61
-
62
- /**
63
- * The credential set this source signs with, when several sources share one.
64
- *
65
- * ## What it is for
66
- *
67
- * Every binding a bucket needs is read per key — `S3_BUCKET__MEDIA`,
68
- * `S3_ACCESS_KEY_ID__MEDIA`, and so on. That is right for the bucket *name*,
69
- * which is different for every source by definition, and wrong for the
70
- * credentials, which usually are not: fifteen buckets on one MinIO install
71
- * meant fifteen copies of the same endpoint, access key and secret — ninety
72
- * variables where eighteen would do, and one key rotation became fifteen
73
- * paired edits where a single missed one fails at upload time with an opaque
74
- * signing error.
75
- *
76
- * Naming an account here lets the *account-scoped* bindings fall back to
77
- * `<BASE>__<ACCOUNT>` when no per-key value is set. The bucket name never
78
- * falls back: it is what distinguishes one source from another.
79
- *
80
- * ## Why it does not fall back to the bare variable
81
- *
82
- * A source with no `account` reads only its own suffixed names, exactly as
83
- * before — so every project that predates this is wire-identical. The
84
- * unsuffixed `S3_ACCESS_KEY_ID` belongs to the *default* source, and letting
85
- * a named bucket inherit it would mean a typo'd key silently signs with
86
- * another source's credentials. Two forms, both explicit, opt-in.
87
- */
88
- account?: string;
89
-
90
- /**
91
- * How the frontend reaches this storage. Defaults to `"server"`.
92
- *
93
- * When `"direct"`, the client uses a provider-specific SDK
94
- * (e.g. `@firebase/storage`) and the backend does not proxy
95
- * upload/download traffic for this source.
96
- */
97
- transport: StorageSourceTransport;
98
-
99
- /** Human-readable label for the UI (e.g. "Firebase Storage", "S3 Media"). */
100
- label?: string;
101
- }
102
-
103
- /**
104
- * A resolved storage source: the single source of truth that the frontend
105
- * router and backend registry both derive from.
106
- *
107
- * @group Models
108
- */
109
- export interface ResolvedStorageSource {
110
- /** Storage source key (routing key, shared front + back). */
111
- key: string;
112
- /** Engine backing the source. */
113
- engine: string;
114
- /** Frontend transport. */
115
- transport: StorageSourceTransport;
116
- /** Human-readable label. */
117
- label?: string;
118
- }
119
-
120
- /**
121
- * The environment-variable suffix for a storage or data source key.
122
- *
123
- * `""` for the default source — so a single-bucket project keeps configuring
124
- * plain `S3_BUCKET` — and `__<KEY>` for every named one, uppercased with
125
- * non-alphanumerics collapsed to underscores: `media-cdn` → `S3_BUCKET__MEDIA_CDN`.
126
- *
127
- * The rule derives the variable name from the declared key rather than
128
- * discovering keys by scanning the environment. Scanning would have to guess how
129
- * `S3_BUCKET__MEDIA_CDN` splits into a key; deriving cannot be ambiguous, and a
130
- * typo surfaces as a missing source at boot instead of a silently ignored
131
- * variable.
132
- *
133
- * It lives in this package, with no dependencies, because four things must agree
134
- * on it exactly: the CLI (validating a build), the runtime (reading its own
135
- * environment), the control plane (writing a tenant's Secret), and the docs. A
136
- * second implementation of a naming convention is a second chance to disagree.
137
- *
138
- * @group Models
139
- */
140
- export function storageEnvSuffix(key: string, defaultKey: string = DEFAULT_STORAGE_SOURCE_KEY): string {
141
- if (!key || key === defaultKey) return "";
142
- const normalized = key
143
- .replace(/[^A-Za-z0-9]+/g, "_")
144
- .replace(/^_+|_+$/g, "")
145
- .toUpperCase();
146
- if (!normalized) {
147
- throw new Error(
148
- `Source key "${key}" cannot be turned into an environment variable name. ` +
149
- "Use a key containing at least one letter or digit."
150
- );
151
- }
152
- return `__${normalized}`;
153
- }
154
-
155
- /**
156
- * Two distinct keys that collapse onto the same variable name, or `null`.
157
- *
158
- * `media-cdn` and `media_cdn` are different source keys but the same suffix, so
159
- * without this one of them silently reads the other's configuration. Returns the
160
- * offending pair rather than throwing, so each caller can raise it in its own
161
- * idiom — a `BundleError` at boot, a build failure in the CLI, a rejected deploy
162
- * in a control plane.
163
- *
164
- * @group Models
165
- */
166
- export function findStorageSuffixCollision(
167
- keys: string[],
168
- defaultKey: string = DEFAULT_STORAGE_SOURCE_KEY
169
- ): { a: string; b: string; suffix: string } | null {
170
- const seen = new Map<string, string>();
171
- for (const key of keys) {
172
- const suffix = storageEnvSuffix(key, defaultKey);
173
- const existing = seen.get(suffix);
174
- if (existing !== undefined && existing !== key) {
175
- return { a: existing, b: key, suffix };
176
- }
177
- seen.set(suffix, key);
178
- }
179
- return null;
180
- }
181
-
182
- /** The `storage` block of `rebase.json`, structurally. */
183
- export type DeclaredStorageSources = Record<string, {
184
- engine: string;
185
- transport?: StorageSourceTransport;
186
- label?: string;
187
- }>;
188
-
189
- /**
190
- * Merge the two places a project may declare storage sources into one list.
191
- *
192
- * `rebase.json` is authoritative for every field it states. Config code may add
193
- * sources it does not mention and fill in fields it left out, but may not
194
- * contradict it: the manifest is what a host reads to decide which buckets need
195
- * configuring, and a runtime that quietly disagreed with it would put the
196
- * console back to describing a topology the tenant does not have — the exact
197
- * failure this whole mechanism exists to end.
198
- *
199
- * Note what is *not* here: no default source is invented when both inputs are
200
- * empty. That decision belongs to the resolver, which knows whether declaring
201
- * nothing means "one plain bucket" (it does) or "no storage at all".
202
- *
203
- * @group Models
204
- */
205
- export function normalizeStorageSources(
206
- declared: DeclaredStorageSources | StorageSourceDefinition[] | undefined,
207
- exported: StorageSourceDefinition[] | undefined
208
- ): StorageSourceDefinition[] {
209
- const merged = new Map<string, StorageSourceDefinition>();
210
-
211
- // Two shapes, one meaning. `rebase.json` states sources as a record keyed by
212
- // source key, which is how JSON expresses a set of named things; the bundle
213
- // manifest stores the already-resolved array. Accepting both is what lets the
214
- // CLI and the runtime call this with what each of them happens to hold.
215
- const declaredEntries: [string, { engine: string; transport?: StorageSourceTransport; label?: string }][] =
216
- Array.isArray(declared)
217
- ? declared.filter(d => d?.key).map(d => [d.key, d])
218
- : Object.entries(declared ?? {});
219
-
220
- for (const [key, config] of declaredEntries) {
221
- merged.set(key, {
222
- key,
223
- engine: config.engine,
224
- transport: config.transport ?? "server",
225
- ...(config.label !== undefined ? { label: config.label } : {})
226
- });
227
- }
228
-
229
- for (const definition of exported ?? []) {
230
- if (!definition?.key) continue;
231
- const existing = merged.get(definition.key);
232
- if (!existing) {
233
- merged.set(definition.key, {
234
- key: definition.key,
235
- engine: definition.engine,
236
- transport: definition.transport ?? "server",
237
- ...(definition.label !== undefined ? { label: definition.label } : {})
238
- });
239
- continue;
240
- }
241
- // Fill gaps only. `rebase.json` stated these; code does not overrule it.
242
- if (existing.label === undefined && definition.label !== undefined) {
243
- existing.label = definition.label;
244
- }
245
- }
246
-
247
- return Array.from(merged.values());
248
- }
@@ -1,117 +0,0 @@
1
- export interface WebSocketErrorPayload {
2
- error?: string | { message: string; code?: string };
3
- message?: string;
4
- code?: string;
5
- }
6
-
7
- export interface WebSocketMessage {
8
- type: string;
9
- payload?: unknown;
10
- subscriptionId?: string;
11
- requestId?: string;
12
- rows?: Record<string, unknown>[];
13
- row?: Record<string, unknown> | null;
14
- error?: string;
15
- /**
16
- * Channel name, on broadcast and presence frames.
17
- *
18
- * These are addressed by channel rather than by `requestId` or
19
- * `subscriptionId`, so this is the only field that routes them.
20
- */
21
- channel?: string;
22
- }
23
-
24
- /**
25
- * The key columns a collection's rows are addressed by.
26
- *
27
- * A row is exactly its columns and carries no address, so a subscriber that has
28
- * to recognise one — to patch it, or to keep its reference across a refetch —
29
- * derives the address from these. The SDK is usable with no collections
30
- * declared at all, so the server is the only side that knows them.
31
- *
32
- * Undefined when the server cannot resolve them: a table with no primary key
33
- * and no `id` column has no address, and rows of it cannot be recognised by
34
- * anyone.
35
- */
36
- export type WirePrimaryKeys = { fieldName: string; type: "string" | "number"; isUUID?: boolean }[];
37
-
38
- export interface CollectionUpdateMessage extends WebSocketMessage {
39
- type: "collection_update";
40
- subscriptionId: string;
41
- rows: Record<string, unknown>[];
42
- /**
43
- * See {@link WirePrimaryKeys}. Sent with the rows themselves — and not only
44
- * with a patch — because a CDC-originated change sends no patch at all: it
45
- * invalidates and goes straight to a refetch, and the merge that preserves
46
- * unchanged rows' references needs an address to match them by.
47
- */
48
- pks?: WirePrimaryKeys;
49
- }
50
-
51
- export interface SingleUpdateMessage extends WebSocketMessage {
52
- type: "single_update";
53
- subscriptionId: string;
54
- row: Record<string, unknown> | null;
55
- }
56
-
57
- /**
58
- * Lightweight patch message sent to collection subscribers when a single
59
- * row is created, updated, or deleted. The client can merge this into
60
- * its cached collection data for near-instant cross-tab updates without
61
- * waiting for a full collection refetch.
62
- */
63
- export interface CollectionPatchMessage extends WebSocketMessage {
64
- type: "collection_patch";
65
- subscriptionId: string;
66
- /** The address of the row this patch refers to — derived, never read off it. */
67
- id: string;
68
- /** The updated row, or null if deleted */
69
- row: Record<string, unknown> | null;
70
- /** See {@link WirePrimaryKeys}: how the subscriber finds {@link id} in its cache. */
71
- pks?: WirePrimaryKeys;
72
- }
73
-
74
- /**
75
- * One retained broadcast, as it travels on the wire.
76
- *
77
- * `seq` is per-channel, dense and monotonically increasing — it is the only
78
- * thing a reconnecting client needs to say where it got to. See
79
- * {@link ChannelHistoryMessage}.
80
- */
81
- export interface ChannelHistoryEntry {
82
- seq: number;
83
- event: string;
84
- payload: unknown;
85
- /**
86
- * The server-side client id of whoever sent it, for information only.
87
- *
88
- * Deliberately not used to filter a client's own messages out of a replay:
89
- * a reconnect assigns a brand-new client id, so the very case replay exists
90
- * for is the case where this would fail to match. Consumers that cannot
91
- * tolerate re-applying their own operations must make them idempotent.
92
- */
93
- senderId?: string;
94
- /** When the server accepted it, ISO-8601. */
95
- at: string;
96
- }
97
-
98
- /**
99
- * Server → client: the retained messages a client missed.
100
- *
101
- * `retained: false` means the channel has no retention rule configured, so
102
- * there is no history to replay and there never will be — an explicit answer
103
- * rather than an empty one, so a client can tell "nothing missed" apart from
104
- * "this channel does not keep history".
105
- */
106
- export interface ChannelHistoryMessage extends WebSocketMessage {
107
- type: "channel_history";
108
- channel: string;
109
- messages: ChannelHistoryEntry[];
110
- retained: boolean;
111
- /**
112
- * The highest seq the server holds for this channel, whether or not it was
113
- * returned. Lets a client that capped its request with `limit` see that it
114
- * is still behind, and decide to resync wholesale instead of paging.
115
- */
116
- latestSeq?: number;
117
- }
@@ -1,2 +0,0 @@
1
- export * from "./user";
2
-
package/src/users/user.ts DELETED
@@ -1,69 +0,0 @@
1
-
2
- /**
3
- * The canonical representation of an authenticated user in the Rebase ecosystem.
4
- *
5
- * Used by {@link AuthController}, collections, callbacks, and both the
6
- * `@rebasepro/client` and `@rebasepro/app` packages. It is the only user type
7
- * those packages export — the `RebaseUser` / `UserInfo` aliases are gone.
8
- *
9
- * **Backend-managed fields** (`uid`, `email`, `roles`, `metadata`, `createdAt`)
10
- * are populated by the server. **Client-visible fields** (`displayName`,
11
- * `photoURL`, `providerId`, `isAnonymous`, `emailVerified`) may be set during
12
- * authentication or profile updates.
13
- *
14
- * @see AdminUser — the admin-API DTO, which adds audit fields (`createdAt`,
15
- * `updatedAt`) and required `roles`.
16
- *
17
- * @group Models
18
- */
19
- export type User = {
20
- /**
21
- * The user's unique ID, scoped to the project.
22
- */
23
- readonly uid: string;
24
- /**
25
- * The display name of the user.
26
- */
27
- readonly displayName: string | null;
28
- /**
29
- * The email of the user.
30
- */
31
- readonly email: string | null;
32
- /**
33
- * The profile photo URL of the user.
34
- */
35
- readonly photoURL: string | null;
36
- /**
37
- * The provider used to authenticate the user (e.g. `"password"`,
38
- * `"google"`, `"github"`).
39
- */
40
- readonly providerId: string;
41
- /**
42
- * Whether the user is anonymous (created via anonymous sign-in).
43
- */
44
- readonly isAnonymous: boolean;
45
-
46
- /**
47
- * Whether the user's email address has been verified.
48
- */
49
- readonly emailVerified?: boolean;
50
-
51
- /**
52
- * Role IDs assigned to this user (e.g. `["admin", "editor"]`).
53
- */
54
- roles?: string[];
55
-
56
- /**
57
- * The date and time when the user was created.
58
- */
59
- createdAt?: Date | string | null;
60
-
61
- /**
62
- * Additional metadata/custom claims associated with the user.
63
- * Accessible by the frontend, but only writable by the backend.
64
- */
65
- readonly metadata?: Record<string, any>;
66
-
67
- getIdToken?: (forceRefresh?: boolean) => Promise<string>;
68
-
69
- };