@rebasepro/types 0.11.1-canary.gfd39654 → 0.12.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.
@@ -2,7 +2,6 @@ import type { ComponentRef } from "./component_ref";
2
2
 
3
3
  import type { Entity, EntityReference, EntityRelation, EntityValues, GeoPoint, Vector } from "./entities";
4
4
  import type { JoinStep, OnAction, Relation, ResolvedRelation } from "./relations";
5
- import type { CollectionConfig, FilterValues, WhereFilterOp } from "./collections";
6
5
  import type { ColorKey, ColorScheme } from "./chips";
7
6
  import type { AuthState } from "../controllers/auth_state";
8
7
  import type { AfterReadProps, BeforeSaveProps } from "./entity_callbacks";
@@ -65,19 +64,49 @@ export type Properties = {
65
64
  [key: string]: Property;
66
65
  };
67
66
 
67
+ /**
68
+ * `Omit` that survives a union.
69
+ *
70
+ * `Property` is a union discriminated on `type`, and a bare `Omit<Property, K>`
71
+ * collapses it into one object whose `type` is the union of every tag — so
72
+ * `property.type === "string"` stops narrowing and the concrete property types
73
+ * become unreachable. The `T extends unknown` clause makes it distribute, so
74
+ * each member is omitted from separately and keeps its own discriminant.
75
+ */
76
+ type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
77
+
78
+ /**
79
+ * The fields that describe a property's **column**, which only an engine with
80
+ * columns has.
81
+ *
82
+ * `columnType` names a Postgres type (`uuid`, `bigserial`, `jsonb`, `text[]`)
83
+ * and `columnName` overrides the snake_case derivation used to build a column
84
+ * name. `DataSourceCapabilities.supportsColumnTypes` already reported this at
85
+ * runtime — `false` for both document engines — while the types let a MongoDB
86
+ * property declare `columnType: "bigserial"`.
87
+ *
88
+ * They stay declared on the concrete property interfaces rather than moving,
89
+ * because that is where their per-type value unions live; what changes is that
90
+ * the document engines' property aliases omit them.
91
+ */
92
+ type SqlColumnFields = "columnType" | "columnName";
93
+
68
94
  export type PostgresProperty = Exclude<Property, ReferenceProperty>;
69
95
  export type PostgresProperties = {
70
96
  [key: string]: PostgresProperty;
71
97
  };
72
98
 
73
- export type FirebaseProperty = Exclude<Property, RelationProperty>;
99
+ export type FirebaseProperty = DistributiveOmit<Exclude<Property, RelationProperty | VectorProperty>, SqlColumnFields>;
74
100
  export type FirebaseProperties = {
75
101
  [key: string]: FirebaseProperty;
76
102
  };
77
103
 
78
104
  // MongoDB is a document store: it uses references (stored pointers), not
79
105
  // SQL-style relations/joins. Same gating as Firestore.
80
- export type MongoProperty = Exclude<Property, RelationProperty>;
106
+ //
107
+ // `vector` goes with them: it is pgvector-shaped, only `@rebasepro/server-postgres`
108
+ // reads it, and `supportsVectors` on the engine's capabilities says so.
109
+ export type MongoProperty = DistributiveOmit<Exclude<Property, RelationProperty | VectorProperty>, SqlColumnFields>;
81
110
  export type MongoProperties = {
82
111
  [key: string]: MongoProperty;
83
112
  };
@@ -164,7 +193,7 @@ export interface BaseProperty<CustomProps = unknown> {
164
193
 
165
194
  /**
166
195
  * You can use this prop to reuse a property that has been defined
167
- * in the top level of the CMS in the prop `fields`.
196
+ * in the top level of the admin in the prop `fields`.
168
197
  * All the configuration will be taken from the inherited config, and
169
198
  * overwritten by the current property config.
170
199
  */
@@ -473,20 +502,6 @@ export interface ReferenceProperty extends BaseProperty {
473
502
  * property.
474
503
  */
475
504
  path?: string;
476
- /**
477
- * Allow selection of entities that pass the given filter only.
478
- * e.g. `fixedFilter: { age: [">=", 18] }`
479
- */
480
- fixedFilter?: FilterValues<string>;
481
-
482
- /**
483
- * Should the reference include the ID of the entity. Defaults to `true`
484
- */
485
- includeId?: boolean;
486
- /**
487
- * Should the reference include a link to the entity (open the entity details). Defaults to `true`
488
- */
489
- includeEntityLink?: boolean;
490
505
  }
491
506
 
492
507
  /**
@@ -548,29 +563,6 @@ export interface RelationProperty extends BaseProperty {
548
563
  * collection's `relations` array.
549
564
  */
550
565
  resolvedRelation?: ResolvedRelation;
551
-
552
- // ─── UI configuration ───
553
-
554
- /**
555
- * Allow selection of entities that pass the given filter only.
556
- * e.g. `fixedFilter: { age: [">=", 18] }`
557
- */
558
- fixedFilter?: FilterValues<string>;
559
-
560
- /**
561
- * Should the reference include the ID of the entity. Defaults to `true`
562
- */
563
- includeId?: boolean;
564
- /**
565
- * Should the reference include a link to the entity (open the entity details). Defaults to `true`
566
- */
567
- includeEntityLink?: boolean;
568
-
569
- /**
570
- * Choose the widget to use for selecting the relation.
571
- * Defaults to `select`.
572
- */
573
- widget?: "select" | "dialog";
574
566
  }
575
567
 
576
568
  export interface ArrayProperty extends BaseProperty {
@@ -634,17 +626,6 @@ export interface ArrayProperty extends BaseProperty {
634
626
  * Rules for validating this property
635
627
  */
636
628
  validation?: ArrayPropertyValidationSchema;
637
-
638
- /**
639
- * Can the elements in this array be reordered. Defaults to `true`.
640
- * This prop has no effect if `disabled` is set to true.
641
- */
642
- sortable?: boolean;
643
- /**
644
- * Can the elements in this array be added. Defaults to `true`
645
- * This prop has no effect if `disabled` is set to true.
646
- */
647
- canAddElements?: boolean;
648
629
  }
649
630
 
650
631
  export interface MapProperty extends BaseProperty {
@@ -665,6 +646,12 @@ export interface MapProperty extends BaseProperty {
665
646
  * Order in which the properties are displayed.
666
647
  * If you are specifying your collection as code, the order is the same as the
667
648
  * one you define in `properties`, and you don't need to specify this prop.
649
+ *
650
+ * Stays on the property rather than moving to the `admin` block, unlike the
651
+ * rest of the map's presentation options: `sortProperties` in
652
+ * `@rebasepro/common` reads it recursively, and `@rebasepro/firebase` calls
653
+ * that when it builds collections. A core package cannot read the admin
654
+ * block — the field exists only once `@rebasepro/admin-types` is installed.
668
655
  */
669
656
  propertiesOrder?: string[];
670
657
  /**
@@ -673,13 +660,13 @@ export interface MapProperty extends BaseProperty {
673
660
  * will be considered valid, even if you set `required` in the properties.
674
661
  */
675
662
  validation?: PropertyValidationSchema;
676
- /**
677
- * Properties that are displayed when rendered as a preview
678
- */
679
- previewProperties?: string[];
680
663
  /**
681
664
  * Render this map as a key-value table that allows to use
682
665
  * arbitrary keys. You don't need to define the properties in this case.
666
+ *
667
+ * Core rather than admin despite the wording: it says the map has no
668
+ * declared shape, which is what the OpenAPI generator emits the schema
669
+ * from (`additionalProperties` instead of a property list).
683
670
  */
684
671
  keyValue?: boolean;
685
672
  }
@@ -88,3 +88,133 @@ export interface ResolvedStorageSource {
88
88
  /** Human-readable label. */
89
89
  label?: string;
90
90
  }
91
+
92
+ /**
93
+ * The environment-variable suffix for a storage or data source key.
94
+ *
95
+ * `""` for the default source — so a single-bucket project keeps configuring
96
+ * plain `S3_BUCKET` — and `__<KEY>` for every named one, uppercased with
97
+ * non-alphanumerics collapsed to underscores: `media-cdn` → `S3_BUCKET__MEDIA_CDN`.
98
+ *
99
+ * The rule derives the variable name from the declared key rather than
100
+ * discovering keys by scanning the environment. Scanning would have to guess how
101
+ * `S3_BUCKET__MEDIA_CDN` splits into a key; deriving cannot be ambiguous, and a
102
+ * typo surfaces as a missing source at boot instead of a silently ignored
103
+ * variable.
104
+ *
105
+ * It lives in this package, with no dependencies, because four things must agree
106
+ * on it exactly: the CLI (validating a build), the runtime (reading its own
107
+ * environment), the control plane (writing a tenant's Secret), and the docs. A
108
+ * second implementation of a naming convention is a second chance to disagree.
109
+ *
110
+ * @group Models
111
+ */
112
+ export function storageEnvSuffix(key: string, defaultKey: string = DEFAULT_STORAGE_SOURCE_KEY): string {
113
+ if (!key || key === defaultKey) return "";
114
+ const normalized = key
115
+ .replace(/[^A-Za-z0-9]+/g, "_")
116
+ .replace(/^_+|_+$/g, "")
117
+ .toUpperCase();
118
+ if (!normalized) {
119
+ throw new Error(
120
+ `Source key "${key}" cannot be turned into an environment variable name. ` +
121
+ "Use a key containing at least one letter or digit."
122
+ );
123
+ }
124
+ return `__${normalized}`;
125
+ }
126
+
127
+ /**
128
+ * Two distinct keys that collapse onto the same variable name, or `null`.
129
+ *
130
+ * `media-cdn` and `media_cdn` are different source keys but the same suffix, so
131
+ * without this one of them silently reads the other's configuration. Returns the
132
+ * offending pair rather than throwing, so each caller can raise it in its own
133
+ * idiom — a `BundleError` at boot, a build failure in the CLI, a rejected deploy
134
+ * in a control plane.
135
+ *
136
+ * @group Models
137
+ */
138
+ export function findStorageSuffixCollision(
139
+ keys: string[],
140
+ defaultKey: string = DEFAULT_STORAGE_SOURCE_KEY
141
+ ): { a: string; b: string; suffix: string } | null {
142
+ const seen = new Map<string, string>();
143
+ for (const key of keys) {
144
+ const suffix = storageEnvSuffix(key, defaultKey);
145
+ const existing = seen.get(suffix);
146
+ if (existing !== undefined && existing !== key) {
147
+ return { a: existing, b: key, suffix };
148
+ }
149
+ seen.set(suffix, key);
150
+ }
151
+ return null;
152
+ }
153
+
154
+ /** The `storage` block of `rebase.json`, structurally. */
155
+ export type DeclaredStorageSources = Record<string, {
156
+ engine: string;
157
+ transport?: StorageSourceTransport;
158
+ label?: string;
159
+ }>;
160
+
161
+ /**
162
+ * Merge the two places a project may declare storage sources into one list.
163
+ *
164
+ * `rebase.json` is authoritative for every field it states. Config code may add
165
+ * sources it does not mention and fill in fields it left out, but may not
166
+ * contradict it: the manifest is what a host reads to decide which buckets need
167
+ * configuring, and a runtime that quietly disagreed with it would put the
168
+ * console back to describing a topology the tenant does not have — the exact
169
+ * failure this whole mechanism exists to end.
170
+ *
171
+ * Note what is *not* here: no default source is invented when both inputs are
172
+ * empty. That decision belongs to the resolver, which knows whether declaring
173
+ * nothing means "one plain bucket" (it does) or "no storage at all".
174
+ *
175
+ * @group Models
176
+ */
177
+ export function normalizeStorageSources(
178
+ declared: DeclaredStorageSources | StorageSourceDefinition[] | undefined,
179
+ exported: StorageSourceDefinition[] | undefined
180
+ ): StorageSourceDefinition[] {
181
+ const merged = new Map<string, StorageSourceDefinition>();
182
+
183
+ // Two shapes, one meaning. `rebase.json` states sources as a record keyed by
184
+ // source key, which is how JSON expresses a set of named things; the bundle
185
+ // manifest stores the already-resolved array. Accepting both is what lets the
186
+ // CLI and the runtime call this with what each of them happens to hold.
187
+ const declaredEntries: [string, { engine: string; transport?: StorageSourceTransport; label?: string }][] =
188
+ Array.isArray(declared)
189
+ ? declared.filter(d => d?.key).map(d => [d.key, d])
190
+ : Object.entries(declared ?? {});
191
+
192
+ for (const [key, config] of declaredEntries) {
193
+ merged.set(key, {
194
+ key,
195
+ engine: config.engine,
196
+ transport: config.transport ?? "server",
197
+ ...(config.label !== undefined ? { label: config.label } : {})
198
+ });
199
+ }
200
+
201
+ for (const definition of exported ?? []) {
202
+ if (!definition?.key) continue;
203
+ const existing = merged.get(definition.key);
204
+ if (!existing) {
205
+ merged.set(definition.key, {
206
+ key: definition.key,
207
+ engine: definition.engine,
208
+ transport: definition.transport ?? "server",
209
+ ...(definition.label !== undefined ? { label: definition.label } : {})
210
+ });
211
+ continue;
212
+ }
213
+ // Fill gaps only. `rebase.json` stated these; code does not overrule it.
214
+ if (existing.label === undefined && definition.label !== undefined) {
215
+ existing.label = definition.label;
216
+ }
217
+ }
218
+
219
+ return Array.from(merged.values());
220
+ }
@@ -115,45 +115,3 @@ export interface ChannelHistoryMessage extends WebSocketMessage {
115
115
  */
116
116
  latestSeq?: number;
117
117
  }
118
-
119
- /**
120
- * Column metadata returned by table introspection.
121
- */
122
- export interface TableColumnInfo {
123
- column_name: string;
124
- data_type: string;
125
- udt_name: string;
126
- is_nullable: string;
127
- column_default: string | null;
128
- character_maximum_length: number | null;
129
- /** Enum values, populated for USER-DEFINED (enum) columns */
130
- enum_values?: string[];
131
- }
132
-
133
- export interface TableForeignKeyInfo {
134
- column_name: string;
135
- foreign_table_name: string;
136
- foreign_column_name: string;
137
- }
138
-
139
- export interface TableJunctionInfo {
140
- junction_table_name: string;
141
- source_column_name: string;
142
- target_table_name: string;
143
- target_column_name: string;
144
- }
145
-
146
- export interface TablePolicyInfo {
147
- policy_name: string;
148
- roles: string[];
149
- cmd: string;
150
- qual?: string;
151
- with_check?: string;
152
- }
153
-
154
- export interface TableMetadata {
155
- columns: TableColumnInfo[];
156
- foreignKeys: TableForeignKeyInfo[];
157
- junctions: TableJunctionInfo[];
158
- policies: TablePolicyInfo[];
159
- }