@rebasepro/types 0.10.0 → 0.10.1-canary.0a881d4

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 (96) hide show
  1. package/dist/call_context.d.ts +51 -0
  2. package/dist/controllers/auth_state.d.ts +23 -0
  3. package/dist/controllers/client.d.ts +3 -1
  4. package/dist/controllers/collection_registry.d.ts +5 -1
  5. package/dist/controllers/data_driver.d.ts +31 -2
  6. package/dist/controllers/index.d.ts +1 -10
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.es.js +374 -1
  9. package/dist/index.es.js.map +1 -1
  10. package/dist/types/admin_block.d.ts +34 -0
  11. package/dist/types/backend.d.ts +30 -1
  12. package/dist/types/channel_bus.d.ts +192 -0
  13. package/dist/types/collection_contract.d.ts +43 -0
  14. package/dist/types/collections.d.ts +17 -789
  15. package/dist/types/component_ref.d.ts +39 -6
  16. package/dist/types/entity_callbacks.d.ts +1 -1
  17. package/dist/types/index.d.ts +7 -15
  18. package/dist/types/project_manifest.d.ts +336 -0
  19. package/dist/types/properties.d.ts +12 -157
  20. package/dist/types/schema_version.d.ts +18 -0
  21. package/dist/types/security_rules.d.ts +320 -0
  22. package/dist/types/storage_authorize.d.ts +74 -0
  23. package/package.json +3 -6
  24. package/src/call_context.ts +56 -0
  25. package/src/controllers/auth_state.ts +24 -0
  26. package/src/controllers/client.ts +3 -2
  27. package/src/controllers/collection_registry.ts +5 -1
  28. package/src/controllers/data_driver.ts +58 -2
  29. package/src/controllers/index.ts +1 -10
  30. package/src/index.ts +1 -1
  31. package/src/types/admin_block.ts +75 -0
  32. package/src/types/backend.ts +33 -1
  33. package/src/types/channel_bus.ts +202 -0
  34. package/src/types/collection_contract.ts +278 -0
  35. package/src/types/collections.ts +25 -848
  36. package/src/types/component_ref.ts +40 -6
  37. package/src/types/entity_callbacks.ts +1 -1
  38. package/src/types/index.ts +7 -15
  39. package/src/types/policy.ts +25 -11
  40. package/src/types/project_manifest.ts +362 -0
  41. package/src/types/properties.ts +11 -169
  42. package/src/types/schema_version.ts +112 -0
  43. package/src/types/security_rules.ts +344 -0
  44. package/src/types/storage_authorize.ts +77 -0
  45. package/dist/controllers/analytics_controller.d.ts +0 -7
  46. package/dist/controllers/auth.d.ts +0 -110
  47. package/dist/controllers/customization_controller.d.ts +0 -61
  48. package/dist/controllers/dialogs_controller.d.ts +0 -36
  49. package/dist/controllers/local_config_persistence.d.ts +0 -20
  50. package/dist/controllers/navigation.d.ts +0 -225
  51. package/dist/controllers/registry.d.ts +0 -80
  52. package/dist/controllers/side_dialogs_controller.d.ts +0 -67
  53. package/dist/controllers/side_panel_controller.d.ts +0 -97
  54. package/dist/controllers/snackbar.d.ts +0 -24
  55. package/dist/rebase_context.d.ts +0 -110
  56. package/dist/types/breadcrumbs.d.ts +0 -26
  57. package/dist/types/builders.d.ts +0 -15
  58. package/dist/types/component_overrides.d.ts +0 -196
  59. package/dist/types/entity_actions.d.ts +0 -104
  60. package/dist/types/entity_link_builder.d.ts +0 -7
  61. package/dist/types/entity_views.d.ts +0 -95
  62. package/dist/types/export_import.d.ts +0 -21
  63. package/dist/types/formex.d.ts +0 -40
  64. package/dist/types/locales.d.ts +0 -4
  65. package/dist/types/modify_collections.d.ts +0 -5
  66. package/dist/types/plugins.d.ts +0 -277
  67. package/dist/types/property_config.d.ts +0 -74
  68. package/dist/types/slots.d.ts +0 -262
  69. package/dist/types/translations.d.ts +0 -915
  70. package/dist/types/user_management_delegate.d.ts +0 -22
  71. package/src/controllers/analytics_controller.tsx +0 -57
  72. package/src/controllers/auth.tsx +0 -121
  73. package/src/controllers/customization_controller.tsx +0 -72
  74. package/src/controllers/dialogs_controller.tsx +0 -37
  75. package/src/controllers/local_config_persistence.tsx +0 -22
  76. package/src/controllers/navigation.ts +0 -267
  77. package/src/controllers/registry.ts +0 -95
  78. package/src/controllers/side_dialogs_controller.tsx +0 -82
  79. package/src/controllers/side_panel_controller.tsx +0 -112
  80. package/src/controllers/snackbar.ts +0 -29
  81. package/src/rebase_context.tsx +0 -130
  82. package/src/types/breadcrumbs.ts +0 -27
  83. package/src/types/builders.ts +0 -17
  84. package/src/types/component_overrides.ts +0 -244
  85. package/src/types/entity_actions.tsx +0 -125
  86. package/src/types/entity_link_builder.ts +0 -8
  87. package/src/types/entity_views.tsx +0 -114
  88. package/src/types/export_import.ts +0 -26
  89. package/src/types/formex.ts +0 -45
  90. package/src/types/locales.ts +0 -81
  91. package/src/types/modify_collections.tsx +0 -6
  92. package/src/types/plugins.tsx +0 -346
  93. package/src/types/property_config.tsx +0 -95
  94. package/src/types/slots.tsx +0 -307
  95. package/src/types/translations.ts +0 -1026
  96. package/src/types/user_management_delegate.ts +0 -23
@@ -1,9 +1,8 @@
1
- import type { ComponentRef } from "./component_ref";
2
1
  import type { Entity, EntityReference, EntityRelation, EntityValues, GeoPoint, Vector } from "./entities";
3
2
  import type { JoinStep, OnAction, Relation } from "./relations";
4
- import type { CollectionConfig, FilterValues, WhereFilterOp } from "./collections";
3
+ import type { CollectionConfig, FilterValues } from "./collections";
5
4
  import type { ColorKey, ColorScheme } from "./chips";
6
- import type { AuthController } from "../controllers/auth";
5
+ import type { AuthState } from "../controllers/auth_state";
7
6
  import type { AfterReadProps, BeforeSaveProps } from "./entity_callbacks";
8
7
  import type { User } from "../users";
9
8
  /**
@@ -103,47 +102,7 @@ export type InferEntityType<P extends Properties> = {
103
102
  } & {
104
103
  -readonly [K in OptionalPropertyKeys<P>]?: InferPropertyType<P[K]>;
105
104
  };
106
- /**
107
- * Interface including all common properties of a CMS property.
108
- * @group Entity properties
109
- */
110
- export interface BaseUIConfig<CustomProps = unknown> {
111
- columnWidth?: number;
112
- hideFromCollection?: boolean;
113
- readOnly?: boolean;
114
- disabled?: boolean | PropertyDisabledConfig;
115
- widthPercentage?: number;
116
- customProps?: CustomProps;
117
- Field?: ComponentRef<any>;
118
- Preview?: ComponentRef<any>;
119
- /**
120
- * Narrow the filter operators offered for this property in collection
121
- * filter UIs (table header filters and the Filters dialog).
122
- *
123
- * The final offered set is the **intersection** of the engine's
124
- * capabilities, the property-type defaults, and this list — you can only
125
- * *restrict*, never enable an operator the underlying engine cannot run.
126
- *
127
- * Pass an empty array to disable filtering on this property entirely.
128
- *
129
- * @example
130
- * // Email column: exact match, contains, and null check only
131
- * ui: { filterOperators: ["==", "ilike", "is-null"] }
132
- */
133
- filterOperators?: readonly WhereFilterOp[];
134
- /**
135
- * Replace the filter field rendered for this property in collection
136
- * filter UIs. The component receives `FilterFieldBindingProps`
137
- * (property, resolved `operators`, `value`, `setValue`, …).
138
- *
139
- * Takes precedence over the collection-level
140
- * `components["Collection.FilterField"]` override and the built-in
141
- * per-type filter fields.
142
- */
143
- Filter?: ComponentRef<any>;
144
- }
145
105
  export interface BaseProperty<CustomProps = unknown> {
146
- ui?: BaseUIConfig<CustomProps>;
147
106
  /**
148
107
  * Property name (e.g. Product)
149
108
  */
@@ -219,35 +178,7 @@ export interface BaseProperty<CustomProps = unknown> {
219
178
  */
220
179
  metadata?: Record<string, unknown>;
221
180
  }
222
- /**
223
- * @group Entity properties
224
- */
225
- export interface StringUIConfig extends BaseUIConfig {
226
- /**
227
- * Is this string property long enough so it should be displayed in
228
- * a multiple line field. Defaults to false. If set to true,
229
- * the number of lines adapts to the content
230
- */
231
- multiline?: boolean;
232
- /**
233
- * Should this string property be displayed as a markdown field. If true,
234
- * the field is rendered as a text editor that supports markdown highlight
235
- * syntax. It also includes a preview of the result.
236
- */
237
- markdown?: boolean;
238
- /**
239
- * Should this string be rendered as a tag instead of just text.
240
- */
241
- previewAsTag?: boolean;
242
- clearable?: boolean;
243
- /**
244
- * If the value of this property is a URL, you can set this flag to true
245
- * to add a link, or one of the supported media types to render a preview
246
- */
247
- url?: boolean | PreviewType;
248
- }
249
181
  export interface StringProperty extends BaseProperty {
250
- ui?: StringUIConfig;
251
182
  type: "string";
252
183
  /**
253
184
  * Default value for new entities. Must be a string.
@@ -305,15 +236,17 @@ export interface StringProperty extends BaseProperty {
305
236
  * Does this field include an email
306
237
  */
307
238
  email?: boolean;
308
- }
309
- /**
310
- * @group Entity properties
311
- */
312
- export interface NumberUIConfig extends BaseUIConfig {
313
- clearable?: boolean;
239
+ /**
240
+ * Does this string hold a URL?
241
+ *
242
+ * A statement about the *data*, which is why it sits here beside `email`
243
+ * rather than in the admin block: the OpenAPI generator turns it into
244
+ * `format: "uri"`, so it is part of the published API contract. How the panel
245
+ * renders it — as a link, an image, a video — is `admin.urlPreview`.
246
+ */
247
+ url?: boolean;
314
248
  }
315
249
  export interface NumberProperty extends BaseProperty {
316
- ui?: NumberUIConfig;
317
250
  type: "number";
318
251
  /**
319
252
  * Default value for new entities. Must be a number.
@@ -350,7 +283,6 @@ export interface NumberProperty extends BaseProperty {
350
283
  * @group Entity properties
351
284
  */
352
285
  export interface BooleanProperty extends BaseProperty {
353
- ui?: BaseUIConfig;
354
286
  type: "boolean";
355
287
  /**
356
288
  * Default value for new entities. Must be a boolean.
@@ -361,14 +293,7 @@ export interface BooleanProperty extends BaseProperty {
361
293
  */
362
294
  validation?: PropertyValidationSchema;
363
295
  }
364
- /**
365
- * @group Entity properties
366
- */
367
- export interface VectorUIConfig extends BaseUIConfig {
368
- clearable?: boolean;
369
- }
370
296
  export interface VectorProperty extends BaseProperty {
371
- ui?: VectorUIConfig;
372
297
  type: "vector";
373
298
  /**
374
299
  * Default value for new entities.
@@ -388,17 +313,7 @@ export interface BinaryProperty extends BaseProperty {
388
313
  defaultValue?: string;
389
314
  validation?: PropertyValidationSchema;
390
315
  }
391
- /**
392
- * @group Entity properties
393
- */
394
- export interface DateUIConfig extends BaseUIConfig {
395
- /**
396
- * Add an icon to clear the value and set it to `null`. Defaults to `false`
397
- */
398
- clearable?: boolean;
399
- }
400
316
  export interface DateProperty extends BaseProperty {
401
- ui?: DateUIConfig;
402
317
  type: "date";
403
318
  /**
404
319
  * Default value for new entities. Must be a Date.
@@ -434,7 +349,6 @@ export interface DateProperty extends BaseProperty {
434
349
  * @group Entity properties
435
350
  */
436
351
  export interface GeopointProperty extends BaseProperty {
437
- ui?: BaseUIConfig;
438
352
  type: "geopoint";
439
353
  /**
440
354
  * Default value for new entities. Must be a GeoPoint.
@@ -445,12 +359,6 @@ export interface GeopointProperty extends BaseProperty {
445
359
  */
446
360
  validation?: PropertyValidationSchema;
447
361
  }
448
- /**
449
- * @group Entity properties
450
- */
451
- export interface ReferenceUIConfig extends BaseUIConfig {
452
- previewProperties?: string[];
453
- }
454
362
  /**
455
363
  * A pointer to a entity, stored **as a value** on the row (id + path, and
456
364
  * optionally a `driver`/`databaseId` for cross-datasource pointers).
@@ -468,7 +376,6 @@ export interface ReferenceUIConfig extends BaseUIConfig {
468
376
  * @group Entity properties
469
377
  */
470
378
  export interface ReferenceProperty extends BaseProperty {
471
- ui?: ReferenceUIConfig;
472
379
  type: "reference";
473
380
  /**
474
381
  * Default value for new entities. Must be a EntityReference.
@@ -505,13 +412,6 @@ export interface ReferenceProperty extends BaseProperty {
505
412
  */
506
413
  includeEntityLink?: boolean;
507
414
  }
508
- /**
509
- * @group Entity properties
510
- */
511
- export interface RelationUIConfig extends BaseUIConfig {
512
- previewProperties?: string[];
513
- widget?: "select" | "dialog";
514
- }
515
415
  /**
516
416
  * A schema-level relationship between collections **within a single
517
417
  * datasource** — backed by a foreign key, junction table, or explicit join
@@ -529,7 +429,6 @@ export interface RelationUIConfig extends BaseUIConfig {
529
429
  * @group Entity properties
530
430
  */
531
431
  export interface RelationProperty extends BaseProperty {
532
- ui?: RelationUIConfig;
533
432
  type: "relation";
534
433
  /**
535
434
  * Default value for new entities. Must be a EntityRelation or array of EntityRelation.
@@ -637,15 +536,7 @@ export interface RelationProperty extends BaseProperty {
637
536
  */
638
537
  widget?: "select" | "dialog";
639
538
  }
640
- /**
641
- * @group Entity properties
642
- */
643
- export interface ArrayUIConfig extends BaseUIConfig {
644
- expanded?: boolean;
645
- minimalistView?: boolean;
646
- }
647
539
  export interface ArrayProperty extends BaseProperty {
648
- ui?: ArrayUIConfig;
649
540
  type: "array";
650
541
  /**
651
542
  * Default value for new entities. Must be an array.
@@ -717,16 +608,7 @@ export interface ArrayProperty extends BaseProperty {
717
608
  */
718
609
  canAddElements?: boolean;
719
610
  }
720
- /**
721
- * @group Entity properties
722
- */
723
- export interface MapUIConfig extends BaseUIConfig {
724
- expanded?: boolean;
725
- minimalistView?: boolean;
726
- spreadChildren?: boolean;
727
- }
728
611
  export interface MapProperty extends BaseProperty {
729
- ui?: MapUIConfig;
730
612
  type: "map";
731
613
  /**
732
614
  * Default value for new entities. Must be a record/object.
@@ -772,30 +654,8 @@ export type PropertyBuilderProps<M extends Record<string, unknown> = Record<stri
772
654
  index?: number;
773
655
  path: string;
774
656
  entityId?: string | number;
775
- authController: AuthController;
657
+ authController: AuthState;
776
658
  };
777
- /**
778
- * @group Entity properties
779
- */
780
- export interface PropertyDisabledConfig {
781
- /**
782
- * Enable this flag if you would like to clear the value of the field
783
- * when the corresponding property gets disabled.
784
- *
785
- * This is useful for keeping data consistency when you have conditional
786
- * properties.
787
- */
788
- clearOnDisabled?: boolean;
789
- /**
790
- * Explanation of why this property is disabled (e.g. a different field
791
- * needs to be enabled)
792
- */
793
- disabledMessage?: string;
794
- /**
795
- * Set this flag to true if you want to hide this field when disabled
796
- */
797
- hidden?: boolean;
798
- }
799
659
  /**
800
660
  * We use this type to define mapping between string or number values in
801
661
  * the data source to a label (such in a select dropdown).
@@ -1046,11 +906,6 @@ export interface UploadedFileContext {
1046
906
  */
1047
907
  storage: StorageConfig;
1048
908
  }
1049
- /**
1050
- * Used for previewing urls if the download file is known
1051
- * @group Entity properties
1052
- */
1053
- export type PreviewType = "image" | "video" | "audio" | "file";
1054
909
  /**
1055
910
  * MIME types for storage fields
1056
911
  * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
@@ -0,0 +1,18 @@
1
+ import type { CollectionConfig } from "./collections";
2
+ /**
3
+ * Compute the canonical string a schema version hashes.
4
+ *
5
+ * Exposed separately so the hashing itself can differ by environment: Node has
6
+ * `crypto`, and callers without it can still compare canonical forms directly.
7
+ */
8
+ export declare function canonicalSchemaPayload(collections: CollectionConfig[]): string;
9
+ /**
10
+ * A short, non-cryptographic digest of the canonical payload.
11
+ *
12
+ * FNV-1a style, 64 bits, as two 32-bit halves. This is an identity, not a
13
+ * security boundary: nothing trusts a schema version to prove anything, it only
14
+ * answers "is this the same schema as before". A hand-rolled hash keeps this
15
+ * module free of `node:crypto`, so the identical function runs in the browser,
16
+ * in the CLI, and in the runtime — which is the property that actually matters.
17
+ */
18
+ export declare function computeSchemaVersion(collections: CollectionConfig[]): string;
@@ -0,0 +1,320 @@
1
+ /**
2
+ * Row-level security rules for a collection.
3
+ *
4
+ * 325 lines of RLS policy contract that used to sit in the middle of
5
+ * `collections.ts`, between the admin panel's table view-models and the auth
6
+ * collection config. It has no relationship to the shape of a collection — it is
7
+ * the most-read part of the BaaS surface, and it now reads on its own.
8
+ *
9
+ * A rule is compiled to Postgres `CREATE POLICY` DDL and, for the structured
10
+ * flavour, independently evaluated in JavaScript, so the admin UI's idea of what
11
+ * a user may do derives from the same expression the database enforces.
12
+ *
13
+ * Note that rule *names* are not what you write: an unnamed rule compiles to
14
+ * `<table>_<op>_<sha1[0:7]>`, plus an injected `default_admin` baseline. Derive
15
+ * them with `getPolicyNamesForRule`/`getEffectiveSecurityRules` rather than
16
+ * matching on `rule.name`.
17
+ */
18
+ import type { PolicyExpression } from "./policy";
19
+ /**
20
+ * SQL operation that a policy applies to.
21
+ * @group Models
22
+ */
23
+ export type SecurityOperation = "select" | "insert" | "update" | "delete" | "all";
24
+ /**
25
+ * Flexible Row Level Security rule for a collection.
26
+ *
27
+ * Built on PostgreSQL Row Level Security. Rules can range from
28
+ * simple convenience shortcuts to fully custom SQL expressions, giving you the
29
+ * full power of PostgreSQL Row Level Security.
30
+ *
31
+ * The authenticated user's identity is available in raw SQL via:
32
+ * - `auth.uid()` — the user's ID
33
+ * - `auth.roles()` — comma-separated app role IDs
34
+ * - `auth.jwt()` — full JWT claims as JSONB
35
+ *
36
+ * These are set automatically per-transaction by the backend.
37
+ *
38
+ * **How rules combine:** PostgreSQL evaluates all matching policies for an
39
+ * operation. Permissive rules are OR'd together (any one passing is enough).
40
+ * Restrictive rules are AND'd (all must pass). This is standard PostgreSQL RLS behavior.
41
+ *
42
+ * **Mutual exclusivity:** `ownerField`, `access`, structured `condition`, and
43
+ * raw SQL (`using`/`withCheck`) cannot be combined. The type system enforces
44
+ * this — attempting to set conflicting fields will produce a compile-time
45
+ * error.
46
+ *
47
+ * **Which form to reach for:** prefer the structured {@link StructuredSecurityRule}
48
+ * (`condition`/`check`) or the shortcuts (`ownerField`, `access`, `roles`). These
49
+ * are engine-agnostic and evaluated identically by the database and the admin UI,
50
+ * so the UI never shows an action the database will reject. Raw SQL
51
+ * ({@link RawSQLSecurityRule}) keeps full PostgreSQL power but is Postgres-only
52
+ * and server-authoritative (the UI cannot evaluate arbitrary SQL locally).
53
+ *
54
+ * @group Models
55
+ */
56
+ export type SecurityRule = OwnerSecurityRule | PublicSecurityRule | StructuredSecurityRule | RawSQLSecurityRule | RolesOnlySecurityRule;
57
+ /**
58
+ * Shared fields for all SecurityRule variants.
59
+ * @group Models
60
+ */
61
+ export interface SecurityRuleBase {
62
+ /**
63
+ * Optional human-readable name for the policy.
64
+ * If not provided, one will be auto-generated from the table name and operation.
65
+ * Must be unique per table.
66
+ *
67
+ * When using `operations` (array), each generated policy will have the
68
+ * operation name appended, e.g. `"owner_access_select"`, `"owner_access_update"`.
69
+ */
70
+ name?: string;
71
+ /**
72
+ * Which SQL operation this policy applies to.
73
+ * Use this when the policy targets a single operation or all operations.
74
+ *
75
+ * For multiple specific operations, use `operations` (array) instead.
76
+ * If neither is specified, defaults to `"all"`.
77
+ *
78
+ * @default "all"
79
+ */
80
+ operation?: SecurityOperation;
81
+ /**
82
+ * Array of SQL operations this policy applies to.
83
+ * The compiler will generate one PostgreSQL policy per operation, sharing
84
+ * the same configuration.
85
+ *
86
+ * This reduces boilerplate when the same rule applies to multiple (but not all)
87
+ * operations.
88
+ *
89
+ * Takes precedence over `operation` (singular) if both are specified.
90
+ *
91
+ * @example
92
+ * // Same rule for select and update
93
+ * { operations: ["select", "update"], ownerField: "user_id" }
94
+ *
95
+ * @example
96
+ * // Equivalent to operation: "all"
97
+ * { operations: ["all"], ownerField: "user_id" }
98
+ */
99
+ operations?: readonly SecurityOperation[];
100
+ /**
101
+ * Whether this policy is `"permissive"` (default) or `"restrictive"`.
102
+ *
103
+ * - **permissive**: Multiple permissive policies for the same operation are
104
+ * OR'd together — if *any* passes, access is granted.
105
+ * - **restrictive**: Restrictive policies are AND'd with all permissive
106
+ * policies — they act as additional gates that *must* also pass.
107
+ *
108
+ * This is the standard PostgreSQL RLS model.
109
+ *
110
+ * @default "permissive"
111
+ */
112
+ mode?: "permissive" | "restrictive";
113
+ /**
114
+ * **Shortcut.** Restrict this rule to users that have one of these
115
+ * application-level roles.
116
+ *
117
+ * **Important:** These are NOT native PostgreSQL database roles — names
118
+ * like `public`, `anon` or `authenticated` belong to {@link pgRoles} and
119
+ * produce a condition no user can satisfy if used here. These are
120
+ * application roles managed by Rebase, stored as an inline `roles TEXT[]`
121
+ * column on the users table, and injected into each transaction as
122
+ * `app.user_roles` — which `auth.roles()` reads.
123
+ *
124
+ * There is no roles registry: a role exists once it is assigned to a user.
125
+ *
126
+ * Generates a safe array-overlap condition — the user passes if they hold
127
+ * *any* of the listed roles:
128
+ * `string_to_array(auth.roles(), ',') && ARRAY['<role1>', '<role2>']`
129
+ *
130
+ * (Note: this is a true set intersection, NOT a regex/substring match, so
131
+ * a role named `admin` never matches `nonadmin` or `superadmin`.)
132
+ *
133
+ * Can be combined with `ownerField`, `access`, `condition`, or raw
134
+ * `using`/`withCheck`. When combined, the role check is AND'd with the
135
+ * other condition.
136
+ *
137
+ * @example
138
+ * // Only admins can delete
139
+ * { operation: "delete", roles: ["admin"] }
140
+ *
141
+ * @example
142
+ * // Admins have unfiltered read access to all rows
143
+ * { operation: "select", roles: ["admin"], using: "true" }
144
+ */
145
+ roles?: readonly string[];
146
+ /**
147
+ * **Advanced.** Native PostgreSQL database roles the policy applies to.
148
+ *
149
+ * By default, all generated policies target the `public` role (i.e.
150
+ * every database connection). This is correct for most setups where
151
+ * a single database role is used for all connections.
152
+ *
153
+ * **Important:** These are NOT the same as the application-level
154
+ * {@link roles} (admin, editor, viewer, etc.) — those are enforced in the
155
+ * USING/WITH CHECK clauses via `auth.roles()`. This field controls the
156
+ * PostgreSQL `TO` clause in `CREATE POLICY ... TO role_name`.
157
+ *
158
+ * Use this if you have dedicated PostgreSQL roles (e.g. `app_read`,
159
+ * `app_write`) and want policies to target specific ones.
160
+ *
161
+ * @default ["public"]
162
+ *
163
+ * @example
164
+ * // Only apply this policy when connected as `app_role`
165
+ * { operation: "select", access: "public", pgRoles: ["app_role"] }
166
+ */
167
+ pgRoles?: readonly string[];
168
+ }
169
+ /**
170
+ * Security rule that grants access based on row ownership.
171
+ * Generates a USING/WITH CHECK clause like: `<column> = auth.uid()`
172
+ *
173
+ * Cannot be combined with `using`, `withCheck`, or `access`.
174
+ *
175
+ * @example
176
+ * { operation: "all", ownerField: "user_id" }
177
+ *
178
+ * @group Models
179
+ */
180
+ export interface OwnerSecurityRule extends SecurityRuleBase {
181
+ /** The property (column) that stores the owner's user ID. */
182
+ ownerField: string;
183
+ access?: never;
184
+ using?: never;
185
+ withCheck?: never;
186
+ condition?: never;
187
+ check?: never;
188
+ }
189
+ /**
190
+ * Security rule that grants unrestricted row access (no row filtering).
191
+ * Generates `USING (true)`.
192
+ *
193
+ * This means "no row-level filter", NOT "anonymous/unauthenticated access".
194
+ * Authentication is still enforced at the API layer — this only controls which
195
+ * *rows* authenticated users can see.
196
+ *
197
+ * Cannot be combined with `using`, `withCheck`, or `ownerField`.
198
+ *
199
+ * @example
200
+ * // Public read (any authenticated user sees all rows)
201
+ * { operation: "select", access: "public" }
202
+ *
203
+ * @group Models
204
+ */
205
+ export interface PublicSecurityRule extends SecurityRuleBase {
206
+ /** Grant unrestricted row access for this operation. */
207
+ access: "public";
208
+ ownerField?: never;
209
+ using?: never;
210
+ withCheck?: never;
211
+ condition?: never;
212
+ check?: never;
213
+ }
214
+ /**
215
+ * Security rule expressed as a structured, engine-agnostic
216
+ * {@link PolicyExpression}. This is the **recommended** way to write a
217
+ * non-trivial condition: it compiles to PostgreSQL `USING`/`WITH CHECK` SQL
218
+ * *and* is evaluated identically by the admin UI, so the UI can never show an
219
+ * action the database will reject.
220
+ *
221
+ * Cannot be combined with `ownerField`, `access`, or raw `using`/`withCheck`.
222
+ *
223
+ * @example
224
+ * // Owner, or any user holding the `moderator` role
225
+ * {
226
+ * operation: "update",
227
+ * condition: policy.or(
228
+ * policy.compare(policy.field("user_id"), "eq", policy.authUid()),
229
+ * policy.rolesOverlap(["moderator"])
230
+ * )
231
+ * }
232
+ *
233
+ * @group Models
234
+ */
235
+ export interface StructuredSecurityRule extends SecurityRuleBase {
236
+ /**
237
+ * Structured condition for the `USING` clause — which *existing* rows are
238
+ * visible / can be modified / deleted (SELECT, UPDATE, DELETE).
239
+ */
240
+ condition: PolicyExpression;
241
+ /**
242
+ * Structured condition for the `WITH CHECK` clause — which *new/updated*
243
+ * row values are allowed (INSERT, UPDATE). Defaults to `condition` when
244
+ * omitted, mirroring PostgreSQL's own behavior.
245
+ */
246
+ check?: PolicyExpression;
247
+ ownerField?: never;
248
+ access?: never;
249
+ using?: never;
250
+ withCheck?: never;
251
+ }
252
+ /**
253
+ * Security rule using raw SQL expressions for full PostgreSQL RLS power.
254
+ *
255
+ * **Postgres-only and server-authoritative.** Arbitrary SQL cannot be
256
+ * evaluated by the admin UI, so a rule using this form is treated as *unknown*
257
+ * client-side (never silently allowed) and its effect on visible actions is
258
+ * reflected from the server. For conditions that should also drive the UI
259
+ * precisely, prefer the structured {@link StructuredSecurityRule}.
260
+ *
261
+ * Cannot be combined with `ownerField`, `access`, or structured `condition`.
262
+ *
263
+ * You can reference columns via `{column_name}` which will be resolved to
264
+ * `table.column_name` in the generated Drizzle code.
265
+ *
266
+ * @example
267
+ * // Rows published in the last 30 days are visible
268
+ * { operation: "select", using: "{published_at} > now() - interval '30 days'" }
269
+ *
270
+ * @example
271
+ * // Only the owner, or users with 'moderator' role
272
+ * {
273
+ * operation: "select",
274
+ * using: "{user_id} = auth.uid() OR auth.roles() ~ 'moderator'"
275
+ * }
276
+ *
277
+ * @group Models
278
+ */
279
+ export interface RawSQLSecurityRule extends SecurityRuleBase {
280
+ /**
281
+ * Raw SQL expression for the `USING` clause.
282
+ * This controls which *existing* rows are visible / can be modified / deleted.
283
+ * Applied to SELECT, UPDATE, and DELETE.
284
+ */
285
+ using: string;
286
+ /**
287
+ * Raw SQL expression for the `WITH CHECK` clause.
288
+ * This controls which *new/updated* row values are allowed.
289
+ * Applied to INSERT and UPDATE.
290
+ *
291
+ * If not provided on INSERT/UPDATE policies, falls back to `using`
292
+ * (which matches PostgreSQL's own default behavior).
293
+ */
294
+ withCheck?: string;
295
+ ownerField?: never;
296
+ access?: never;
297
+ condition?: never;
298
+ check?: never;
299
+ }
300
+ /**
301
+ * Security rule that only filters by application roles, without any
302
+ * row-level condition (USING/WITH CHECK).
303
+ *
304
+ * Useful for simple "only admins can access this table" rules where
305
+ * no per-row filtering is needed.
306
+ *
307
+ * @example
308
+ * // Only admins can delete
309
+ * { operation: "delete", roles: ["admin"] }
310
+ *
311
+ * @group Models
312
+ */
313
+ export interface RolesOnlySecurityRule extends SecurityRuleBase {
314
+ ownerField?: never;
315
+ access?: never;
316
+ using?: never;
317
+ withCheck?: never;
318
+ condition?: never;
319
+ check?: never;
320
+ }
@@ -0,0 +1,74 @@
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
+ export type StorageOperation = "read" | "write" | "delete" | "list";
12
+ /** The caller, as resolved by whichever auth middleware ran. */
13
+ export interface StorageAuthorizeUser {
14
+ uid: string;
15
+ email?: string;
16
+ roles?: string[];
17
+ }
18
+ export interface StorageAuthorizeContext {
19
+ /** Object key, bucket prefix stripped and traversal already sanitized. */
20
+ key: string;
21
+ bucket: string;
22
+ operation: StorageOperation;
23
+ /** Null when the route allows unauthenticated access. */
24
+ user: StorageAuthorizeUser | null;
25
+ /** Named backend the request targeted, when one was given. */
26
+ storageId?: string;
27
+ /**
28
+ * Trusted, RLS-bypassing data access, so the hook can answer the question it
29
+ * actually has to answer: *who owns this object?*
30
+ *
31
+ * Without it the hook can only do prefix arithmetic on the key, which
32
+ * expresses no real multi-tenant rule — ownership lives in a row, not in a
33
+ * string. And it cannot simply import the server to get one: a project
34
+ * declares this hook from its **config** package, which depends on
35
+ * `@rebasepro/types` alone and cannot resolve `@rebasepro/server` at
36
+ * runtime. So the accessor is handed in.
37
+ *
38
+ * It bypasses row-level security on purpose. The hook IS the authorization
39
+ * decision; asking it to make that decision through a reader that has
40
+ * already been narrowed by the caller's own permissions is circular.
41
+ */
42
+ data?: StorageAuthorizeData;
43
+ }
44
+ /**
45
+ * The slice of the data API a storage hook needs: read a collection, in the
46
+ * trusted server context.
47
+ *
48
+ * Deliberately read-only and deliberately tiny. A hook that can write is a hook
49
+ * that can be tricked into writing, and an authorization check has no business
50
+ * mutating anything.
51
+ */
52
+ export interface StorageAuthorizeData {
53
+ collection(slug: string): {
54
+ find(query?: Record<string, unknown>): Promise<{
55
+ data: Record<string, unknown>[];
56
+ }>;
57
+ findById(id: string): Promise<Record<string, unknown> | null>;
58
+ };
59
+ }
60
+ /**
61
+ * Per-object access control for storage, the analogue of an RLS policy on a
62
+ * collection.
63
+ *
64
+ * Without it, storage routes authenticate but do not authorize: `requireAuth`
65
+ * and `publicRead` are global switches, so any signed-in user could read any
66
+ * key they could name. That is fine for a single-tenant app and useless for a
67
+ * multi-tenant one, where the only thing separating two tenants' files would be
68
+ * key unguessability — not an access-control model. Multi-tenant apps were
69
+ * left to route every byte through a custom function to get an ownership check
70
+ * in, and each of them had to invent it.
71
+ *
72
+ * Return false (or throw) to deny. Denials surface as 403.
73
+ */
74
+ export type StorageAuthorize = (ctx: StorageAuthorizeContext) => boolean | Promise<boolean>;