@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
@@ -19,9 +19,9 @@
19
19
  * A declaration says a resource *exists* and what shape it has. It never says
20
20
  * how to reach it — that is a property of the environment, not of the project,
21
21
  * and it differs between a laptop, a self-hosted box and a tenant in the cloud.
22
- * Binding lives in `@rebasepro/server`'s boot path, reading environment
23
- * variables or an infrastructure config file, and it keys off the logical name
24
- * declared here.
22
+ * Binding lives in `@rebasepro/server`'s boot path, where each kind registers
23
+ * the resolver that reads its environment variables, keyed off the logical
24
+ * name declared here.
25
25
  *
26
26
  * This split is the whole point. Before it, storage topology was hand-written
27
27
  * into `rebase.json` while database topology lived in TypeScript, and the
@@ -54,6 +54,26 @@ export type ResourceTransport =
54
54
  export interface ResourceKindSpec {
55
55
  /** The kind's name, as it appears in a declaration and in the graph. */
56
56
  kind: string;
57
+ /**
58
+ * Which definition of this kind this is. Bump it whenever anything else in
59
+ * the spec changes.
60
+ *
61
+ * Two copies of this package can meet in one process — a published driver
62
+ * inlines it into its dist, and the runtime image ships its own — and the
63
+ * registry is shared between them on purpose. Without a revision the only
64
+ * thing the registry can do with two specs that differ is refuse, and a
65
+ * refusal at driver load is a pod that never boots: every bundle built with
66
+ * a driver older than the change dies on the first image that carries it.
67
+ * With one, the higher revision wins whichever copy loads first, and the
68
+ * older copy is told so. Missing means 0, which is what every copy shipped
69
+ * before revisions existed reports.
70
+ *
71
+ * Only copies that know about revisions honour them. A copy published
72
+ * BEFORE they existed still compares the whole literal and throws, so a
73
+ * kind that has shipped in such a copy cannot change its literal at all —
74
+ * not even to add this field. Correct those kinds with `amendResourceKind`.
75
+ */
76
+ revision?: number;
57
77
  /** Engines this kind ships with. `custom:<id>` is always additionally valid. */
58
78
  engines: readonly string[];
59
79
  /** Used when a declaration names none. */
@@ -100,6 +120,19 @@ export interface ResourceDeclaration {
100
120
  label?: string;
101
121
  /** Kind-specific options, validated against the kind's `optionKeys`. */
102
122
  options: Readonly<Record<string, unknown>>;
123
+ /**
124
+ * What in the project reaches this resource, as `<what>:<name>` — a
125
+ * `collection:posts` routed to a database, a `property:posts.cover` stored
126
+ * in a bucket, a `function:report` importing a handle.
127
+ *
128
+ * Recorded by the derive step, never by a constructor: a declaration says
129
+ * a resource exists, and only a reader that has evaluated the rest of the
130
+ * project can say who uses it. It is the map a host needs to split a
131
+ * monolith into units later, and the map a console needs to answer "what
132
+ * breaks if I remove this". Absent when nothing was recorded, which is
133
+ * different from an empty list.
134
+ */
135
+ usedBy?: readonly string[];
103
136
  }
104
137
  /**
105
138
  * The value a constructor returns.
@@ -115,9 +148,68 @@ export interface ResourceHandle extends ResourceDeclaration {
115
148
  }
116
149
  /** Whether a value is a resource handle rather than a plain string key. */
117
150
  export declare function isResourceHandle(value: unknown): value is ResourceHandle;
151
+ /**
152
+ * A reference to a resource where a key is expected: the handle a constructor
153
+ * returned, or the key spelled as a string.
154
+ *
155
+ * The handle is the point. `dataSource: analytics` is the same name spelled
156
+ * once — rename the export and every use follows, jump-to-definition lands on
157
+ * the declaration, and the derive step can record who uses what. The string
158
+ * form stays because a key has to survive serialisation: the runtime and the
159
+ * admin UI read collections as plain data, where a handle cannot travel.
160
+ */
161
+ export type ResourceRef = string | ResourceHandle;
118
162
  /** The key a resource reference names, whether it is a handle or already a key. */
119
- export declare function resourceKeyOf(ref: string | ResourceHandle): string;
120
- /** Register a resource kind. Idempotent for an identical spec; throws on a conflicting one. */
163
+ export declare function resourceKeyOf(ref: ResourceRef): string;
164
+ /**
165
+ * Replace every resource handle inside a value with its key, deeply.
166
+ *
167
+ * Applied where authored config becomes data: `defineCollection`, the
168
+ * collection loaders, the derive step. Past that point a collection is plain
169
+ * data that serialises, compares with `===` and reaches the admin UI over the
170
+ * wire, so a handle must not survive into it. Plain objects and arrays are
171
+ * walked; anything else — a function, a Date, a class instance — is a leaf and
172
+ * is returned as it is, which is what keeps callbacks and validators intact.
173
+ */
174
+ export declare function resolveResourceRefs<T>(value: T): T;
175
+ /**
176
+ * Corrections this copy applies on top of a registered kind.
177
+ *
178
+ * Deliberately a module local — per COPY of this package — where the registry
179
+ * above is deliberately shared. A published driver inlines this package into
180
+ * its dist, and the copy it carries compares the shared registry's entry for a
181
+ * kind against its own literal by `JSON.stringify` and throws if they differ
182
+ * (see `registerResourceKind` before revisions existed). That code is in the
183
+ * field and cannot be changed, so the registered literal of any kind that has
184
+ * ever shipped is frozen: change one enumerable key and every bundle built with
185
+ * an older driver dies at driver load on the next image. What a kind actually
186
+ * binds can still be corrected — here, read through `resourceKind()` and
187
+ * everything built on it, invisible to the older copy, which keeps binding the
188
+ * way it did when it was published.
189
+ */
190
+ type KindAmendment = Partial<Pick<ResourceKindSpec, "envBases" | "envBasesByEngine" | "optionKeys">>;
191
+ /**
192
+ * Correct a registered kind without touching its registered literal.
193
+ *
194
+ * Use this, never an edit to the literal, for a kind that has shipped in a
195
+ * published package. The amendment applies to reads through this copy only.
196
+ */
197
+ export declare function amendResourceKind(kind: string, amendment: KindAmendment): void;
198
+ /**
199
+ * Register a resource kind.
200
+ *
201
+ * Idempotent for an identical spec. When a spec for the same kind is already
202
+ * registered and differs, the `revision` decides: the higher one is kept and
203
+ * the other copy is warned about, in either load order. Two different specs at
204
+ * the SAME revision are a genuine conflict — two packages defining one kind, or
205
+ * a change that forgot to bump — and still throw.
206
+ *
207
+ * Both copies in that comparison are peers on `KINDS_KEY`, which is what makes
208
+ * the rule enforceable: a copy old enough not to know `revision` writes to the
209
+ * legacy map instead and never reaches this function's arithmetic. Registering
210
+ * a kind an older copy already put in the legacy map is therefore not a
211
+ * conflict — it is the ordinary case, and `visibleKinds` prefers this one.
212
+ */
121
213
  export declare function registerResourceKind(spec: ResourceKindSpec): void;
122
214
  /** Every registered kind, for validators and for `rebase doctor`. */
123
215
  export declare function resourceKinds(): ResourceKindSpec[];
@@ -185,8 +277,16 @@ export interface ResourceGraph {
185
277
  }
186
278
  /** The current graph format version. */
187
279
  export declare const RESOURCE_GRAPH_VERSION: 1;
188
- /** Build a graph from the current declarations, sorted for a stable diff. */
189
- export declare function buildResourceGraph(): ResourceGraph;
280
+ /**
281
+ * Build a graph from the current declarations, sorted for a stable diff.
282
+ *
283
+ * `usedBy` maps a `kind:key` id to the things that reach it. The derive step
284
+ * supplies it after evaluating collections; a runtime building the graph at
285
+ * boot passes nothing and gets declarations alone, which is all it binds from.
286
+ */
287
+ export declare function buildResourceGraph(usedBy?: ReadonlyMap<string, readonly string[]>): ResourceGraph;
288
+ /** `kind:key`, the id `usedBy` maps are keyed by. Exported for the derive step. */
289
+ export declare function resourceId(kind: string, key: string): string;
190
290
  /**
191
291
  * The environment variables worth writing for a resource, given its engine.
192
292
  *
@@ -195,3 +295,4 @@ export declare function buildResourceGraph(): ResourceGraph;
195
295
  * guessing narrow would silently omit the variable it actually needs.
196
296
  */
197
297
  export declare function envBasesForResource(declaration: ResourceDeclaration): readonly string[];
298
+ export {};
@@ -56,6 +56,17 @@ export declare const REBASE_SCHEMA = "rebase";
56
56
  export declare const RLS_UID_SQL = "rebase.uid()";
57
57
  /** The request's roles as a comma-separated string, for `string_to_array`. */
58
58
  export declare const RLS_ROLES_SQL = "rebase.roles()";
59
+ /**
60
+ * Whether the caller is a GUEST — signed in through anonymous sign-in rather
61
+ * than with an account.
62
+ *
63
+ * A different question from {@link ANONYMOUS_USER_ID}, and the two are easy to
64
+ * confuse: that sentinel means "no session at all", while this means "a session
65
+ * with nobody behind it". Anonymous sign-in mints a real user row with a real
66
+ * uid, so before this reached the database the two kinds of caller were one
67
+ * principal inside every policy.
68
+ */
69
+ export declare const RLS_IS_ANONYMOUS_SQL = "rebase.is_anonymous()";
59
70
  /** The request's JWT claims as `jsonb`, or `{}`. */
60
71
  export declare const RLS_JWT_SQL = "rebase.jwt()";
61
72
  /**
@@ -89,6 +89,18 @@ export interface StorageSourceDefinition {
89
89
  * upload/download traffic for this source.
90
90
  */
91
91
  transport: StorageSourceTransport;
92
+ /**
93
+ * Serve unqualified uploads — a storage property naming no `storageSource`
94
+ * — from this source.
95
+ *
96
+ * Declared, never inferred. A project with named buckets and no default
97
+ * used to have one chosen for it by declaration order, with a warning, and
98
+ * the choice differed between development and production because the
99
+ * synthesized local default is dropped in production and the promotion was
100
+ * not. Where the files land is the author's decision; boot now fails
101
+ * without one.
102
+ */
103
+ default?: boolean;
92
104
  /** Human-readable label for the UI (e.g. "Firebase Storage", "S3 Media"). */
93
105
  label?: string;
94
106
  }
@@ -145,26 +157,3 @@ export declare function findStorageSuffixCollision(keys: string[], defaultKey?:
145
157
  b: string;
146
158
  suffix: string;
147
159
  } | null;
148
- /** The `storage` block of `rebase.json`, structurally. */
149
- export type DeclaredStorageSources = Record<string, {
150
- engine: string;
151
- transport?: StorageSourceTransport;
152
- label?: string;
153
- }>;
154
- /**
155
- * Merge the two places a project may declare storage sources into one list.
156
- *
157
- * `rebase.json` is authoritative for every field it states. Config code may add
158
- * sources it does not mention and fill in fields it left out, but may not
159
- * contradict it: the manifest is what a host reads to decide which buckets need
160
- * configuring, and a runtime that quietly disagreed with it would put the
161
- * console back to describing a topology the tenant does not have — the exact
162
- * failure this whole mechanism exists to end.
163
- *
164
- * Note what is *not* here: no default source is invented when both inputs are
165
- * empty. That decision belongs to the resolver, which knows whether declaring
166
- * nothing means "one plain bucket" (it does) or "no storage at all".
167
- *
168
- * @group Models
169
- */
170
- export declare function normalizeStorageSources(declared: DeclaredStorageSources | StorageSourceDefinition[] | undefined, exported: StorageSourceDefinition[] | undefined): StorageSourceDefinition[];
package/package.json CHANGED
@@ -1,39 +1,43 @@
1
1
  {
2
2
  "name": "@rebasepro/types",
3
- "type": "module",
4
- "version": "0.17.3",
3
+ "version": "0.18.1",
5
4
  "description": "Rebase type definitions — shared interfaces and controller types",
6
- "funding": {
7
- "url": "https://github.com/sponsors/rebaseco"
5
+ "keywords": [
6
+ "rebase",
7
+ "cms",
8
+ "admin",
9
+ "typescript",
10
+ "headless",
11
+ "headless cms",
12
+ "content manager"
13
+ ],
14
+ "homepage": "https://rebase.pro",
15
+ "bugs": {
16
+ "url": "https://github.com/rebasepro/rebase/issues"
8
17
  },
9
- "author": "Rebase",
10
- "license": "MIT",
11
18
  "repository": {
12
19
  "type": "git",
13
20
  "url": "https://github.com/rebasepro/rebase.git",
14
21
  "directory": "packages/types"
15
22
  },
23
+ "funding": {
24
+ "url": "https://github.com/sponsors/rebasepro"
25
+ },
26
+ "license": "MIT",
27
+ "author": "Rebase",
28
+ "type": "module",
16
29
  "main": "./dist/index.es.js",
17
30
  "module": "./dist/index.es.js",
18
31
  "types": "./dist/index.d.ts",
19
- "source": "src/index.ts",
20
32
  "engines": {
21
- "node": ">=20"
33
+ "node": ">=22.22.0"
22
34
  },
23
- "keywords": [
24
- "rebase",
25
- "cms",
26
- "admin",
27
- "typescript",
28
- "headless",
29
- "headless cms",
30
- "content manager"
31
- ],
32
35
  "exports": {
33
36
  ".": {
34
37
  "types": "./dist/index.d.ts",
35
38
  "development": "./dist/index.es.js",
36
- "import": "./dist/index.es.js"
39
+ "import": "./dist/index.es.js",
40
+ "default": "./dist/index.es.js"
37
41
  },
38
42
  "./package.json": "./package.json"
39
43
  },
@@ -48,12 +52,8 @@
48
52
  "typescript": "^6.0.3",
49
53
  "vite": "^8.1.5"
50
54
  },
51
- "peerDependencies": {
52
- "hono": "^4.12.27"
53
- },
54
55
  "files": [
55
- "dist",
56
- "src"
56
+ "dist"
57
57
  ],
58
58
  "gitHead": "d935eefa5aa8d1009a2398cfac2c1e4ee9aeb6b6",
59
59
  "publishConfig": {
@@ -91,6 +91,7 @@
91
91
  "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json && node ../../tooling/scripts/add-dts-extensions.mjs dist && node ../../tooling/scripts/assert-build-output.mjs",
92
92
  "test:lint": "eslint \"src/**\" --quiet",
93
93
  "test": "jest --passWithNoTests",
94
+ "test:watch": "jest --watch",
94
95
  "clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f"
95
96
  }
96
97
  }
@@ -1,120 +0,0 @@
1
- import type { DataDriver } from "./controllers/data_driver";
2
- import type { StorageSource } from "./controllers/storage";
3
- import type { RebaseClient } from "./controllers/client";
4
- import type { RebaseSdkData } from "./controllers/data";
5
- import type { User } from "./users";
6
-
7
- /**
8
- * Context that is provided to entity callbacks (hooks).
9
- * It contains only the dependencies that are available in both the frontend and the backend.
10
- *
11
- * This is the *whole* context a collection callback gets, and it lives apart from
12
- * {@link RebaseContext} on purpose. `RebaseContext` widens it with nine admin-panel
13
- * controllers — navigation, side dialogs, snackbars — none of which exist in a
14
- * backend process. Keeping them in one type meant every backend module that
15
- * touched a callback signature transitively named the admin UI.
16
- *
17
- * @group Hooks and utilities
18
- */
19
- export type RebaseCallContext<USER extends User = User> = {
20
-
21
- /**
22
- * The Rebase client instance.
23
- * Available in all entity callbacks (beforeSave, afterSave, afterRead,
24
- * beforeDelete, afterDelete) and in CollectionActionsProps via context.
25
- * Use it to call backend functions, access storage, send email, etc.
26
- *
27
- * ⚠️ **Not the same trust level as {@link data}.** Server-side this is the
28
- * app singleton, so `client.dataAsAdmin` is **always** the admin-scoped
29
- * plane — scoped as `{ uid: "service", roles: ["admin"] }`, so policies are
30
- * evaluated against that identity rather than skipped — while {@link data},
31
- * one property over, follows whoever triggered the callback. On a user
32
- * request, reaching for `context.client.dataAsAdmin` silently escalates a
33
- * user-scoped operation to admin. For queries in a callback use
34
- * {@link data}; come here for functions, storage and email.
35
- *
36
- * @example
37
- * // In a beforeSave callback:
38
- * const result = await context.client.functions.invoke('my-function', { ... });
39
- *
40
- * @example
41
- * // In a CollectionAction component:
42
- * const { client } = props.context;
43
- * const result = await client.functions.invoke('extract-job', { url });
44
- */
45
- client: RebaseClient;
46
-
47
- /**
48
- * Unified data access — `context.data.products.create(...)`.
49
- * Access any collection as a dynamic property.
50
- *
51
- * **Inherits the privilege of whatever triggered the callback.** This is not
52
- * a fixed trust level, and it is the one thing to know about this accessor:
53
- *
54
- * - Triggered by a **user request** (REST, realtime, an admin-panel edit):
55
- * user-scoped. The callback runs on the RLS-bound transaction opened for
56
- * that request, so policies apply to reads *and* writes — a callback
57
- * cannot see a row its caller could not.
58
- * - Triggered by **`rebase.dataAsAdmin` or a cron** (the same singleton):
59
- * admin-scoped, not unscoped. That driver is scoped as
60
- * `{ uid: "service", roles: ["admin"] }`, so the callback still runs on an
61
- * RLS-bound transaction — policies are evaluated against that identity.
62
- * - Triggered by **the base driver** (auth flows, migrations): unscoped, on
63
- * the owner connection, bypassing RLS.
64
- *
65
- * So a callback that reads a sibling row will find it when an admin task
66
- * saves and may find nothing when an end user saves — without an error,
67
- * because RLS filters rather than raises. Write callbacks that tolerate
68
- * that, or reach for {@link client}`.dataAsAdmin` deliberately when the
69
- * callback genuinely has to see what an admin may see. Note what that does
70
- * *not* buy you: `policy.serverContext()` (`rebase.uid() IS NULL`) is false
71
- * for the service identity, so a collection whose only rule is
72
- * `serverContext()` stays closed to it.
73
- *
74
- * Verified end-to-end against Postgres rather than asserted — see
75
- * `"scopes context.data to the caller when a callback runs on a user
76
- * request"` in `server-postgres`' `rls-enforcement` e2e suite. The
77
- * documentation previously claimed the opposite (that callbacks always have
78
- * full access), which is the unsafe direction to be wrong in.
79
- *
80
- * Returns flat rows (`{ id, ...columns }`), identical in *shape* to the
81
- * frontend SDK client — so `context.data` in a backend callback and
82
- * `client.data` in the frontend are accessed the same way (`row.title`,
83
- * never `row.values.title`). Shape only: privilege differs as above.
84
- */
85
- data: RebaseSdkData;
86
-
87
- /**
88
- * The driver executing the operation this callback is attached to.
89
- *
90
- * Present server-side only. Declared here because it is already public in
91
- * practice — the backend has always passed it, and the callbacks guide
92
- * documented `context.driver.withAuth(user)` in all six locales. The
93
- * contract simply did not name it, so `buildCallContext` was cast through
94
- * `as unknown as RebaseCallContext` and nothing about the object was
95
- * type-checked at all.
96
- *
97
- * The guide no longer recommends `withAuth` — {@link data} is already
98
- * user-scoped on a user request, so the manual re-scoping it described was
99
- * answering a problem that did not exist. The field stays declared rather
100
- * than removed: it is on the runtime object, dropping it would break anyone
101
- * who found it, and a named optional is better than a silent extra.
102
- *
103
- * `withAuth` is not on {@link DataDriver} because not every engine supports
104
- * RLS scoping; it is narrowed here, and left optional so a driver without it
105
- * is a compile-time absence rather than a runtime surprise.
106
- */
107
- driver?: DataDriver & {
108
- withAuth?(user: { uid: string; roles?: string[] }): Promise<DataDriver>;
109
- };
110
-
111
- /**
112
- * Used storage implementation
113
- */
114
- storageSource: StorageSource;
115
-
116
- /**
117
- * Set by the backend when callbacks are executed on the server.
118
- */
119
- user?: USER;
120
- }
@@ -1,24 +0,0 @@
1
- import type { User } from "../users";
2
-
3
- /**
4
- * The read-only slice of authentication state that property resolution needs.
5
- *
6
- * `dynamicProps`, `conditions` and the JSON-Logic condition context all want the
7
- * same one thing: who is asking. They used to be handed the entire
8
- * {@link AuthController} — `signOut`, `googleLogin`, `authLoading`, thirty-odd
9
- * members of frontend machinery — which meant `properties.ts` named a frontend
10
- * controller, and so did `resolveProperty` in `@rebasepro/common`, which the
11
- * Postgres schema generator calls at build time. A backend generating DDL had to
12
- * satisfy a type with a login method in it.
13
- *
14
- * `User` already carries `roles` and `metadata`, so this is not a reduction in
15
- * what a dynamic property can decide on — only in what it has to be given.
16
- * An `AuthController` satisfies this structurally, so the frontend passes the
17
- * controller it already has.
18
- *
19
- * @group Hooks and utilities
20
- */
21
- export interface AuthState<USER extends User = User> {
22
- /** The signed-in user, or `null` when nobody is. */
23
- user: USER | null;
24
- }