@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.
@@ -29,7 +29,7 @@
29
29
  *
30
30
  * @group Models
31
31
  */
32
- export declare const ADMIN_COLLECTION_KEYS: readonly ["Actions", "additionalFields", "alwaysApplyDefaultValues", "components", "defaultEntityAction", "defaultFilter", "defaultSelectedView", "defaultSize", "defaultViewMode", "disableDefaultActions", "enabledViews", "entityActions", "entityViews", "exportable", "filterPresets", "fixedFilter", "formAutoSave", "formView", "group", "hideFromNavigation", "hideIdFromCollection", "hideIdFromForm", "icon", "includeJsonView", "inlineEditing", "kanban", "listProperties", "localChangesBackup", "openEntityMode", "orderProperty", "pagination", "previewProperties", "propertiesOrder", "selectionController", "selectionEnabled", "sideDialogWidth", "sort", "titleProperty"];
32
+ export declare const ADMIN_COLLECTION_KEYS: readonly ["Actions", "additionalFields", "alwaysApplyDefaultValues", "components", "defaultEntityAction", "defaultFilter", "defaultSelectedView", "defaultSize", "defaultViewMode", "disableDefaultActions", "enabledViews", "entityActions", "entityViews", "exportable", "filterPresets", "fixedFilter", "form", "formAutoSave", "formView", "group", "hideFromNavigation", "hideIdFromCollection", "hideIdFromForm", "icon", "includeJsonView", "inlineEditing", "kanban", "listProperties", "localChangesBackup", "openEntityMode", "orderProperty", "pagination", "previewProperties", "propertiesOrder", "selectionController", "selectionEnabled", "sideDialogWidth", "sort", "titleProperty"];
33
33
  /** A key of a collection's `admin` block. @group Models */
34
34
  export type AdminCollectionKey = typeof ADMIN_COLLECTION_KEYS[number];
35
35
  /**
@@ -49,6 +49,6 @@ export type AdminCollectionKey = typeof ADMIN_COLLECTION_KEYS[number];
49
49
  *
50
50
  * @group Models
51
51
  */
52
- export declare const ADMIN_PROPERTY_KEYS: readonly ["canAddElements", "clearable", "columnWidth", "customProps", "disabled", "expanded", "Field", "Filter", "filterOperators", "fixedFilter", "hideFromCollection", "includeEntityLink", "includeId", "markdown", "minimalistView", "multiline", "Preview", "previewAsTag", "previewProperties", "readOnly", "sortable", "spreadChildren", "urlPreview", "widget", "widthPercentage"];
52
+ export declare const ADMIN_PROPERTY_KEYS: readonly ["canAddElements", "clearable", "columnWidth", "customProps", "disabled", "expanded", "Field", "Filter", "filterOperators", "fixedFilter", "hideFromCollection", "includeEntityLink", "includeId", "markdown", "minimalistView", "multiline", "Preview", "previewAsTag", "previewProperties", "readOnly", "sortable", "span", "spreadChildren", "urlPreview", "widget"];
53
53
  /** A key of a property's `admin` block. @group Models */
54
54
  export type AdminPropertyKey = typeof ADMIN_PROPERTY_KEYS[number];
@@ -1,4 +1,5 @@
1
1
  import type { CollectionConfig, FilterValues, WhereFilterOp } from "./collections";
2
+ import type { LogicalCondition } from "../controllers/data";
2
3
  import type { AuthAdapter } from "./auth_adapter";
3
4
  import type { HistoryConfig } from "../controllers/client";
4
5
  import type { ChannelBusSetting } from "./channel_bus";
@@ -58,6 +59,13 @@ export interface SearchOptions<M extends Record<string, unknown> = Record<string
58
59
  */
59
60
  export interface CountOptions<M extends Record<string, unknown> = Record<string, unknown>> {
60
61
  filter?: FilterValues<Extract<keyof M, string>>;
62
+ /**
63
+ * An `or(...)`/`and(...)` group, alongside `filter`.
64
+ *
65
+ * Counted as well as fetched, or `total` describes a different set of rows
66
+ * from the one that was served — the same reason `filter` is here.
67
+ */
68
+ logical?: LogicalCondition;
61
69
  searchString?: string;
62
70
  databaseId?: string;
63
71
  }
@@ -611,6 +619,25 @@ export interface BackendBootstrapper {
611
619
  ensureCollectionSchema?(collections: unknown[], driverResult: InitializedDriver, log?: (message: string) => void): Promise<{
612
620
  applied: number;
613
621
  }>;
622
+ /**
623
+ * Apply the collections' row-level-security policies, additively and
624
+ * idempotently — the companion to {@link ensureCollectionSchema}.
625
+ *
626
+ * That method creates the tables; a table with RLS disabled and no policies
627
+ * is not servable, because authenticated requests run as a restricted role:
628
+ * a read with no `SELECT` policy returns nothing (a public collection
629
+ * answers 401) and a write with no `INSERT`/`UPDATE` policy is denied. The
630
+ * `db push` CLI applies these from the same collections, but it cannot reach
631
+ * a managed tenant's in-cluster database — the runtime, already connected,
632
+ * is the only thing that can.
633
+ *
634
+ * MUST be idempotent (re-run on every boot) and MUST NOT be destructive.
635
+ * Runs after auth initialization, because the generated policies call the
636
+ * `auth.*` helper functions and `CREATE POLICY` validates they exist.
637
+ */
638
+ ensureCollectionPolicies?(collections: unknown[], driverResult: InitializedDriver, log?: (message: string) => void): Promise<{
639
+ applied: number;
640
+ }>;
614
641
  /**
615
642
  * Initialize WebSocket server for realtime operations.
616
643
  */
@@ -2,4 +2,15 @@ export type ColorScheme = {
2
2
  color: string;
3
3
  text: string;
4
4
  };
5
- export type ColorKey = "blue" | "cyan" | "teal" | "green" | "yellow" | "orange" | "red" | "pink" | "purple" | "gray" | "indigo" | "violet" | "fuchsia" | "rose" | "emerald";
5
+ /**
6
+ * The hues a chip can take.
7
+ *
8
+ * Kept in step with `CHIP_HUES` in `@rebasepro/ui` by hand: config types cannot
9
+ * depend on the component library. A hue added there needs adding here too.
10
+ */
11
+ export type ColorHue = "blue" | "cyan" | "teal" | "green" | "yellow" | "orange" | "red" | "pink" | "purple" | "gray" | "indigo" | "violet" | "fuchsia" | "rose" | "emerald";
12
+ /**
13
+ * How light or saturated a chip is. A hue on its own means `Lighter`.
14
+ */
15
+ export type ColorTone = "Lighter" | "Light" | "Dark" | "Darker";
16
+ export type ColorKey = ColorHue | `${ColorHue}${ColorTone}`;
@@ -26,6 +26,37 @@ export interface CronJobDefinition {
26
26
  * considered timed-out. Default: 300 (5 min).
27
27
  */
28
28
  timeoutSeconds?: number;
29
+ /**
30
+ * How far back to look, on startup, for a slot that elapsed while no
31
+ * instance was holding a timer for it. Off by default.
32
+ *
33
+ * The scheduler drives jobs with in-process `setTimeout` and computes the
34
+ * next slot from *now* on every boot, so a slot only fires if some instance
35
+ * happened to be alive and ticking when it came round. That is not a
36
+ * scale-to-zero problem: a platform that recycles containers — Cloud Run
37
+ * rotating an instance under `--min-instances 1`, a rolling deploy, a crash
38
+ * loop — drops any slot that falls inside the changeover, and the
39
+ * replacement schedules the slot *after* it. The run is skipped in silence.
40
+ *
41
+ * Set this to a window comfortably wider than a restart (a few minutes for
42
+ * a frequent job; an hour or more for a daily one) and startup will run a
43
+ * slot it finds unclaimed inside that window.
44
+ *
45
+ * Two deliberate limits:
46
+ *
47
+ * - **Only the most recent missed slot runs.** Booting after a six-hour
48
+ * outage catches an hourly job up once, not six times. Catch-up exists to
49
+ * stop a run going missing, not to replay history.
50
+ * - **A claims-capable store is required.** Catch-up is skipped entirely
51
+ * when the store has no `tryClaimRun` (or no store is attached), because
52
+ * the claim is the only thing that distinguishes "this slot never ran"
53
+ * from "this slot already ran on the instance I am replacing". Without
54
+ * it, an instance recycled every 30 minutes would re-run the same hourly
55
+ * job every time it booted.
56
+ *
57
+ * @example catchUpWindowSeconds: 3600 // daily job: tolerate an hour of downtime
58
+ */
59
+ catchUpWindowSeconds?: number;
29
60
  /**
30
61
  * The handler function executed on each tick.
31
62
  * Receives a context object with the data driver and logger.
@@ -65,8 +65,42 @@ export interface DatabaseAdapter {
65
65
  } | undefined>;
66
66
  /**
67
67
  * Initialize WebSocket server for realtime operations.
68
+ *
69
+ * `adapter` is the configured AuthAdapter, if any. It is what makes the
70
+ * socket secure by default: an implementation that receives one requires
71
+ * authentication regardless of whether a local `jwtSecret` exists. The
72
+ * parameter was missing from this signature while the caller in `init.ts`
73
+ * already passed it, so it was dropped at every adapter that routed through
74
+ * here — turning an adapter-authenticated server's socket into one that
75
+ * accepted every client as already authenticated.
76
+ */
77
+ initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: DataDriver, config?: unknown, adapter?: import("./auth_adapter").AuthAdapter): Promise<void> | void;
78
+ /**
79
+ * Bring the database's collection tables up to date, additively — the boot
80
+ * companion to `db push`. See `BackendBootstrapper.ensureCollectionSchema`
81
+ * for the contract (create-only; never drop, narrow, or rewrite).
82
+ *
83
+ * Optional, and MUST be forwarded by any wrapper that turns this adapter
84
+ * into a `BackendBootstrapper`: the runtime calls it through the bootstrapper
85
+ * at boot, and a wrapper that silently omits it leaves a managed tenant
86
+ * 500ing every data route with no create step ever having run.
87
+ */
88
+ ensureCollectionSchema?(collections: unknown[], driverResult: InitializedDriver, log?: (message: string) => void): Promise<{
89
+ applied: number;
90
+ }>;
91
+ /**
92
+ * Apply the collections' RLS policies (ENABLE ROW LEVEL SECURITY + the
93
+ * `securityRules` compiled to `CREATE POLICY`) — the boot companion to the
94
+ * policy half of `db push`. Idempotent; see
95
+ * `BackendBootstrapper.ensureCollectionPolicies`.
96
+ *
97
+ * Same forwarding requirement as `ensureCollectionSchema`: without the
98
+ * policies, tables exist but every user-context read is denied (a public
99
+ * collection answers 401).
68
100
  */
69
- initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: DataDriver, config?: unknown): Promise<void> | void;
101
+ ensureCollectionPolicies?(collections: unknown[], driverResult: InitializedDriver, log?: (message: string) => void): Promise<{
102
+ applied: number;
103
+ }>;
70
104
  /**
71
105
  * Return admin capabilities for this database (SQL editor, schema browser, branching).
72
106
  */
@@ -29,12 +29,44 @@ export type PolicyExpression = TruePolicyExpression | FalsePolicyExpression | An
29
29
  *
30
30
  * The consequence for policy authors is that **`auth.uid() IS NOT NULL` is a
31
31
  * tautology on the user path** — it is true for anonymous visitors too. Use
32
- * {@link policy.authenticated} (or `auth.uid() <> 'anonymous'`) to mean "signed
33
- * in", and {@link policy.serverContext} to mean "the trusted server context".
32
+ * {@link policy.authenticated} to mean "signed in", and
33
+ * {@link policy.serverContext} to mean "the trusted server context". Do not
34
+ * hand-write the comparison: see {@link ANONYMOUS_USER_IDS} for why one
35
+ * literal is not enough.
34
36
  *
35
37
  * @group Models
36
38
  */
37
39
  export declare const ANONYMOUS_USER_ID = "anonymous";
40
+ /**
41
+ * Every uid that has ever meant "nobody is signed in" — newest first.
42
+ *
43
+ * There are two because there were two. The types, the policy compiler, the
44
+ * JavaScript evaluator and the linter were all built on
45
+ * {@link ANONYMOUS_USER_ID}, while the request path scoped unauthenticated
46
+ * callers as `'anon'` — so `policy.authenticated()`, which compiled to
47
+ * `auth.uid() <> 'anonymous'`, was *true* for an anonymous visitor. The
48
+ * sanctioned way to write "signed in" granted to everyone, and the linter
49
+ * flagged the spelling that actually worked as a foreign convention.
50
+ *
51
+ * The request path now reports {@link ANONYMOUS_USER_ID}. `'anon'` stays here
52
+ * because policies outlive the server that generated them: a database still
53
+ * holding policies from before the fix, or a project whose server has not been
54
+ * upgraded yet, must not become a grant in either direction. Compile against
55
+ * this list, not against a single literal.
56
+ *
57
+ * No real user id is ever one of these, so a match is always "not signed in".
58
+ *
59
+ * @group Models
60
+ */
61
+ export declare const ANONYMOUS_USER_IDS: readonly string[];
62
+ /**
63
+ * Whether a uid stands for "no one is signed in", in any spelling rebase has
64
+ * used. `null`/`undefined` is the trusted server context, not an anonymous
65
+ * caller, and is therefore **not** anonymous — see {@link ANONYMOUS_USER_ID}.
66
+ *
67
+ * @group Models
68
+ */
69
+ export declare function isAnonymousUid(uid: string | null | undefined): boolean;
38
70
  /** Always allows. Compiles to `true`. @group Models */
39
71
  export interface TruePolicyExpression {
40
72
  kind: "true";
@@ -192,6 +192,21 @@ export interface RebaseProjectManifest {
192
192
  * common project and must not be required to say so.
193
193
  */
194
194
  storage?: Record<string, RebaseStorageSourceConfig>;
195
+ /**
196
+ * Repository-wide opt-out from anonymous CLI usage sharing.
197
+ *
198
+ * **Only `false` does anything.** It suppresses sharing for everyone who
199
+ * clones this repository, overriding each developer's own opt-in — an
200
+ * organisation setting policy for work done on its behalf, the same shape
201
+ * as a committed `.npmrc`.
202
+ *
203
+ * `true` is deliberately ignored, and the CLI says so rather than obeying
204
+ * quietly. This file is committed, so a `true` here would be one developer
205
+ * answering a privacy question for every colleague who later clones the
206
+ * repo — consent by proxy, which is the exact thing opt-in exists to
207
+ * prevent. Individuals opt in with `rebase telemetry enable`.
208
+ */
209
+ telemetry?: boolean;
195
210
  }
196
211
  /**
197
212
  * The per-checkout project link.
@@ -73,6 +73,15 @@ export interface HasOneRelation extends RelationBase {
73
73
  * Defaults to `<thisCollection>_id`.
74
74
  */
75
75
  foreignKeyOnTarget?: string;
76
+ /**
77
+ * Column on **this** collection's table whose value `foreignKeyOnTarget`
78
+ * holds. Defaults to this collection's primary key.
79
+ *
80
+ * Set it when the two sides are joined on a natural key rather than on the
81
+ * row id — an external identity id, a SKU, a tenant slug. See
82
+ * {@link HasManyRelation.sourceKey}, which this mirrors.
83
+ */
84
+ sourceKey?: string;
76
85
  }
77
86
  /**
78
87
  * The target holds the foreign key, and many target rows point back. The
@@ -90,6 +99,30 @@ export interface HasManyRelation extends RelationBase {
90
99
  * Defaults to `<thisCollection>_id`.
91
100
  */
92
101
  foreignKeyOnTarget?: string;
102
+ /**
103
+ * Column on **this** collection's table whose value `foreignKeyOnTarget`
104
+ * holds. Defaults to this collection's primary key.
105
+ *
106
+ * The mirror of `localKey` on {@link BelongsToRelation}: that one names the
107
+ * column this side reads from, this one names the column the other side
108
+ * points at. Without it the pair can only be joined on the row id, which
109
+ * makes a natural-key link — `auth_user_id ↔ auth_user_id`, a SKU, a tenant
110
+ * slug — inexpressible as `hasMany`, and it has to drop to the read-only
111
+ * `via`.
112
+ *
113
+ * The column must be unique: the link addresses one source row per value,
114
+ * and Postgres will not accept a foreign key against a non-unique column.
115
+ *
116
+ * ```ts
117
+ * applications: {
118
+ * kind: "hasMany",
119
+ * target: () => talentApplications,
120
+ * sourceKey: "auth_user_id",
121
+ * foreignKeyOnTarget: "auth_user_id"
122
+ * }
123
+ * ```
124
+ */
125
+ sourceKey?: string;
93
126
  }
94
127
  /**
95
128
  * Both sides hold many, through a junction table. The target rows are shared,
@@ -223,6 +256,8 @@ export interface ResolvedHasOne extends ResolvedRelationBase {
223
256
  shared: false;
224
257
  /** Column on the target's table. */
225
258
  foreignKeyOnTarget: string;
259
+ /** @see ResolvedHasMany.sourceKey */
260
+ sourceKey?: string;
226
261
  }
227
262
  /** @group Models */
228
263
  export interface ResolvedHasMany extends ResolvedRelationBase {
@@ -232,6 +267,22 @@ export interface ResolvedHasMany extends ResolvedRelationBase {
232
267
  shared: false;
233
268
  /** Column on the target's table. */
234
269
  foreignKeyOnTarget: string;
270
+ /**
271
+ * Column on the source's table that `foreignKeyOnTarget` points at, or
272
+ * `undefined` for the source's primary key.
273
+ *
274
+ * The one optional field on a resolved relation, and deliberately so. Every
275
+ * other default is filled in here because it can be: a table name and a
276
+ * column name are derivable from the relation and its two endpoints alone.
277
+ * The primary key is not — this driver resolves it from `isId`, then the
278
+ * Drizzle schema, then a column named `id`, and the middle tier does not
279
+ * exist at resolution time.
280
+ *
281
+ * So `undefined` is a sentinel with exactly one meaning, not a field a
282
+ * consumer is invited to guess at. Read it through `sourceKeyField()`,
283
+ * which is the only place that turns it into a column name.
284
+ */
285
+ sourceKey?: string;
235
286
  }
236
287
  /** @group Models */
237
288
  export interface ResolvedManyToMany extends ResolvedRelationBase {
@@ -2,8 +2,8 @@
2
2
  * The canonical representation of an authenticated user in the Rebase ecosystem.
3
3
  *
4
4
  * Used by {@link AuthController}, collections, callbacks, and both the
5
- * `@rebasepro/client` and `@rebasepro/app` packages. All other user types
6
- * (`RebaseUser`, `UserInfo`) are deprecated aliases of this type.
5
+ * `@rebasepro/client` and `@rebasepro/app` packages. It is the only user type
6
+ * those packages export — the `RebaseUser` / `UserInfo` aliases are gone.
7
7
  *
8
8
  * **Backend-managed fields** (`uid`, `email`, `roles`, `metadata`, `createdAt`)
9
9
  * are populated by the server. **Client-visible fields** (`displayName`,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/types",
3
3
  "type": "module",
4
- "version": "0.12.1-canary.gf5f1d39",
4
+ "version": "0.13.0",
5
5
  "description": "Rebase type definitions — shared interfaces and controller types",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -39,14 +39,14 @@
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/jest": "^30.0.0",
42
- "@types/node": "^25.9.3",
42
+ "@types/node": "^26.1.2",
43
43
  "@types/object-hash": "^3.0.6",
44
44
  "@types/react-measure": "^2.0.12",
45
45
  "hono": "^4.12.27",
46
46
  "jest": "^30.4.2",
47
- "ts-jest": "^29.4.11",
47
+ "ts-jest": "^29.4.12",
48
48
  "typescript": "^6.0.3",
49
- "vite": "^8.0.16"
49
+ "vite": "^8.1.5"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "hono": "^4.12.27"
@@ -94,6 +94,21 @@ export interface AuthClient {
94
94
  * Manually refresh the session token
95
95
  */
96
96
  refreshSession(): Promise<RebaseSession>;
97
+
98
+ /**
99
+ * Whether a session could exist that this client has not loaded yet.
100
+ *
101
+ * `false` means the only way this client can hold a session is an explicit
102
+ * sign-in during this page's lifetime: it neither persists sessions nor
103
+ * carries an httpOnly auth cookie, so there is nothing on disk or in the
104
+ * browser to restore from. A caller that would otherwise probe the server
105
+ * — `getUser()` on mount, say — can skip it, because the answer is already
106
+ * known and the request can only ever fail.
107
+ *
108
+ * Optional so that alternative {@link AuthClient} implementations need not
109
+ * supply it; treat a missing implementation as "unknown, go ahead and ask".
110
+ */
111
+ canRestoreSession?: () => boolean;
97
112
  }
98
113
 
99
114
  // ─── Admin API ───────────────────────────────────────────────────────────────
@@ -321,6 +336,17 @@ export interface RebaseClient<DB = unknown> {
321
336
  /** Base HTTP URL of the backend server */
322
337
  baseUrl?: string;
323
338
 
339
+ /**
340
+ * The path every API route is mounted under, appended to {@link baseUrl}.
341
+ *
342
+ * `"/api"` unless the backend was configured with a different `basePath`
343
+ * and the client told to match. Exposed because code that builds a URL by
344
+ * hand — rather than going through the client's own methods — otherwise has
345
+ * to guess, and guessing `/api` is wrong for exactly the projects that set
346
+ * the option.
347
+ */
348
+ apiPath?: string;
349
+
324
350
  /** WebSocket client for realtime subscriptions */
325
351
  ws?: RebaseWebSocket;
326
352
 
@@ -356,20 +382,14 @@ export interface RebaseClient<DB = unknown> {
356
382
  * the admin-scoped {@link dataAsAdmin} accessor, raw {@link sql}, and the
357
383
  * {@link email} service are all present (non-optional).
358
384
  *
359
- * **Trust levels.** Both {@link dataAsAdmin} and the deprecated {@link data}
360
- * alias point at the same admin-scoped, **RLS-bypassing** driver. Prefer
361
- * `dataAsAdmin` so the privilege is explicit at every call site. For user-scoped
362
- * queries inside a request handler use the request-scoped driver
363
- * (`c.var.driver`) instead — never `dataAsAdmin`/`data`.
385
+ * **Trust levels.** {@link dataAsAdmin} is the admin-scoped, **RLS-bypassing**
386
+ * driver, and it is the only name for it here — the `data` alias that used to
387
+ * sit beside it is deliberately `Omit`ted from {@link RebaseClient} so the
388
+ * privilege has to be spelled out at every call site. For user-scoped queries
389
+ * inside a request handler use the request-scoped driver (`c.var.driver`)
390
+ * instead — never `dataAsAdmin`.
364
391
  */
365
- export interface RebaseServerClient<DB = unknown> extends RebaseClient<DB> {
366
- /**
367
- * @deprecated On the server, prefer {@link dataAsAdmin} for admin scope or
368
- * the request-scoped driver (`c.var.driver`) for user scope. This alias
369
- * points at the same admin-scoped, RLS-bypassing accessor as `dataAsAdmin`.
370
- */
371
- data: RebaseSdkData<DB>;
372
-
392
+ export interface RebaseServerClient<DB = unknown> extends Omit<RebaseClient<DB>, "data"> {
373
393
  /**
374
394
  * Admin-scoped, **RLS-bypassing** data accessor. Always present server-side.
375
395
  * See {@link RebaseClient.dataAsAdmin} for the full safety contract.
@@ -439,6 +459,17 @@ export interface RebaseBrowserClient<DB = unknown> {
439
459
  /** Base HTTP URL of the backend server */
440
460
  baseUrl?: string;
441
461
 
462
+ /**
463
+ * The path every API route is mounted under, appended to {@link baseUrl}.
464
+ *
465
+ * `"/api"` unless the backend was configured with a different `basePath`
466
+ * and the client told to match. Exposed because code that builds a URL by
467
+ * hand — rather than going through the client's own methods — otherwise has
468
+ * to guess, and guessing `/api` is wrong for exactly the projects that set
469
+ * the option.
470
+ */
471
+ apiPath?: string;
472
+
442
473
  /** WebSocket client for realtime subscriptions */
443
474
  ws?: RebaseWebSocket;
444
475
 
@@ -2,6 +2,7 @@ import type { CollectionRegistryController } from "./collection_registry";
2
2
  import type { EntityStatus, EntityValues } from "../types/entities";
3
3
  import type { CollectionConfig, FilterValues } from "../types/collections";
4
4
  import type { RebaseCallContext } from "../call_context";
5
+ import type { LogicalCondition } from "./data";
5
6
 
6
7
 
7
8
  /**
@@ -103,6 +104,14 @@ export interface FetchCollectionProps<M extends Record<string, unknown> = Record
103
104
  path: string;
104
105
  collection?: CollectionConfig<M>;
105
106
  filter?: FilterValues<Extract<keyof M, string>>,
107
+ /**
108
+ * An `or(...)`/`and(...)` group, applied alongside `filter`.
109
+ *
110
+ * The REST layer parsed `?or=` into this and then had nowhere to put it, so
111
+ * the group was dropped and the read ran unfiltered — returning every row
112
+ * the caller's policies allowed rather than the ones they asked for.
113
+ */
114
+ logical?: LogicalCondition;
106
115
  limit?: number;
107
116
  offset?: number;
108
117
  startAfter?: unknown;
@@ -366,6 +375,8 @@ export interface RestFetchService {
366
375
  collectionPath: string,
367
376
  options?: {
368
377
  filter?: FilterValues<string>;
378
+ /** An `or(...)`/`and(...)` group, applied alongside `filter`. */
379
+ logical?: LogicalCondition;
369
380
  orderBy?: string;
370
381
  order?: "desc" | "asc";
371
382
  limit?: number;
@@ -47,6 +47,7 @@ export const ADMIN_COLLECTION_KEYS = [
47
47
  "exportable",
48
48
  "filterPresets",
49
49
  "fixedFilter",
50
+ "form",
50
51
  "formAutoSave",
51
52
  "formView",
52
53
  "group",
@@ -113,10 +114,10 @@ export const ADMIN_PROPERTY_KEYS = [
113
114
  "previewProperties",
114
115
  "readOnly",
115
116
  "sortable",
117
+ "span",
116
118
  "spreadChildren",
117
119
  "urlPreview",
118
120
  "widget",
119
- "widthPercentage"
120
121
  ] as const;
121
122
 
122
123
  /** A key of a property's `admin` block. @group Models */
@@ -1,4 +1,5 @@
1
1
  import type { CollectionConfig, FilterValues, WhereFilterOp } from "./collections";
2
+ import type { LogicalCondition } from "../controllers/data";
2
3
  import type { AuthAdapter } from "./auth_adapter";
3
4
  import type { HistoryConfig } from "../controllers/client";
4
5
  import type { ChannelBusSetting } from "./channel_bus";
@@ -73,6 +74,13 @@ export interface SearchOptions<M extends Record<string, unknown> = Record<string
73
74
  */
74
75
  export interface CountOptions<M extends Record<string, unknown> = Record<string, unknown>> {
75
76
  filter?: FilterValues<Extract<keyof M, string>>;
77
+ /**
78
+ * An `or(...)`/`and(...)` group, alongside `filter`.
79
+ *
80
+ * Counted as well as fetched, or `total` describes a different set of rows
81
+ * from the one that was served — the same reason `filter` is here.
82
+ */
83
+ logical?: LogicalCondition;
76
84
  searchString?: string;
77
85
  databaseId?: string;
78
86
  }
@@ -798,6 +806,28 @@ export interface BackendBootstrapper {
798
806
  log?: (message: string) => void
799
807
  ): Promise<{ applied: number }>;
800
808
 
809
+ /**
810
+ * Apply the collections' row-level-security policies, additively and
811
+ * idempotently — the companion to {@link ensureCollectionSchema}.
812
+ *
813
+ * That method creates the tables; a table with RLS disabled and no policies
814
+ * is not servable, because authenticated requests run as a restricted role:
815
+ * a read with no `SELECT` policy returns nothing (a public collection
816
+ * answers 401) and a write with no `INSERT`/`UPDATE` policy is denied. The
817
+ * `db push` CLI applies these from the same collections, but it cannot reach
818
+ * a managed tenant's in-cluster database — the runtime, already connected,
819
+ * is the only thing that can.
820
+ *
821
+ * MUST be idempotent (re-run on every boot) and MUST NOT be destructive.
822
+ * Runs after auth initialization, because the generated policies call the
823
+ * `auth.*` helper functions and `CREATE POLICY` validates they exist.
824
+ */
825
+ ensureCollectionPolicies?(
826
+ collections: unknown[],
827
+ driverResult: InitializedDriver,
828
+ log?: (message: string) => void
829
+ ): Promise<{ applied: number }>;
830
+
801
831
  /**
802
832
  * Initialize WebSocket server for realtime operations.
803
833
  */
@@ -3,7 +3,13 @@ export type ColorScheme = {
3
3
  text: string;
4
4
  }
5
5
 
6
- export type ColorKey =
6
+ /**
7
+ * The hues a chip can take.
8
+ *
9
+ * Kept in step with `CHIP_HUES` in `@rebasepro/ui` by hand: config types cannot
10
+ * depend on the component library. A hue added there needs adding here too.
11
+ */
12
+ export type ColorHue =
7
13
  | "blue"
8
14
  | "cyan"
9
15
  | "teal"
@@ -19,3 +25,10 @@ export type ColorKey =
19
25
  | "fuchsia"
20
26
  | "rose"
21
27
  | "emerald";
28
+
29
+ /**
30
+ * How light or saturated a chip is. A hue on its own means `Lighter`.
31
+ */
32
+ export type ColorTone = "Lighter" | "Light" | "Dark" | "Darker";
33
+
34
+ export type ColorKey = ColorHue | `${ColorHue}${ColorTone}`;
package/src/types/cron.ts CHANGED
@@ -38,6 +38,38 @@ export interface CronJobDefinition {
38
38
  */
39
39
  timeoutSeconds?: number;
40
40
 
41
+ /**
42
+ * How far back to look, on startup, for a slot that elapsed while no
43
+ * instance was holding a timer for it. Off by default.
44
+ *
45
+ * The scheduler drives jobs with in-process `setTimeout` and computes the
46
+ * next slot from *now* on every boot, so a slot only fires if some instance
47
+ * happened to be alive and ticking when it came round. That is not a
48
+ * scale-to-zero problem: a platform that recycles containers — Cloud Run
49
+ * rotating an instance under `--min-instances 1`, a rolling deploy, a crash
50
+ * loop — drops any slot that falls inside the changeover, and the
51
+ * replacement schedules the slot *after* it. The run is skipped in silence.
52
+ *
53
+ * Set this to a window comfortably wider than a restart (a few minutes for
54
+ * a frequent job; an hour or more for a daily one) and startup will run a
55
+ * slot it finds unclaimed inside that window.
56
+ *
57
+ * Two deliberate limits:
58
+ *
59
+ * - **Only the most recent missed slot runs.** Booting after a six-hour
60
+ * outage catches an hourly job up once, not six times. Catch-up exists to
61
+ * stop a run going missing, not to replay history.
62
+ * - **A claims-capable store is required.** Catch-up is skipped entirely
63
+ * when the store has no `tryClaimRun` (or no store is attached), because
64
+ * the claim is the only thing that distinguishes "this slot never ran"
65
+ * from "this slot already ran on the instance I am replacing". Without
66
+ * it, an instance recycled every 30 minutes would re-run the same hourly
67
+ * job every time it booted.
68
+ *
69
+ * @example catchUpWindowSeconds: 3600 // daily job: tolerate an hour of downtime
70
+ */
71
+ catchUpWindowSeconds?: number;
72
+
41
73
  /**
42
74
  * The handler function executed on each tick.
43
75
  * Receives a context object with the data driver and logger.