@rebasepro/types 0.12.1-canary.gf5f1d39 → 0.13.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.
@@ -82,14 +82,55 @@ export interface DatabaseAdapter {
82
82
 
83
83
  /**
84
84
  * Initialize WebSocket server for realtime operations.
85
+ *
86
+ * `adapter` is the configured AuthAdapter, if any. It is what makes the
87
+ * socket secure by default: an implementation that receives one requires
88
+ * authentication regardless of whether a local `jwtSecret` exists. The
89
+ * parameter was missing from this signature while the caller in `init.ts`
90
+ * already passed it, so it was dropped at every adapter that routed through
91
+ * here — turning an adapter-authenticated server's socket into one that
92
+ * accepted every client as already authenticated.
85
93
  */
86
94
  initializeWebsockets?(
87
95
  server: unknown,
88
96
  realtimeService: RealtimeProvider,
89
97
  driver: DataDriver,
90
98
  config?: unknown,
99
+ adapter?: import("./auth_adapter").AuthAdapter,
91
100
  ): Promise<void> | void;
92
101
 
102
+ /**
103
+ * Bring the database's collection tables up to date, additively — the boot
104
+ * companion to `db push`. See `BackendBootstrapper.ensureCollectionSchema`
105
+ * for the contract (create-only; never drop, narrow, or rewrite).
106
+ *
107
+ * Optional, and MUST be forwarded by any wrapper that turns this adapter
108
+ * into a `BackendBootstrapper`: the runtime calls it through the bootstrapper
109
+ * at boot, and a wrapper that silently omits it leaves a managed tenant
110
+ * 500ing every data route with no create step ever having run.
111
+ */
112
+ ensureCollectionSchema?(
113
+ collections: unknown[],
114
+ driverResult: InitializedDriver,
115
+ log?: (message: string) => void,
116
+ ): Promise<{ applied: number }>;
117
+
118
+ /**
119
+ * Apply the collections' RLS policies (ENABLE ROW LEVEL SECURITY + the
120
+ * `securityRules` compiled to `CREATE POLICY`) — the boot companion to the
121
+ * policy half of `db push`. Idempotent; see
122
+ * `BackendBootstrapper.ensureCollectionPolicies`.
123
+ *
124
+ * Same forwarding requirement as `ensureCollectionSchema`: without the
125
+ * policies, tables exist but every user-context read is denied (a public
126
+ * collection answers 401).
127
+ */
128
+ ensureCollectionPolicies?(
129
+ collections: unknown[],
130
+ driverResult: InitializedDriver,
131
+ log?: (message: string) => void,
132
+ ): Promise<{ applied: number }>;
133
+
93
134
  /**
94
135
  * Return admin capabilities for this database (SQL editor, schema browser, branching).
95
136
  */
@@ -42,13 +42,49 @@ export type PolicyExpression =
42
42
  *
43
43
  * The consequence for policy authors is that **`auth.uid() IS NOT NULL` is a
44
44
  * tautology on the user path** — it is true for anonymous visitors too. Use
45
- * {@link policy.authenticated} (or `auth.uid() <> 'anonymous'`) to mean "signed
46
- * in", and {@link policy.serverContext} to mean "the trusted server context".
45
+ * {@link policy.authenticated} to mean "signed in", and
46
+ * {@link policy.serverContext} to mean "the trusted server context". Do not
47
+ * hand-write the comparison: see {@link ANONYMOUS_USER_IDS} for why one
48
+ * literal is not enough.
47
49
  *
48
50
  * @group Models
49
51
  */
50
52
  export const ANONYMOUS_USER_ID = "anonymous";
51
53
 
54
+ /**
55
+ * Every uid that has ever meant "nobody is signed in" — newest first.
56
+ *
57
+ * There are two because there were two. The types, the policy compiler, the
58
+ * JavaScript evaluator and the linter were all built on
59
+ * {@link ANONYMOUS_USER_ID}, while the request path scoped unauthenticated
60
+ * callers as `'anon'` — so `policy.authenticated()`, which compiled to
61
+ * `auth.uid() <> 'anonymous'`, was *true* for an anonymous visitor. The
62
+ * sanctioned way to write "signed in" granted to everyone, and the linter
63
+ * flagged the spelling that actually worked as a foreign convention.
64
+ *
65
+ * The request path now reports {@link ANONYMOUS_USER_ID}. `'anon'` stays here
66
+ * because policies outlive the server that generated them: a database still
67
+ * holding policies from before the fix, or a project whose server has not been
68
+ * upgraded yet, must not become a grant in either direction. Compile against
69
+ * this list, not against a single literal.
70
+ *
71
+ * No real user id is ever one of these, so a match is always "not signed in".
72
+ *
73
+ * @group Models
74
+ */
75
+ export const ANONYMOUS_USER_IDS: readonly string[] = [ANONYMOUS_USER_ID, "anon"];
76
+
77
+ /**
78
+ * Whether a uid stands for "no one is signed in", in any spelling rebase has
79
+ * used. `null`/`undefined` is the trusted server context, not an anonymous
80
+ * caller, and is therefore **not** anonymous — see {@link ANONYMOUS_USER_ID}.
81
+ *
82
+ * @group Models
83
+ */
84
+ export function isAnonymousUid(uid: string | null | undefined): boolean {
85
+ return typeof uid === "string" && ANONYMOUS_USER_IDS.includes(uid);
86
+ }
87
+
52
88
  /** Always allows. Compiles to `true`. @group Models */
53
89
  export interface TruePolicyExpression {
54
90
  kind: "true";
@@ -200,6 +200,21 @@ export interface RebaseProjectManifest {
200
200
  * common project and must not be required to say so.
201
201
  */
202
202
  storage?: Record<string, RebaseStorageSourceConfig>;
203
+ /**
204
+ * Repository-wide opt-out from anonymous CLI usage sharing.
205
+ *
206
+ * **Only `false` does anything.** It suppresses sharing for everyone who
207
+ * clones this repository, overriding each developer's own opt-in — an
208
+ * organisation setting policy for work done on its behalf, the same shape
209
+ * as a committed `.npmrc`.
210
+ *
211
+ * `true` is deliberately ignored, and the CLI says so rather than obeying
212
+ * quietly. This file is committed, so a `true` here would be one developer
213
+ * answering a privacy question for every colleague who later clones the
214
+ * repo — consent by proxy, which is the exact thing opt-in exists to
215
+ * prevent. Individuals opt in with `rebase telemetry enable`.
216
+ */
217
+ telemetry?: boolean;
203
218
  }
204
219
 
205
220
  /**
@@ -82,6 +82,15 @@ export interface HasOneRelation extends RelationBase {
82
82
  * Defaults to `<thisCollection>_id`.
83
83
  */
84
84
  foreignKeyOnTarget?: string;
85
+ /**
86
+ * Column on **this** collection's table whose value `foreignKeyOnTarget`
87
+ * holds. Defaults to this collection's primary key.
88
+ *
89
+ * Set it when the two sides are joined on a natural key rather than on the
90
+ * row id — an external identity id, a SKU, a tenant slug. See
91
+ * {@link HasManyRelation.sourceKey}, which this mirrors.
92
+ */
93
+ sourceKey?: string;
85
94
  }
86
95
 
87
96
  /**
@@ -100,6 +109,30 @@ export interface HasManyRelation extends RelationBase {
100
109
  * Defaults to `<thisCollection>_id`.
101
110
  */
102
111
  foreignKeyOnTarget?: string;
112
+ /**
113
+ * Column on **this** collection's table whose value `foreignKeyOnTarget`
114
+ * holds. Defaults to this collection's primary key.
115
+ *
116
+ * The mirror of `localKey` on {@link BelongsToRelation}: that one names the
117
+ * column this side reads from, this one names the column the other side
118
+ * points at. Without it the pair can only be joined on the row id, which
119
+ * makes a natural-key link — `auth_user_id ↔ auth_user_id`, a SKU, a tenant
120
+ * slug — inexpressible as `hasMany`, and it has to drop to the read-only
121
+ * `via`.
122
+ *
123
+ * The column must be unique: the link addresses one source row per value,
124
+ * and Postgres will not accept a foreign key against a non-unique column.
125
+ *
126
+ * ```ts
127
+ * applications: {
128
+ * kind: "hasMany",
129
+ * target: () => talentApplications,
130
+ * sourceKey: "auth_user_id",
131
+ * foreignKeyOnTarget: "auth_user_id"
132
+ * }
133
+ * ```
134
+ */
135
+ sourceKey?: string;
103
136
  }
104
137
 
105
138
  /**
@@ -248,6 +281,8 @@ export interface ResolvedHasOne extends ResolvedRelationBase {
248
281
  shared: false;
249
282
  /** Column on the target's table. */
250
283
  foreignKeyOnTarget: string;
284
+ /** @see ResolvedHasMany.sourceKey */
285
+ sourceKey?: string;
251
286
  }
252
287
 
253
288
  /** @group Models */
@@ -258,6 +293,22 @@ export interface ResolvedHasMany extends ResolvedRelationBase {
258
293
  shared: false;
259
294
  /** Column on the target's table. */
260
295
  foreignKeyOnTarget: string;
296
+ /**
297
+ * Column on the source's table that `foreignKeyOnTarget` points at, or
298
+ * `undefined` for the source's primary key.
299
+ *
300
+ * The one optional field on a resolved relation, and deliberately so. Every
301
+ * other default is filled in here because it can be: a table name and a
302
+ * column name are derivable from the relation and its two endpoints alone.
303
+ * The primary key is not — this driver resolves it from `isId`, then the
304
+ * Drizzle schema, then a column named `id`, and the middle tier does not
305
+ * exist at resolution time.
306
+ *
307
+ * So `undefined` is a sentinel with exactly one meaning, not a field a
308
+ * consumer is invited to guess at. Read it through `sourceKeyField()`,
309
+ * which is the only place that turns it into a column name.
310
+ */
311
+ sourceKey?: string;
261
312
  }
262
313
 
263
314
  /** @group Models */
package/src/users/user.ts CHANGED
@@ -3,8 +3,8 @@
3
3
  * The canonical representation of an authenticated user in the Rebase ecosystem.
4
4
  *
5
5
  * Used by {@link AuthController}, collections, callbacks, and both the
6
- * `@rebasepro/client` and `@rebasepro/app` packages. All other user types
7
- * (`RebaseUser`, `UserInfo`) are deprecated aliases of this type.
6
+ * `@rebasepro/client` and `@rebasepro/app` packages. It is the only user type
7
+ * those packages export — the `RebaseUser` / `UserInfo` aliases are gone.
8
8
  *
9
9
  * **Backend-managed fields** (`uid`, `email`, `roles`, `metadata`, `createdAt`)
10
10
  * are populated by the server. **Client-visible fields** (`displayName`,