@rebasepro/types 0.12.0 → 0.12.1-canary.g009ed95

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.
@@ -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";
@@ -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`,