@rebasepro/common 0.6.1 → 0.8.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.
Files changed (46) hide show
  1. package/dist/collections/CollectionRegistry.d.ts +30 -2
  2. package/dist/collections/default-collections.d.ts +255 -2
  3. package/dist/data/buildRoutedRebaseData.d.ts +53 -0
  4. package/dist/data/filter-dialect.d.ts +61 -0
  5. package/dist/data/query_builder.d.ts +4 -4
  6. package/dist/data/resolveDataSource.d.ts +43 -0
  7. package/dist/index.d.ts +4 -0
  8. package/dist/index.es.js +777 -178
  9. package/dist/index.es.js.map +1 -1
  10. package/dist/index.umd.js +793 -176
  11. package/dist/index.umd.js.map +1 -1
  12. package/dist/table-classification.d.ts +47 -0
  13. package/dist/util/builders.d.ts +48 -1
  14. package/dist/util/callbacks.d.ts +6 -1
  15. package/dist/util/index.d.ts +1 -0
  16. package/dist/util/permissions.d.ts +26 -2
  17. package/dist/util/policy/evaluatePolicy.d.ts +31 -0
  18. package/dist/util/policy/index.d.ts +3 -0
  19. package/dist/util/policy/policyToPostgres.d.ts +10 -0
  20. package/dist/util/policy/securityRuleToConditions.d.ts +24 -0
  21. package/dist/util/policy/sqlToPolicy.d.ts +20 -0
  22. package/dist/util/storage.d.ts +26 -1
  23. package/package.json +3 -3
  24. package/src/collections/CollectionRegistry.ts +80 -16
  25. package/src/collections/default-collections.ts +4 -4
  26. package/src/data/buildRebaseData.ts +9 -120
  27. package/src/data/buildRoutedRebaseData.ts +97 -0
  28. package/src/data/filter-dialect.ts +318 -0
  29. package/src/data/query_builder.ts +10 -10
  30. package/src/data/resolveDataSource.ts +79 -0
  31. package/src/index.ts +4 -1
  32. package/src/table-classification.ts +109 -0
  33. package/src/util/builders.ts +78 -1
  34. package/src/util/callbacks.ts +8 -1
  35. package/src/util/index.ts +1 -0
  36. package/src/util/permissions.test.ts +5 -3
  37. package/src/util/permissions.ts +85 -158
  38. package/src/util/policy/evaluatePolicy.ts +146 -0
  39. package/src/util/policy/index.ts +3 -0
  40. package/src/util/policy/policyToPostgres.ts +85 -0
  41. package/src/util/policy/securityRuleToConditions.ts +67 -0
  42. package/src/util/policy/sqlToPolicy.ts +88 -0
  43. package/src/util/references.ts +1 -1
  44. package/src/util/relations.ts +8 -9
  45. package/src/util/resolutions.ts +6 -6
  46. package/src/util/storage.ts +34 -1
@@ -1,5 +1,27 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import { EntityCallbacks, EntityCollection } from "@rebasepro/types";
2
+ import { DataSourceRegistry } from "../data/resolveDataSource";
2
3
  export declare class CollectionRegistry {
4
+ /**
5
+ * Declared data sources, used during normalization to resolve each
6
+ * collection's engine (so `dataSource`-only collections get the right
7
+ * capabilities). Empty by default.
8
+ */
9
+ private dataSources;
10
+ /**
11
+ * Global lifecycle callbacks applied to every collection.
12
+ * Runs on all data paths (REST, WebSocket, `rebase.data`).
13
+ * Execution order: global → collection → property callbacks.
14
+ */
15
+ private _globalCallbacks?;
16
+ /**
17
+ * Set global lifecycle callbacks that apply to every collection.
18
+ * Typically called once during backend initialization.
19
+ */
20
+ setGlobalCallbacks(callbacks: EntityCallbacks): void;
21
+ /**
22
+ * Get the currently registered global callbacks, if any.
23
+ */
24
+ getGlobalCallbacks(): EntityCallbacks | undefined;
3
25
  private collectionsByTableName;
4
26
  private collectionsBySlug;
5
27
  private rootCollections;
@@ -9,7 +31,13 @@ export declare class CollectionRegistry {
9
31
  private rawRootCollections;
10
32
  private cachedRawCollectionsList;
11
33
  private lastRawInputSnapshot;
12
- constructor(collections?: EntityCollection[]);
34
+ constructor(collections?: EntityCollection[], dataSources?: DataSourceRegistry);
35
+ /**
36
+ * Provide the declared data sources used to resolve each collection's
37
+ * engine during normalization. Set this before registering collections.
38
+ * Returns true if the registry changed (callers may re-register).
39
+ */
40
+ setDataSources(dataSources: DataSourceRegistry): boolean;
13
41
  reset(): void;
14
42
  /**
15
43
  * Registers a collection and its subcollections recursively.
@@ -1,4 +1,3 @@
1
- import type { PostgresCollection } from "@rebasepro/types";
2
1
  /**
3
2
  * Default users collection.
4
3
  *
@@ -6,4 +5,258 @@ import type { PostgresCollection } from "@rebasepro/types";
6
5
  * Slug-based dedup (Map keyed by slug, last-write-wins) lets developers
7
6
  * override by defining their own collection with `slug: "users"`.
8
7
  */
9
- export declare const defaultUsersCollection: PostgresCollection;
8
+ export declare const defaultUsersCollection: import("@rebasepro/types").PostgresCollection<import("@rebasepro/types").InferEntityType<{
9
+ readonly id: {
10
+ readonly name: "ID";
11
+ readonly type: "string";
12
+ readonly isId: "uuid";
13
+ readonly ui: {
14
+ readonly readOnly: true;
15
+ };
16
+ };
17
+ readonly email: {
18
+ readonly name: "Email";
19
+ readonly type: "string";
20
+ readonly validation: {
21
+ readonly required: true;
22
+ readonly unique: true;
23
+ };
24
+ };
25
+ readonly displayName: {
26
+ readonly name: "Name";
27
+ readonly type: "string";
28
+ readonly columnName: "display_name";
29
+ readonly validation: {
30
+ readonly required: true;
31
+ };
32
+ };
33
+ readonly photoURL: {
34
+ readonly name: "Photo URL";
35
+ readonly type: "string";
36
+ readonly columnName: "photo_url";
37
+ readonly ui: {
38
+ readonly url: "image";
39
+ };
40
+ };
41
+ readonly roles: {
42
+ readonly name: "Roles";
43
+ readonly type: "array";
44
+ readonly columnType: "text[]";
45
+ readonly of: {
46
+ readonly name: "Role";
47
+ readonly type: "string";
48
+ readonly enum: {
49
+ readonly admin: "Admin";
50
+ readonly editor: "Editor";
51
+ readonly viewer: "Viewer";
52
+ };
53
+ };
54
+ };
55
+ readonly passwordHash: {
56
+ readonly name: "Password Hash";
57
+ readonly type: "string";
58
+ readonly columnName: "password_hash";
59
+ readonly ui: {
60
+ readonly hideFromCollection: true;
61
+ readonly disabled: {
62
+ readonly hidden: true;
63
+ };
64
+ };
65
+ };
66
+ readonly emailVerified: {
67
+ readonly name: "Email Verified";
68
+ readonly type: "boolean";
69
+ readonly columnName: "email_verified";
70
+ readonly defaultValue: false;
71
+ readonly ui: {
72
+ readonly hideFromCollection: true;
73
+ readonly disabled: {
74
+ readonly hidden: true;
75
+ };
76
+ };
77
+ };
78
+ readonly emailVerificationToken: {
79
+ readonly name: "Email Verification Token";
80
+ readonly type: "string";
81
+ readonly columnName: "email_verification_token";
82
+ readonly ui: {
83
+ readonly hideFromCollection: true;
84
+ readonly disabled: {
85
+ readonly hidden: true;
86
+ };
87
+ };
88
+ };
89
+ readonly emailVerificationSentAt: {
90
+ readonly name: "Email Verification Sent At";
91
+ readonly type: "date";
92
+ readonly columnName: "email_verification_sent_at";
93
+ readonly ui: {
94
+ readonly hideFromCollection: true;
95
+ readonly disabled: {
96
+ readonly hidden: true;
97
+ };
98
+ };
99
+ };
100
+ readonly metadata: {
101
+ readonly name: "Metadata";
102
+ readonly type: "map";
103
+ readonly keyValue: true;
104
+ readonly properties: {};
105
+ readonly defaultValue: {};
106
+ readonly ui: {
107
+ readonly hideFromCollection: true;
108
+ readonly disabled: {
109
+ readonly hidden: true;
110
+ };
111
+ };
112
+ };
113
+ readonly createdAt: {
114
+ readonly name: "Created At";
115
+ readonly type: "date";
116
+ readonly columnName: "created_at";
117
+ readonly autoValue: "on_create";
118
+ readonly ui: {
119
+ readonly readOnly: true;
120
+ };
121
+ };
122
+ readonly updatedAt: {
123
+ readonly name: "Updated At";
124
+ readonly type: "date";
125
+ readonly columnName: "updated_at";
126
+ readonly autoValue: "on_update";
127
+ readonly ui: {
128
+ readonly hideFromCollection: true;
129
+ readonly disabled: {
130
+ readonly hidden: true;
131
+ };
132
+ };
133
+ };
134
+ }>, import("@rebasepro/types").User> & {
135
+ properties: {
136
+ readonly id: {
137
+ readonly name: "ID";
138
+ readonly type: "string";
139
+ readonly isId: "uuid";
140
+ readonly ui: {
141
+ readonly readOnly: true;
142
+ };
143
+ };
144
+ readonly email: {
145
+ readonly name: "Email";
146
+ readonly type: "string";
147
+ readonly validation: {
148
+ readonly required: true;
149
+ readonly unique: true;
150
+ };
151
+ };
152
+ readonly displayName: {
153
+ readonly name: "Name";
154
+ readonly type: "string";
155
+ readonly columnName: "display_name";
156
+ readonly validation: {
157
+ readonly required: true;
158
+ };
159
+ };
160
+ readonly photoURL: {
161
+ readonly name: "Photo URL";
162
+ readonly type: "string";
163
+ readonly columnName: "photo_url";
164
+ readonly ui: {
165
+ readonly url: "image";
166
+ };
167
+ };
168
+ readonly roles: {
169
+ readonly name: "Roles";
170
+ readonly type: "array";
171
+ readonly columnType: "text[]";
172
+ readonly of: {
173
+ readonly name: "Role";
174
+ readonly type: "string";
175
+ readonly enum: {
176
+ readonly admin: "Admin";
177
+ readonly editor: "Editor";
178
+ readonly viewer: "Viewer";
179
+ };
180
+ };
181
+ };
182
+ readonly passwordHash: {
183
+ readonly name: "Password Hash";
184
+ readonly type: "string";
185
+ readonly columnName: "password_hash";
186
+ readonly ui: {
187
+ readonly hideFromCollection: true;
188
+ readonly disabled: {
189
+ readonly hidden: true;
190
+ };
191
+ };
192
+ };
193
+ readonly emailVerified: {
194
+ readonly name: "Email Verified";
195
+ readonly type: "boolean";
196
+ readonly columnName: "email_verified";
197
+ readonly defaultValue: false;
198
+ readonly ui: {
199
+ readonly hideFromCollection: true;
200
+ readonly disabled: {
201
+ readonly hidden: true;
202
+ };
203
+ };
204
+ };
205
+ readonly emailVerificationToken: {
206
+ readonly name: "Email Verification Token";
207
+ readonly type: "string";
208
+ readonly columnName: "email_verification_token";
209
+ readonly ui: {
210
+ readonly hideFromCollection: true;
211
+ readonly disabled: {
212
+ readonly hidden: true;
213
+ };
214
+ };
215
+ };
216
+ readonly emailVerificationSentAt: {
217
+ readonly name: "Email Verification Sent At";
218
+ readonly type: "date";
219
+ readonly columnName: "email_verification_sent_at";
220
+ readonly ui: {
221
+ readonly hideFromCollection: true;
222
+ readonly disabled: {
223
+ readonly hidden: true;
224
+ };
225
+ };
226
+ };
227
+ readonly metadata: {
228
+ readonly name: "Metadata";
229
+ readonly type: "map";
230
+ readonly keyValue: true;
231
+ readonly properties: {};
232
+ readonly defaultValue: {};
233
+ readonly ui: {
234
+ readonly hideFromCollection: true;
235
+ readonly disabled: {
236
+ readonly hidden: true;
237
+ };
238
+ };
239
+ };
240
+ readonly createdAt: {
241
+ readonly name: "Created At";
242
+ readonly type: "date";
243
+ readonly columnName: "created_at";
244
+ readonly autoValue: "on_create";
245
+ readonly ui: {
246
+ readonly readOnly: true;
247
+ };
248
+ };
249
+ readonly updatedAt: {
250
+ readonly name: "Updated At";
251
+ readonly type: "date";
252
+ readonly columnName: "updated_at";
253
+ readonly autoValue: "on_update";
254
+ readonly ui: {
255
+ readonly hideFromCollection: true;
256
+ readonly disabled: {
257
+ readonly hidden: true;
258
+ };
259
+ };
260
+ };
261
+ };
262
+ };
@@ -0,0 +1,53 @@
1
+ import { RebaseData } from "@rebasepro/types";
2
+ /**
3
+ * Parameters for {@link buildRoutedRebaseData}.
4
+ */
5
+ export interface RoutedRebaseDataParams {
6
+ /**
7
+ * The default data source. Handles every collection that does not
8
+ * resolve to an entry in `sources` (i.e. server-transport collections,
9
+ * which ride the Rebase client).
10
+ */
11
+ defaultData: RebaseData;
12
+ /**
13
+ * Per-data-source {@link RebaseData} instances for direct and custom
14
+ * transports, keyed by data-source key (e.g. `"analytics"`). Server-
15
+ * mediated sources are not listed here — they fall through to
16
+ * `defaultData`.
17
+ */
18
+ sources: Record<string, RebaseData>;
19
+ /**
20
+ * Resolve the data-source key for a given collection slug or path.
21
+ * Typically backed by the collection registry + `resolveDataSource`
22
+ * (`resolveDataSource(registry.getCollection(path), defs).key`).
23
+ *
24
+ * Return `undefined` (or a key absent from `sources`) to route to the
25
+ * default data source.
26
+ */
27
+ resolveKey: (slugOrPath: string) => string | undefined;
28
+ }
29
+ /**
30
+ * Build a {@link RebaseData} that routes each collection to the right
31
+ * backend based on its resolved data source.
32
+ *
33
+ * `.collection(path)` (and dynamic `data.products`-style access) resolves the
34
+ * collection's data-source key via `resolveKey` and delegates to the matching
35
+ * entry in `sources`, falling back to `defaultData` when there is no match.
36
+ * Because routing keys off the *path being accessed*, a reference widget
37
+ * inside a Firestore form that points at a Postgres collection is still
38
+ * served by Postgres — routing follows the target, not the ancestor.
39
+ *
40
+ * When `sources` is empty this returns `defaultData` untouched, so the
41
+ * single-driver setup keeps identical behaviour and identity (important for
42
+ * effect dependencies that key off the data instance).
43
+ *
44
+ * @example
45
+ * const data = buildRoutedRebaseData({
46
+ * defaultData: client.data,
47
+ * sources: { analytics: buildRebaseData(firestoreDriver) },
48
+ * resolveKey: (path) => resolveDataSource(registry.getCollection(path), defs).key
49
+ * });
50
+ * await data.products.find(); // → default (server / Postgres)
51
+ * await data.events.find(); // → Firestore, if `events.dataSource === "analytics"`
52
+ */
53
+ export declare function buildRoutedRebaseData({ defaultData, sources, resolveKey }: RoutedRebaseDataParams): RebaseData;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * REST wire-format adapter for the unified filter system.
3
+ *
4
+ * This module is the ONLY code in the entire codebase that knows about
5
+ * PostgREST-style dot-syntax strings (`eq.active`, `gt.18`, `in.(a,b)`).
6
+ * Everything else speaks `FilterValues` exclusively.
7
+ *
8
+ * @module
9
+ */
10
+ import { FilterValues, LogicalCondition, FilterCondition } from "@rebasepro/types";
11
+ /**
12
+ * Convert `FilterValues` to a PostgREST-style querystring record.
13
+ *
14
+ * - Single conditions produce a string value.
15
+ * - Multiple conditions on the same field produce a string array (repeated params).
16
+ *
17
+ * @example
18
+ * serializeFilter({ status: ["==", "active"] })
19
+ * // → { status: "eq.active" }
20
+ *
21
+ * serializeFilter({ age: [[">=", 18], ["<", 65]] })
22
+ * // → { age: ["gte.18", "lt.65"] }
23
+ */
24
+ export declare function serializeFilter(filter: FilterValues<string> | Record<string, any>): Record<string, string | string[]>;
25
+ /**
26
+ * Convert a PostgREST-style querystring record to `FilterValues`.
27
+ *
28
+ * - String values are parsed as single conditions.
29
+ * - String arrays (repeated query params) become multiple conditions on the same field.
30
+ *
31
+ * @example
32
+ * deserializeFilter({ status: "eq.active" })
33
+ * // → { status: ["==", "active"] }
34
+ *
35
+ * deserializeFilter({ age: ["gte.18", "lt.65"] })
36
+ * // → { age: [[">=", 18], ["<", 65]] }
37
+ */
38
+ export declare function deserializeFilter(query: Record<string, any>): FilterValues<string>;
39
+ /**
40
+ * Serialize a `LogicalCondition` or `FilterCondition` to its wire-format string.
41
+ *
42
+ * @example
43
+ * serializeLogicalCondition({ column: "status", operator: "==", value: "active" })
44
+ * // → "status.eq.active"
45
+ *
46
+ * serializeLogicalCondition({ type: "or", conditions: [...] })
47
+ * // → "or(status.eq.active,status.eq.pending)"
48
+ */
49
+ export declare function serializeLogicalCondition(cond: LogicalCondition | FilterCondition): string;
50
+ /**
51
+ * Parse a logical condition wire-format string back into a
52
+ * `LogicalCondition` or `FilterCondition`.
53
+ *
54
+ * @example
55
+ * deserializeLogicalCondition("status.eq.active")
56
+ * // → { column: "status", operator: "==", value: "active" }
57
+ *
58
+ * deserializeLogicalCondition("or(status.eq.active,age.gte.18)")
59
+ * // → { type: "or", conditions: [...] }
60
+ */
61
+ export declare function deserializeLogicalCondition(str: string): LogicalCondition | FilterCondition;
@@ -1,7 +1,7 @@
1
- import { FindResponse, CollectionAccessor, QueryBuilderInterface, FilterOperator, LogicalCondition, WhereValue, FilterCondition } from "@rebasepro/types";
1
+ import { FindResponse, CollectionAccessor, QueryBuilderInterface, WhereFilterOp, LogicalCondition, WhereValue, FilterCondition } from "@rebasepro/types";
2
2
  export declare function or(...conditions: (FilterCondition | LogicalCondition)[]): LogicalCondition;
3
3
  export declare function and(...conditions: (FilterCondition | LogicalCondition)[]): LogicalCondition;
4
- export declare function cond(column: string, operator: FilterOperator, value: unknown): FilterCondition;
4
+ export declare function cond(column: string, operator: WhereFilterOp, value: unknown): FilterCondition;
5
5
  export declare class QueryBuilder<M extends Record<string, unknown> = Record<string, unknown>> implements QueryBuilderInterface<M> {
6
6
  private collection;
7
7
  private params;
@@ -11,14 +11,14 @@ export declare class QueryBuilder<M extends Record<string, unknown> = Record<str
11
11
  * @example
12
12
  * client.collection('users').where('age', '>=', 18).find()
13
13
  */
14
- where<K extends keyof M & string>(column: K, operator: FilterOperator, value: WhereValue<M[K]>): this;
14
+ where<K extends keyof M & string>(column: K, operator: WhereFilterOp, value: WhereValue<M[K]>): this;
15
15
  where(logicalCondition: LogicalCondition): this;
16
16
  /**
17
17
  * Order the results by a specific column.
18
18
  * @example
19
19
  * client.collection('users').orderBy('createdAt', 'desc').find()
20
20
  */
21
- orderBy(column: keyof M & string, ascending?: "asc" | "desc"): this;
21
+ orderBy(column: keyof M & string, direction?: "asc" | "desc"): this;
22
22
  /**
23
23
  * Limit the number of results returned.
24
24
  */
@@ -0,0 +1,43 @@
1
+ import { DataSourceDefinition, ResolvedDataSource } from "@rebasepro/types";
2
+ /**
3
+ * The subset of a collection needed to resolve its data source. Accepting a
4
+ * structural type (rather than the full `EntityCollection`) keeps this usable
5
+ * from anywhere — frontend router, backend registry, editor — without coupling
6
+ * to the collection union.
7
+ */
8
+ export interface DataSourceResolvable {
9
+ /** Preferred routing key. */
10
+ dataSource?: string;
11
+ /** Engine type discriminant (set on variant collection types). */
12
+ engine?: string;
13
+ /** Within-engine instance. */
14
+ databaseId?: string;
15
+ }
16
+ /** A lookup of data-source definitions by key. */
17
+ export type DataSourceRegistry = Record<string, DataSourceDefinition>;
18
+ /**
19
+ * Build a keyed registry from a list of {@link DataSourceDefinition}s.
20
+ * Later entries win on key collision.
21
+ */
22
+ export declare function createDataSourceRegistry(definitions?: DataSourceDefinition[]): DataSourceRegistry;
23
+ /**
24
+ * Resolve the effective data source for a collection — the single source of
25
+ * truth shared by the frontend router, the backend driver registry, and the
26
+ * editor's capability lookups.
27
+ *
28
+ * Resolution order:
29
+ * 1. The routing **key** is `collection.dataSource`, else
30
+ * {@link DEFAULT_DATA_SOURCE_KEY}.
31
+ * 2. If a definition is registered for that key, it provides `engine`,
32
+ * `transport`, and `databaseId`.
33
+ * 3. Otherwise values are synthesized: `engine` from `collection.engine`
34
+ * (or the key, or `"postgres"`), `transport` defaults to `"server"`,
35
+ * and `databaseId` from the collection.
36
+ *
37
+ * `capabilities` are always derived from the resolved `engine`, so two
38
+ * data sources sharing an engine share capabilities.
39
+ *
40
+ * @param collection the collection (or any object carrying the routing fields)
41
+ * @param registry optional registry of declared data sources
42
+ */
43
+ export declare function resolveDataSource(collection: DataSourceResolvable | undefined, registry?: DataSourceRegistry): ResolvedDataSource;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  export * from "./util";
2
2
  export * from "./collections";
3
3
  export * from "./data/buildRebaseData";
4
+ export * from "./data/buildRoutedRebaseData";
5
+ export * from "./data/resolveDataSource";
4
6
  export * from "./data/query_builder";
7
+ export * from "./data/filter-dialect";
8
+ export * from "./table-classification";