@rebasepro/common 0.8.0 → 0.9.1-canary.09aaf62

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 (65) hide show
  1. package/README.md +5 -5
  2. package/dist/collections/CollectionRegistry.d.ts +16 -16
  3. package/dist/collections/default-collections.d.ts +5 -1
  4. package/dist/data/buildRebaseData.d.ts +44 -3
  5. package/dist/data/buildRoutedRebaseData.d.ts +14 -9
  6. package/dist/data/filter-dialect.d.ts +18 -4
  7. package/dist/data/query_builder.d.ts +1 -1
  8. package/dist/data/resolveDataSource.d.ts +1 -1
  9. package/dist/data/sort-dialect.d.ts +41 -0
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.es.js +1236 -179
  12. package/dist/index.es.js.map +1 -1
  13. package/dist/util/auth-default-policies.d.ts +22 -0
  14. package/dist/util/builders.d.ts +19 -56
  15. package/dist/util/callbacks.d.ts +3 -3
  16. package/dist/util/collections.d.ts +4 -4
  17. package/dist/util/entities.d.ts +2 -2
  18. package/dist/util/filter-operator-resolution.d.ts +32 -0
  19. package/dist/util/identity.d.ts +83 -0
  20. package/dist/util/index.d.ts +4 -0
  21. package/dist/util/junction-policies.d.ts +108 -0
  22. package/dist/util/navigation_from_path.d.ts +4 -4
  23. package/dist/util/navigation_utils.d.ts +3 -3
  24. package/dist/util/parent_references_from_path.d.ts +2 -2
  25. package/dist/util/permissions.d.ts +6 -6
  26. package/dist/util/policy/evaluatePolicy.d.ts +8 -1
  27. package/dist/util/policy/index.d.ts +1 -0
  28. package/dist/util/policy/policyToPostgres.d.ts +14 -2
  29. package/dist/util/policy/sqlToPolicy.d.ts +24 -14
  30. package/dist/util/references.d.ts +2 -2
  31. package/dist/util/relations.d.ts +5 -5
  32. package/dist/util/resolutions.d.ts +2 -2
  33. package/package.json +7 -8
  34. package/src/collections/CollectionRegistry.ts +36 -36
  35. package/src/collections/default-collections.ts +2 -0
  36. package/src/data/buildRebaseData.ts +430 -60
  37. package/src/data/buildRoutedRebaseData.ts +22 -16
  38. package/src/data/filter-dialect.ts +151 -60
  39. package/src/data/query_builder.ts +11 -2
  40. package/src/data/resolveDataSource.ts +1 -1
  41. package/src/data/sort-dialect.ts +56 -0
  42. package/src/index.ts +1 -0
  43. package/src/util/auth-default-policies.ts +152 -0
  44. package/src/util/builders.ts +25 -99
  45. package/src/util/callbacks.ts +8 -8
  46. package/src/util/collections.ts +4 -4
  47. package/src/util/entities.ts +4 -4
  48. package/src/util/filter-operator-resolution.ts +81 -0
  49. package/src/util/identity.ts +166 -0
  50. package/src/util/index.ts +4 -0
  51. package/src/util/junction-policies.ts +353 -0
  52. package/src/util/navigation_from_path.ts +4 -4
  53. package/src/util/navigation_utils.ts +8 -8
  54. package/src/util/parent_references_from_path.ts +3 -3
  55. package/src/util/permissions.test.ts +2 -2
  56. package/src/util/permissions.ts +7 -7
  57. package/src/util/policy/evaluatePolicy.ts +26 -4
  58. package/src/util/policy/index.ts +1 -0
  59. package/src/util/policy/policyToPostgres.ts +123 -17
  60. package/src/util/policy/sqlToPolicy.ts +190 -13
  61. package/src/util/references.ts +2 -2
  62. package/src/util/relations.ts +12 -12
  63. package/src/util/resolutions.ts +5 -5
  64. package/dist/index.umd.js +0 -2901
  65. package/dist/index.umd.js.map +0 -1
@@ -1,23 +1,18 @@
1
1
  import {
2
- AdditionalFieldDelegate,
3
2
  ArrayProperty,
4
3
  BooleanProperty,
5
4
  DateProperty,
6
- EntityCallbacks,
7
- EntityCollection,
8
- EnumValueConfig,
9
- EnumValues,
10
- FirebaseCollection,
5
+ CollectionConfig,
6
+ FirebaseCollectionConfig,
11
7
  FirebaseProperties,
12
8
  GeopointProperty,
13
9
  InferEntityType,
14
10
  MapProperty,
15
- MongoDBCollection,
11
+ MongoDBCollectionConfig,
16
12
  MongoProperties,
17
13
  NumberProperty,
18
- PostgresCollection,
14
+ PostgresCollectionConfig,
19
15
  PostgresProperties,
20
- Properties,
21
16
  Property,
22
17
  ReferenceProperty,
23
18
  StringProperty,
@@ -26,17 +21,20 @@ import {
26
21
 
27
22
 
28
23
  /**
29
- * Identity function we use to defeat the type system of Typescript and build
30
- * collection views with all its properties
31
- * @param collection
24
+ * @deprecated Use {@link defineCollection} instead — it infers property
25
+ * types automatically (autocomplete on `titleProperty`, `sort`,
26
+ * `propertiesOrder`, callbacks) without manual generics.
27
+ * `buildCollection` is kept for FireCMS migration compatibility and will
28
+ * be removed before 1.0.
29
+ *
32
30
  * @group Builder
33
31
  */
34
32
  export function buildCollection<
35
33
  M extends Record<string, unknown> = Record<string, unknown>,
36
34
  USER extends User = User>
37
35
  (
38
- collection: EntityCollection<M, USER>
39
- ): EntityCollection<M, USER> {
36
+ collection: CollectionConfig<M, USER>
37
+ ): CollectionConfig<M, USER> {
40
38
  return collection;
41
39
  }
42
40
 
@@ -74,8 +72,8 @@ export function defineCollection<
74
72
  const P extends PostgresProperties,
75
73
  USER extends User = User
76
74
  >(
77
- collection: Omit<PostgresCollection<InferEntityType<P>, USER>, "properties"> & { properties: P }
78
- ): PostgresCollection<InferEntityType<P>, USER> & { properties: P };
75
+ collection: Omit<PostgresCollectionConfig<InferEntityType<P>, USER>, "properties"> & { properties: P }
76
+ ): PostgresCollectionConfig<InferEntityType<P>, USER> & { properties: P };
79
77
 
80
78
  /**
81
79
  * Define a Firestore-backed collection with full type inference.
@@ -85,8 +83,8 @@ export function defineCollection<
85
83
  const P extends FirebaseProperties,
86
84
  USER extends User = User
87
85
  >(
88
- collection: Omit<FirebaseCollection<InferEntityType<P>, USER>, "properties"> & { properties: P }
89
- ): FirebaseCollection<InferEntityType<P>, USER> & { properties: P };
86
+ collection: Omit<FirebaseCollectionConfig<InferEntityType<P>, USER>, "properties"> & { properties: P }
87
+ ): FirebaseCollectionConfig<InferEntityType<P>, USER> & { properties: P };
90
88
 
91
89
  /**
92
90
  * Define a MongoDB-backed collection with full type inference.
@@ -96,23 +94,25 @@ export function defineCollection<
96
94
  const P extends MongoProperties,
97
95
  USER extends User = User
98
96
  >(
99
- collection: Omit<MongoDBCollection<InferEntityType<P>, USER>, "properties"> & { properties: P }
100
- ): MongoDBCollection<InferEntityType<P>, USER> & { properties: P };
97
+ collection: Omit<MongoDBCollectionConfig<InferEntityType<P>, USER>, "properties"> & { properties: P }
98
+ ): MongoDBCollectionConfig<InferEntityType<P>, USER> & { properties: P };
101
99
 
102
100
  /**
103
101
  * Implementation — delegates to the correct overload at the type level.
104
102
  * At runtime this is a plain identity function.
105
103
  */
106
104
  export function defineCollection(
107
- collection: EntityCollection
108
- ): EntityCollection {
105
+ collection: CollectionConfig
106
+ ): CollectionConfig {
109
107
  return collection;
110
108
  }
111
109
 
112
110
  /**
113
- * Identity function we use to defeat the type system of Typescript and preserve
114
- * the property keys.
115
- * @param property
111
+ * @deprecated Use plain typed property objects with {@link defineCollection}
112
+ * instead — `defineCollection` infers property types automatically, making
113
+ * this wrapper unnecessary. `buildProperty` is kept for FireCMS migration
114
+ * compatibility and will be removed before 1.0.
115
+ *
116
116
  * @group Builder
117
117
  */
118
118
  export function buildProperty<T, P extends Property = Property>(
@@ -130,77 +130,3 @@ export function buildProperty<T, P extends Property = Property>(
130
130
  // SAFETY: Identity function — P is a subtype of the conditional return type by definition
131
131
  return property as unknown as ReturnType<typeof buildProperty<T, P>>;
132
132
  }
133
-
134
- /**
135
- * Identity function we use to defeat the type system of Typescript and preserve
136
- * the properties keys.
137
- * @param properties
138
- * @group Builder
139
- */
140
- export function buildProperties<M extends Record<string, unknown>>(
141
- properties: Properties
142
- ): Properties {
143
- return properties;
144
- }
145
-
146
- /**
147
- * Identity function we use to defeat the type system of Typescript and preserve
148
- * the properties keys.
149
- * @param propertiesOrBuilder
150
- * @group Builder
151
- */
152
- export function buildPropertiesOrBuilder<M extends Record<string, unknown>>(
153
- propertiesOrBuilder: Properties
154
- ): Properties {
155
- return propertiesOrBuilder;
156
- }
157
-
158
- /**
159
- * Identity function we use to defeat the type system of Typescript and preserve
160
- * the properties keys.
161
- * @param enumValues
162
- * @group Builder
163
- */
164
- export function buildEnum(
165
- enumValues: EnumValues
166
- ): EnumValues {
167
- return enumValues;
168
- }
169
-
170
- /**
171
- * Identity function we use to defeat the type system of Typescript and preserve
172
- * the properties keys.
173
- * @param enumValueConfig
174
- * @group Builder
175
- */
176
- export function buildEnumValueConfig(
177
- enumValueConfig: EnumValueConfig
178
- ): EnumValueConfig {
179
- return enumValueConfig;
180
- }
181
-
182
- /**
183
- * Identity function we use to defeat the type system of Typescript and preserve
184
- * the properties keys.
185
- * @param callbacks
186
- * @group Builder
187
- */
188
- export function buildEntityCallbacks<M extends Record<string, unknown> = Record<string, unknown>>(
189
- callbacks: EntityCallbacks<M>
190
- ): EntityCallbacks<M> {
191
- return callbacks;
192
- }
193
-
194
- /**
195
- * Identity function we use to defeat the type system of Typescript and build
196
- * additional field delegates views with all its properties
197
- * @param additionalFieldDelegate
198
- * @group Builder
199
- */
200
- export function buildAdditionalFieldDelegate<M extends Record<string, unknown>, USER extends User = User>(
201
- additionalFieldDelegate: AdditionalFieldDelegate<M, USER>
202
- ): AdditionalFieldDelegate<M, USER> {
203
- return additionalFieldDelegate;
204
- }
205
-
206
-
@@ -1,4 +1,4 @@
1
- import { EntityCallbacks, Properties, RebaseCallContext } from "@rebasepro/types";
1
+ import { CollectionCallbacks, Properties, RebaseCallContext } from "@rebasepro/types";
2
2
 
3
3
  /**
4
4
  * Context passed to entity lifecycle callbacks.
@@ -85,24 +85,24 @@ async function processProperties(
85
85
 
86
86
  /**
87
87
  * Helper function to extract field-level PropertyCallbacks from a properties schema
88
- * and wrap them into an EntityCallbacks object recursively.
88
+ * and wrap them into an CollectionCallbacks object recursively.
89
89
  */
90
- export const buildPropertyCallbacks = (properties: Properties): EntityCallbacks | undefined => {
90
+ export const buildPropertyCallbacks = (properties: Properties): CollectionCallbacks | undefined => {
91
91
  if (!properties) return undefined;
92
92
 
93
- const propertyCallbacks: EntityCallbacks = {};
93
+ const propertyCallbacks: CollectionCallbacks = {};
94
94
 
95
95
  if (hasPropertyCallbacks(properties, "afterRead")) {
96
96
  propertyCallbacks.afterRead = async (props) => {
97
+ const row = props.row;
97
98
  const processedValues = await processProperties(
98
99
  properties,
99
- props.entity.values as Record<string, unknown>,
100
- props.entity.values as Record<string, unknown>,
100
+ row,
101
+ row,
101
102
  props as unknown,
102
103
  "afterRead"
103
104
  );
104
- return { ...props.entity,
105
- values: processedValues };
105
+ return { ...props.row, ...processedValues };
106
106
  };
107
107
  }
108
108
 
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DefaultSelectedViewBuilder,
3
3
  DefaultSelectedViewParams,
4
- EntityCollection,
4
+ CollectionConfig,
5
5
  Properties,
6
6
  Property
7
7
  } from "@rebasepro/types";
@@ -99,7 +99,7 @@ export function resolveDefaultSelectedView(
99
99
  }
100
100
 
101
101
 
102
- export function getLocalChangesBackup(collection: EntityCollection) {
102
+ export function getLocalChangesBackup(collection: CollectionConfig) {
103
103
  if (!collection.localChangesBackup) {
104
104
  return "manual_apply";
105
105
  }
@@ -108,12 +108,12 @@ export function getLocalChangesBackup(collection: EntityCollection) {
108
108
  }
109
109
 
110
110
  /**
111
- * Returns the primary keys for an entity collection by inspecting the properties
111
+ * Returns the primary keys for a entity collection by inspecting the properties
112
112
  * and finding any properties with `isId`.
113
113
  * Fallbacks to `["id"]` if no properties are marked as `isId: true`.
114
114
  * @param collection
115
115
  */
116
- export function getPrimaryKeys<M extends Record<string, unknown>>(collection: EntityCollection<M>): Extract<keyof M, string>[] {
116
+ export function getPrimaryKeys<M extends Record<string, unknown>>(collection: CollectionConfig<M>): Extract<keyof M, string>[] {
117
117
  const properties = collection.properties;
118
118
  if (!properties) {
119
119
  return ["id"] as Extract<keyof M, string>[];
@@ -81,7 +81,7 @@ export function getDefaultValueFortype(type: DataType): unknown {
81
81
  }
82
82
 
83
83
  /**
84
- * Update the automatic values in an entity before save
84
+ * Update the automatic values in a entity before save
85
85
  * @group Driver
86
86
  */
87
87
  export function updateDateAutoValues<M extends Record<string, unknown>>({
@@ -117,7 +117,7 @@ export function updateDateAutoValues<M extends Record<string, unknown>>({
117
117
  }
118
118
 
119
119
  /**
120
- * Add missing required fields, expected in the collection, to the values of an entity
120
+ * Add missing required fields, expected in the collection, to the values of a entity
121
121
  * @param values
122
122
  * @param properties
123
123
  * @group Driver
@@ -148,7 +148,7 @@ export function getReferenceFrom<M extends Record<string, unknown>>(entity: Enti
148
148
  }
149
149
 
150
150
  export function getRelationFrom<M extends Record<string, unknown>>(entity: Entity<M>): EntityRelation {
151
- return new EntityRelation(entity.id, entity.path, entity);
151
+ return new EntityRelation(entity.id, entity.path, entity as unknown as Record<string, unknown>);
152
152
  }
153
153
 
154
154
  /**
@@ -179,7 +179,7 @@ export function normalizeToEntityRelation(value: unknown, propertyType?: string)
179
179
  return new EntityRelation(
180
180
  obj.id as string | number,
181
181
  obj.path as string,
182
- obj.data as Entity | undefined
182
+ obj.data as Record<string, unknown> | undefined
183
183
  );
184
184
  }
185
185
 
@@ -0,0 +1,81 @@
1
+ import {
2
+ ALL_WHERE_FILTER_OPS,
3
+ DataType,
4
+ getDataSourceCapabilities,
5
+ Property,
6
+ WhereFilterOp
7
+ } from "@rebasepro/types";
8
+
9
+ /**
10
+ * Default operators offered per property type, before engine capabilities and
11
+ * per-property narrowing are applied. These mirror what the built-in filter
12
+ * fields can render.
13
+ */
14
+ const COMPARISON_OPS: readonly WhereFilterOp[] = ["==", "!=", ">", ">=", "<", "<="];
15
+ const NULL_CHECK_OPS: readonly WhereFilterOp[] = ["is-null", "is-not-null"];
16
+ const MEMBERSHIP_OPS: readonly WhereFilterOp[] = ["in", "not-in"];
17
+ const PATTERN_OPS: readonly WhereFilterOp[] = ["like", "ilike", "not-like", "not-ilike"];
18
+
19
+ const DEFAULT_OPS_BY_TYPE: Partial<Record<DataType, readonly WhereFilterOp[]>> = {
20
+ string: [...COMPARISON_OPS, ...MEMBERSHIP_OPS, ...PATTERN_OPS, ...NULL_CHECK_OPS],
21
+ number: [...COMPARISON_OPS, ...MEMBERSHIP_OPS, ...NULL_CHECK_OPS],
22
+ date: [...COMPARISON_OPS, ...NULL_CHECK_OPS],
23
+ boolean: ["==", "!=", ...NULL_CHECK_OPS],
24
+ reference: ["==", "!=", ...MEMBERSHIP_OPS, ...NULL_CHECK_OPS],
25
+ relation: ["==", "!=", ...MEMBERSHIP_OPS, ...NULL_CHECK_OPS]
26
+ // geopoint, map, vector, binary, array (as a container): not filterable
27
+ // through the generic filter UI.
28
+ };
29
+
30
+ /** Operators offered when the property is an *array of* a filterable type. */
31
+ const ARRAY_OPS: readonly WhereFilterOp[] = ["array-contains", "array-contains-any"];
32
+
33
+ export interface ResolveFilterOperatorsParams {
34
+ /**
35
+ * The property to filter on. For array properties, pass the **item**
36
+ * property (`property.of`) together with `isArray: true` — the same
37
+ * convention the filter field dispatchers use.
38
+ */
39
+ property: Property;
40
+ /** True when filtering an array of `property`. */
41
+ isArray?: boolean;
42
+ /**
43
+ * The engine backing the collection (`collection.engine`, e.g.
44
+ * `"postgres"`, `"firestore"`). Falls back to the default engine's
45
+ * capabilities when omitted.
46
+ */
47
+ engine?: string;
48
+ }
49
+
50
+ /**
51
+ * Resolve which filter operators the UI should offer for a property.
52
+ *
53
+ * The result is the **intersection** of three sets:
54
+ * 1. what the engine can execute — {@link DataSourceCapabilities.filterOperators}
55
+ * (e.g. Firestore cannot run the LIKE family);
56
+ * 2. what makes sense for the property type (e.g. no `>` on booleans);
57
+ * 3. the developer's optional narrowing — `property.ui.filterOperators`.
58
+ *
59
+ * Returns an empty array when the property is not filterable (either by
60
+ * type, or because the developer disabled it with `filterOperators: []`).
61
+ *
62
+ * @group Models
63
+ */
64
+ export function resolveFilterOperators({
65
+ property,
66
+ isArray,
67
+ engine
68
+ }: ResolveFilterOperatorsParams): WhereFilterOp[] {
69
+ const typeDefaults: readonly WhereFilterOp[] = isArray
70
+ ? ARRAY_OPS
71
+ : DEFAULT_OPS_BY_TYPE[property.type] ?? [];
72
+ if (typeDefaults.length === 0) return [];
73
+
74
+ const engineOps = new Set(getDataSourceCapabilities(engine).filterOperators ?? ALL_WHERE_FILTER_OPS);
75
+
76
+ const narrowing = property.ui?.filterOperators;
77
+ const narrowingSet = narrowing !== undefined ? new Set(narrowing) : undefined;
78
+
79
+ return typeDefaults.filter(op =>
80
+ engineOps.has(op) && (narrowingSet === undefined || narrowingSet.has(op)));
81
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Row identity: the address of a row, and how to derive it.
3
+ *
4
+ * Postgres has no `id`. A row is identified by its primary key — one or more
5
+ * columns, with any names and any types. `id` is something we synthesize on top
6
+ * of that: a single string token, because the admin needs *one* value it can put
7
+ * in a URL (`/products/1:::2`), use as a cache key, and hang a relation ref off.
8
+ *
9
+ * That token is an address, not data. It is derived from the row's columns and
10
+ * never stored in them — a row is exactly its columns, with their real types.
11
+ * Writing the address back into the row is what used to rename primary keys
12
+ * (`sku` → `id`) and restringify them (`42` → `"42"`) on the way out.
13
+ *
14
+ * These live in `common` because both sides need them and must agree exactly:
15
+ * the driver parses an incoming address back into key columns, and the admin
16
+ * derives the address from a row it was served.
17
+ */
18
+
19
+ /**
20
+ * A primary-key column: its name, the type it round-trips as, and whether it is
21
+ * a UUID (which is a string despite sometimes being described as an id "number").
22
+ */
23
+ export interface PrimaryKeyInfo {
24
+ fieldName: string;
25
+ type: "string" | "number";
26
+ isUUID?: boolean;
27
+ }
28
+
29
+ /** Separator between the parts of a composite address. */
30
+ export const COMPOSITE_ID_SEPARATOR = ":::";
31
+
32
+ /**
33
+ * Derive a row's address from its key columns.
34
+ *
35
+ * Single key → the value as a string. Composite → each part joined by
36
+ * {@link COMPOSITE_ID_SEPARATOR}, in primary-key order, which is what
37
+ * {@link parseIdValues} expects to invert.
38
+ */
39
+ export function buildCompositeId(values: Record<string, unknown>, primaryKeys: PrimaryKeyInfo[]): string {
40
+ if (primaryKeys.length === 0) {
41
+ return "";
42
+ }
43
+ if (primaryKeys.length === 1) {
44
+ return String(values[primaryKeys[0].fieldName] ?? "");
45
+ }
46
+ return primaryKeys.map(pk => String(values[pk.fieldName] ?? "")).join(COMPOSITE_ID_SEPARATOR);
47
+ }
48
+
49
+ /**
50
+ * Invert {@link buildCompositeId}: turn an address back into key columns, each
51
+ * coerced to the type its column actually round-trips as.
52
+ *
53
+ * This is the boundary where a URL segment becomes a query parameter, so a
54
+ * malformed address must throw rather than silently produce a query that
55
+ * matches the wrong row (or none).
56
+ */
57
+ export function parseIdValues(idValue: string | number, primaryKeys: PrimaryKeyInfo[]): Record<string, string | number> {
58
+ const result: Record<string, string | number> = {};
59
+
60
+ if (primaryKeys.length === 0) {
61
+ return result;
62
+ }
63
+
64
+ if (primaryKeys.length === 1) {
65
+ const pk = primaryKeys[0];
66
+ if (pk.type === "number" && !pk.isUUID) {
67
+ const parsed = typeof idValue === "number" ? idValue : parseInt(String(idValue), 10);
68
+ if (isNaN(parsed)) {
69
+ throw new Error(`Invalid numeric ID: ${idValue}`);
70
+ }
71
+ result[pk.fieldName] = parsed;
72
+ } else {
73
+ result[pk.fieldName] = String(idValue);
74
+ }
75
+ return result;
76
+ }
77
+
78
+ // Composite key
79
+ const parts = String(idValue).split(COMPOSITE_ID_SEPARATOR);
80
+ if (parts.length !== primaryKeys.length) {
81
+ throw new Error(`Composite ID parts mismatch. Expected ${primaryKeys.length}, got ${parts.length} for ID: ${idValue}`);
82
+ }
83
+
84
+ for (let i = 0; i < primaryKeys.length; i++) {
85
+ const pk = primaryKeys[i];
86
+ const val = parts[i];
87
+ if (pk.type === "number" && !pk.isUUID) {
88
+ const parsed = parseInt(val, 10);
89
+ if (isNaN(parsed)) {
90
+ throw new Error(`Invalid numeric ID component: ${val}`);
91
+ }
92
+ result[pk.fieldName] = parsed;
93
+ } else {
94
+ result[pk.fieldName] = val;
95
+ }
96
+ }
97
+
98
+ return result;
99
+ }
100
+
101
+ /**
102
+ * The primary keys of a collection, as declared by its properties.
103
+ *
104
+ * This is the only tier both sides can read, because it is the only one written
105
+ * in the config: the postgres driver can also infer keys from the Drizzle
106
+ * schema, which the browser never sees and is never sent — the admin compiles
107
+ * the collection files into its own bundle rather than being served them. A key
108
+ * that lives only in the Drizzle schema is therefore invisible here, and the
109
+ * server says so at boot (`warnOnKeysTheAdminCannotResolve`) naming the `isId`
110
+ * to add.
111
+ *
112
+ * Returns an empty array when a collection declares none, which callers must
113
+ * treat as "not addressable" rather than defaulting to `id`: guessing a key
114
+ * that is not the real one produces confidently wrong addresses.
115
+ */
116
+ export function getDeclaredPrimaryKeys(collection: {
117
+ properties?: Record<string, unknown>;
118
+ }): PrimaryKeyInfo[] {
119
+ const properties = collection.properties;
120
+ if (!properties) return [];
121
+
122
+ const keys: PrimaryKeyInfo[] = [];
123
+ for (const [fieldName, propRaw] of Object.entries(properties)) {
124
+ const prop = propRaw as { type?: string; isId?: unknown } | undefined;
125
+ if (!prop || typeof prop !== "object") continue;
126
+ if (!("isId" in prop) || !prop.isId) continue;
127
+ keys.push({
128
+ fieldName,
129
+ type: prop.type === "number" ? "number" : "string",
130
+ isUUID: prop.isId === "uuid"
131
+ });
132
+ }
133
+ return keys;
134
+ }
135
+
136
+ /**
137
+ * The keys to address a collection's rows with, resolved the way the driver
138
+ * resolves them — minus the tier the browser cannot reach.
139
+ *
140
+ * The postgres driver tries, in order: properties marked `isId`; the primary
141
+ * keys of the Drizzle schema; and finally a column literally named `id`. Only
142
+ * the first and last are visible in a `CollectionConfig`, which is what both
143
+ * sides share.
144
+ *
145
+ * So the two agree except on a collection that declares no `isId` and whose key
146
+ * is known only to Drizzle. There, the driver reads the real key, and this
147
+ * either resolves nothing (reported to the console by the caller) or — if the
148
+ * table happens to have an unrelated `id` property — resolves `id`, which is
149
+ * the wrong key and cannot be detected from here: the addresses look right and
150
+ * route wrong. Only the config can settle it, so the server names both cases
151
+ * at boot (`warnOnKeysTheAdminCannotResolve`) with the `isId` to add.
152
+ */
153
+ export function resolvePrimaryKeys(collection: {
154
+ properties?: Record<string, unknown>;
155
+ }): PrimaryKeyInfo[] {
156
+ const declared = getDeclaredPrimaryKeys(collection);
157
+ if (declared.length > 0) return declared;
158
+
159
+ const idProp = collection.properties?.id as { type?: string } | undefined;
160
+ if (idProp && typeof idProp === "object") {
161
+ return [{ fieldName: "id",
162
+ type: idProp.type === "number" ? "number" : "string" }];
163
+ }
164
+
165
+ return [];
166
+ }
package/src/util/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./collections";
2
2
  export * from "./common";
3
3
  export * from "./entities";
4
+ export * from "./identity";
4
5
  export * from "./enums";
5
6
  export * from "./paths";
6
7
  export * from "./resolutions";
@@ -13,5 +14,8 @@ export * from "./builders";
13
14
  export * from "./storage";
14
15
  export * from "./callbacks";
15
16
  export * from "./relations";
17
+ export * from "./auth-default-policies";
18
+ export * from "./junction-policies";
16
19
  export * from "./conditions";
17
20
  export * from "./navigation_utils";
21
+ export * from "./filter-operator-resolution";