@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.
- package/README.md +5 -5
- package/dist/collections/CollectionRegistry.d.ts +16 -16
- package/dist/collections/default-collections.d.ts +5 -1
- package/dist/data/buildRebaseData.d.ts +44 -3
- package/dist/data/buildRoutedRebaseData.d.ts +14 -9
- package/dist/data/filter-dialect.d.ts +18 -4
- package/dist/data/query_builder.d.ts +1 -1
- package/dist/data/resolveDataSource.d.ts +1 -1
- package/dist/data/sort-dialect.d.ts +41 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +1236 -179
- package/dist/index.es.js.map +1 -1
- package/dist/util/auth-default-policies.d.ts +22 -0
- package/dist/util/builders.d.ts +19 -56
- package/dist/util/callbacks.d.ts +3 -3
- package/dist/util/collections.d.ts +4 -4
- package/dist/util/entities.d.ts +2 -2
- package/dist/util/filter-operator-resolution.d.ts +32 -0
- package/dist/util/identity.d.ts +83 -0
- package/dist/util/index.d.ts +4 -0
- package/dist/util/junction-policies.d.ts +108 -0
- package/dist/util/navigation_from_path.d.ts +4 -4
- package/dist/util/navigation_utils.d.ts +3 -3
- package/dist/util/parent_references_from_path.d.ts +2 -2
- package/dist/util/permissions.d.ts +6 -6
- package/dist/util/policy/evaluatePolicy.d.ts +8 -1
- package/dist/util/policy/index.d.ts +1 -0
- package/dist/util/policy/policyToPostgres.d.ts +14 -2
- package/dist/util/policy/sqlToPolicy.d.ts +24 -14
- package/dist/util/references.d.ts +2 -2
- package/dist/util/relations.d.ts +5 -5
- package/dist/util/resolutions.d.ts +2 -2
- package/package.json +7 -8
- package/src/collections/CollectionRegistry.ts +36 -36
- package/src/collections/default-collections.ts +2 -0
- package/src/data/buildRebaseData.ts +430 -60
- package/src/data/buildRoutedRebaseData.ts +22 -16
- package/src/data/filter-dialect.ts +151 -60
- package/src/data/query_builder.ts +11 -2
- package/src/data/resolveDataSource.ts +1 -1
- package/src/data/sort-dialect.ts +56 -0
- package/src/index.ts +1 -0
- package/src/util/auth-default-policies.ts +152 -0
- package/src/util/builders.ts +25 -99
- package/src/util/callbacks.ts +8 -8
- package/src/util/collections.ts +4 -4
- package/src/util/entities.ts +4 -4
- package/src/util/filter-operator-resolution.ts +81 -0
- package/src/util/identity.ts +166 -0
- package/src/util/index.ts +4 -0
- package/src/util/junction-policies.ts +353 -0
- package/src/util/navigation_from_path.ts +4 -4
- package/src/util/navigation_utils.ts +8 -8
- package/src/util/parent_references_from_path.ts +3 -3
- package/src/util/permissions.test.ts +2 -2
- package/src/util/permissions.ts +7 -7
- package/src/util/policy/evaluatePolicy.ts +26 -4
- package/src/util/policy/index.ts +1 -0
- package/src/util/policy/policyToPostgres.ts +123 -17
- package/src/util/policy/sqlToPolicy.ts +190 -13
- package/src/util/references.ts +2 -2
- package/src/util/relations.ts +12 -12
- package/src/util/resolutions.ts +5 -5
- package/dist/index.umd.js +0 -2901
- package/dist/index.umd.js.map +0 -1
package/src/util/builders.ts
CHANGED
|
@@ -1,23 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AdditionalFieldDelegate,
|
|
3
2
|
ArrayProperty,
|
|
4
3
|
BooleanProperty,
|
|
5
4
|
DateProperty,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
EnumValueConfig,
|
|
9
|
-
EnumValues,
|
|
10
|
-
FirebaseCollection,
|
|
5
|
+
CollectionConfig,
|
|
6
|
+
FirebaseCollectionConfig,
|
|
11
7
|
FirebaseProperties,
|
|
12
8
|
GeopointProperty,
|
|
13
9
|
InferEntityType,
|
|
14
10
|
MapProperty,
|
|
15
|
-
|
|
11
|
+
MongoDBCollectionConfig,
|
|
16
12
|
MongoProperties,
|
|
17
13
|
NumberProperty,
|
|
18
|
-
|
|
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
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
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:
|
|
39
|
-
):
|
|
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<
|
|
78
|
-
):
|
|
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<
|
|
89
|
-
):
|
|
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<
|
|
100
|
-
):
|
|
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:
|
|
108
|
-
):
|
|
105
|
+
collection: CollectionConfig
|
|
106
|
+
): CollectionConfig {
|
|
109
107
|
return collection;
|
|
110
108
|
}
|
|
111
109
|
|
|
112
110
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
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
|
-
|
package/src/util/callbacks.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
88
|
+
* and wrap them into an CollectionCallbacks object recursively.
|
|
89
89
|
*/
|
|
90
|
-
export const buildPropertyCallbacks = (properties: Properties):
|
|
90
|
+
export const buildPropertyCallbacks = (properties: Properties): CollectionCallbacks | undefined => {
|
|
91
91
|
if (!properties) return undefined;
|
|
92
92
|
|
|
93
|
-
const propertyCallbacks:
|
|
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
|
-
|
|
100
|
-
|
|
100
|
+
row,
|
|
101
|
+
row,
|
|
101
102
|
props as unknown,
|
|
102
103
|
"afterRead"
|
|
103
104
|
);
|
|
104
|
-
return { ...props.
|
|
105
|
-
values: processedValues };
|
|
105
|
+
return { ...props.row, ...processedValues };
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
|
package/src/util/collections.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DefaultSelectedViewBuilder,
|
|
3
3
|
DefaultSelectedViewParams,
|
|
4
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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>[];
|
package/src/util/entities.ts
CHANGED
|
@@ -81,7 +81,7 @@ export function getDefaultValueFortype(type: DataType): unknown {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
* Update the automatic values in
|
|
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
|
|
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
|
|
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";
|