@rebasepro/common 0.8.0 → 0.9.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 (53) hide show
  1. package/README.md +4 -4
  2. package/dist/collections/CollectionRegistry.d.ts +16 -16
  3. package/dist/collections/default-collections.d.ts +1 -1
  4. package/dist/data/buildRebaseData.d.ts +30 -2
  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 +569 -159
  12. package/dist/index.es.js.map +1 -1
  13. package/dist/index.umd.js +573 -163
  14. package/dist/index.umd.js.map +1 -1
  15. package/dist/util/builders.d.ts +19 -56
  16. package/dist/util/callbacks.d.ts +3 -3
  17. package/dist/util/collections.d.ts +4 -4
  18. package/dist/util/entities.d.ts +2 -2
  19. package/dist/util/filter-operator-resolution.d.ts +32 -0
  20. package/dist/util/index.d.ts +1 -0
  21. package/dist/util/navigation_from_path.d.ts +4 -4
  22. package/dist/util/navigation_utils.d.ts +3 -3
  23. package/dist/util/parent_references_from_path.d.ts +2 -2
  24. package/dist/util/permissions.d.ts +6 -6
  25. package/dist/util/policy/policyToPostgres.d.ts +14 -2
  26. package/dist/util/references.d.ts +2 -2
  27. package/dist/util/relations.d.ts +5 -5
  28. package/dist/util/resolutions.d.ts +2 -2
  29. package/package.json +3 -3
  30. package/src/collections/CollectionRegistry.ts +36 -36
  31. package/src/data/buildRebaseData.ts +332 -57
  32. package/src/data/buildRoutedRebaseData.ts +22 -16
  33. package/src/data/filter-dialect.ts +145 -60
  34. package/src/data/query_builder.ts +11 -2
  35. package/src/data/resolveDataSource.ts +1 -1
  36. package/src/data/sort-dialect.ts +56 -0
  37. package/src/index.ts +1 -0
  38. package/src/util/builders.ts +25 -99
  39. package/src/util/callbacks.ts +8 -8
  40. package/src/util/collections.ts +4 -4
  41. package/src/util/entities.ts +4 -4
  42. package/src/util/filter-operator-resolution.ts +81 -0
  43. package/src/util/index.ts +1 -0
  44. package/src/util/navigation_from_path.ts +4 -4
  45. package/src/util/navigation_utils.ts +8 -8
  46. package/src/util/parent_references_from_path.ts +3 -3
  47. package/src/util/permissions.test.ts +2 -2
  48. package/src/util/permissions.ts +7 -7
  49. package/src/util/policy/evaluatePolicy.ts +6 -0
  50. package/src/util/policy/policyToPostgres.ts +90 -10
  51. package/src/util/references.ts +2 -2
  52. package/src/util/relations.ts +12 -12
  53. package/src/util/resolutions.ts +5 -5
package/README.md CHANGED
@@ -15,7 +15,7 @@ pnpm add @rebasepro/common
15
15
  - **Collection utilities** — collection registry, default collection definitions, path resolution, navigation helpers
16
16
  - **Data driver adapter** — `buildRebaseData()` bridges any `DataDriver` implementation into a `RebaseData` proxy with typed collection accessors
17
17
  - **Query builder** — fluent `QueryBuilder` class plus `or()`, `and()`, `cond()` helpers for composing complex queries
18
- - **Entity/property utilities** — entity resolution, enum helpers, permission checks, reference/relation helpers, storage path utils, callback utilities
18
+ - **Snapshot/property utilities** — snapshot resolution, enum helpers, permission checks, reference/relation helpers, storage path utils, callback utilities
19
19
 
20
20
  This package has no React dependency — it's pure TypeScript and can be used in both client and server contexts.
21
21
 
@@ -44,7 +44,7 @@ This package has no React dependency — it's pure TypeScript and can be used in
44
44
  |---|---|
45
45
  | `collections` | Collection config helpers |
46
46
  | `common` | General-purpose utilities |
47
- | `entities` | Entity value resolution |
47
+ | `snapshots` | Snapshot value resolution |
48
48
  | `enums` | Enum type helpers |
49
49
  | `paths` | Path parsing and manipulation |
50
50
  | `resolutions` | Property and collection resolution |
@@ -69,7 +69,7 @@ const data = buildRebaseData(myDriver);
69
69
 
70
70
  // Access collections by name (camelCase auto-converts to snake_case)
71
71
  const { data: products } = await data.products.find({ limit: 10 });
72
- const entity = await data.products.findById("abc-123");
72
+ const snapshot = await data.products.findById("abc-123");
73
73
 
74
74
  // Fluent query builder
75
75
  const { data: results } = await data.products
@@ -89,7 +89,7 @@ const { data: filtered } = await data.products
89
89
 
90
90
  ## Related Packages
91
91
 
92
- - [`@rebasepro/types`](../types) — `DataDriver`, `RebaseData`, `CollectionAccessor`, `Entity`, `FindResponse`, etc.
92
+ - [`@rebasepro/types`](../types) — `DataDriver`, `RebaseData`, `CollectionAccessor`, `Snapshot`, `FindResponse`, etc.
93
93
  - [`@rebasepro/utils`](../utils) — Low-level utilities (`toSnakeCase`, etc.)
94
94
  - [`@rebasepro/core`](../core) — Runtime layer that consumes `@rebasepro/common`
95
95
  - [`@rebasepro/client`](../client) — HTTP client that re-exports and extends the `QueryBuilder`
@@ -1,4 +1,4 @@
1
- import { EntityCallbacks, EntityCollection } from "@rebasepro/types";
1
+ import { CollectionCallbacks, CollectionConfig } from "@rebasepro/types";
2
2
  import { DataSourceRegistry } from "../data/resolveDataSource";
3
3
  export declare class CollectionRegistry {
4
4
  /**
@@ -17,11 +17,11 @@ export declare class CollectionRegistry {
17
17
  * Set global lifecycle callbacks that apply to every collection.
18
18
  * Typically called once during backend initialization.
19
19
  */
20
- setGlobalCallbacks(callbacks: EntityCallbacks): void;
20
+ setGlobalCallbacks(callbacks: CollectionCallbacks): void;
21
21
  /**
22
22
  * Get the currently registered global callbacks, if any.
23
23
  */
24
- getGlobalCallbacks(): EntityCallbacks | undefined;
24
+ getGlobalCallbacks(): CollectionCallbacks | undefined;
25
25
  private collectionsByTableName;
26
26
  private collectionsBySlug;
27
27
  private rootCollections;
@@ -30,8 +30,8 @@ export declare class CollectionRegistry {
30
30
  private rawCollectionsBySlug;
31
31
  private rawRootCollections;
32
32
  private cachedRawCollectionsList;
33
- private lastRawInputSnapshot;
34
- constructor(collections?: EntityCollection[], dataSources?: DataSourceRegistry);
33
+ private lastRawInputEntity;
34
+ constructor(collections?: CollectionConfig[], dataSources?: DataSourceRegistry);
35
35
  /**
36
36
  * Provide the declared data sources used to resolve each collection's
37
37
  * engine during normalization. Set this before registering collections.
@@ -44,13 +44,13 @@ export declare class CollectionRegistry {
44
44
  * Returns true if the collections have changed, false otherwise.
45
45
  *
46
46
  * Idempotent: compares the raw input (before normalization) against a stored
47
- * snapshot. Only re-normalizes and re-registers when the raw input actually changed.
47
+ * entity. Only re-normalizes and re-registers when the raw input actually changed.
48
48
  * @param collections
49
49
  */
50
- registerMultiple(collections: EntityCollection[]): boolean;
51
- register(collection: EntityCollection, rawCollection?: EntityCollection): void;
50
+ registerMultiple(collections: CollectionConfig[]): boolean;
51
+ register(collection: CollectionConfig, rawCollection?: CollectionConfig): void;
52
52
  private _registerRecursively;
53
- normalizeCollection(collection: EntityCollection): EntityCollection;
53
+ normalizeCollection(collection: CollectionConfig): CollectionConfig;
54
54
  /**
55
55
  * Extract Relation[] from properties that have inline relation config (i.e. `target` is set).
56
56
  * This allows developers to define relations directly on properties without a separate
@@ -59,26 +59,26 @@ export declare class CollectionRegistry {
59
59
  private extractRelationsFromProperties;
60
60
  private normalizeProperties;
61
61
  private normalizeProperty;
62
- get(path: string): EntityCollection | undefined;
62
+ get(path: string): CollectionConfig | undefined;
63
63
  /**
64
64
  * Gets the pristine, un-normalized collection exactly as it was provided.
65
65
  * Useful for the AST editor so it doesn't accidentally serialize injected metadata back to disk.
66
66
  */
67
- getRaw(path: string): EntityCollection | undefined;
67
+ getRaw(path: string): CollectionConfig | undefined;
68
68
  /**
69
69
  * Get collection by resolving multi-segment paths through relations
70
70
  * e.g., "authors/70/posts" resolves to the posts collection
71
71
  */
72
- getCollectionByPath(collectionPath: string): EntityCollection | undefined;
73
- getCollections(): EntityCollection[];
74
- getRawCollections(): EntityCollection[];
72
+ getCollectionByPath(collectionPath: string): CollectionConfig | undefined;
73
+ getCollections(): CollectionConfig[];
74
+ getRawCollections(): CollectionConfig[];
75
75
  /**
76
76
  * Resolves a multi-segment path like "products/123/locales" and returns
77
77
  * information about the collections and entity IDs along the path
78
78
  */
79
79
  resolvePathToCollections(path: string): {
80
- collections: EntityCollection[];
80
+ collections: CollectionConfig[];
81
81
  entityIds: (string | number)[];
82
- finalCollection: EntityCollection;
82
+ finalCollection: CollectionConfig;
83
83
  };
84
84
  }
@@ -5,7 +5,7 @@
5
5
  * Slug-based dedup (Map keyed by slug, last-write-wins) lets developers
6
6
  * override by defining their own collection with `slug: "users"`.
7
7
  */
8
- export declare const defaultUsersCollection: import("@rebasepro/types").PostgresCollection<import("@rebasepro/types").InferEntityType<{
8
+ export declare const defaultUsersCollection: import("@rebasepro/types").PostgresCollectionConfig<import("@rebasepro/types").InferEntityType<{
9
9
  readonly id: {
10
10
  readonly name: "ID";
11
11
  readonly type: "string";
@@ -1,4 +1,4 @@
1
- import { DataDriver, RebaseData } from "@rebasepro/types";
1
+ import { DataDriver, RebaseData, RebaseSdkData } from "@rebasepro/types";
2
2
  /**
3
3
  * Build a `RebaseData` object from a `DataDriver` using JavaScript Proxy.
4
4
  *
@@ -9,6 +9,34 @@ import { DataDriver, RebaseData } from "@rebasepro/types";
9
9
  * @example
10
10
  * const data = buildRebaseData(driver);
11
11
  * await data.products.create({ name: "Camera", price: 299 });
12
- * const { data: items } = await data.products.find({ where: { status: "eq.published" } });
12
+ * const { data: items } = await data.products.find({ where: { status: ["==", "published"] } });
13
13
  */
14
14
  export declare function buildRebaseData(driver: DataDriver): RebaseData;
15
+ /**
16
+ * Wrap a flat {@link RebaseSdkData} into a Entity-shaped {@link RebaseData}.
17
+ *
18
+ * This is the **CMS boundary**: the SDK client (`client.data`) returns flat
19
+ * rows, but the admin renders the `Entity` view-model (`entity.values.*`).
20
+ * `core/Rebase.tsx` wraps `client.data` through this before handing it to the
21
+ * CMS `RebaseDataContext` — without it the admin renders rows with only their
22
+ * `id`.
23
+ */
24
+ export declare function wrapAsEntityData(sdkData: RebaseSdkData): RebaseData;
25
+ /**
26
+ * Wrap a Entity-shaped {@link RebaseData} into a flat {@link RebaseSdkData}.
27
+ *
28
+ * Every collection accessor is adapted to return flat rows. Use this to derive
29
+ * the flat SDK data layer (`context.data`) from an existing Entity data layer
30
+ * — e.g. the admin routes its Entity data via `useData()` and exposes the
31
+ * same routing as flat `context.data` for callbacks by wrapping it here.
32
+ */
33
+ export declare function wrapAsSdkData(entityData: RebaseData): RebaseSdkData;
34
+ /**
35
+ * Build a flat {@link RebaseSdkData} from a `DataDriver`.
36
+ *
37
+ * This is the developer-facing SDK data layer used by backend framework
38
+ * callbacks & scripts (`context.data` / `rebase.data`). It returns flat rows —
39
+ * identical in shape to the frontend SDK client — so the API is symmetric
40
+ * across front and back. The admin CMS uses {@link buildRebaseData} (Entity).
41
+ */
42
+ export declare function buildSdkData(driver: DataDriver): RebaseSdkData;
@@ -1,21 +1,26 @@
1
- import { RebaseData } from "@rebasepro/types";
1
+ import { RebaseData, RebaseSdkData } from "@rebasepro/types";
2
+ /**
3
+ * The two data-layer shapes that can be routed: the Entity-shaped admin
4
+ * {@link RebaseData} or the flat SDK {@link RebaseSdkData}. Both expose a
5
+ * `.collection(slug)` accessor, which is all the router needs.
6
+ */
7
+ export type RoutableData = RebaseData | RebaseSdkData;
2
8
  /**
3
9
  * Parameters for {@link buildRoutedRebaseData}.
4
10
  */
5
- export interface RoutedRebaseDataParams {
11
+ export interface RoutedRebaseDataParams<T extends RoutableData = RebaseData> {
6
12
  /**
7
13
  * The default data source. Handles every collection that does not
8
14
  * resolve to an entry in `sources` (i.e. server-transport collections,
9
15
  * which ride the Rebase client).
10
16
  */
11
- defaultData: RebaseData;
17
+ defaultData: T;
12
18
  /**
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`.
19
+ * Per-data-source instances for direct and custom transports, keyed by
20
+ * data-source key (e.g. `"analytics"`). Server-mediated sources are not
21
+ * listed here — they fall through to `defaultData`.
17
22
  */
18
- sources: Record<string, RebaseData>;
23
+ sources: Record<string, T>;
19
24
  /**
20
25
  * Resolve the data-source key for a given collection slug or path.
21
26
  * Typically backed by the collection registry + `resolveDataSource`
@@ -50,4 +55,4 @@ export interface RoutedRebaseDataParams {
50
55
  * await data.products.find(); // → default (server / Postgres)
51
56
  * await data.events.find(); // → Firestore, if `events.dataSource === "analytics"`
52
57
  */
53
- export declare function buildRoutedRebaseData({ defaultData, sources, resolveKey }: RoutedRebaseDataParams): RebaseData;
58
+ export declare function buildRoutedRebaseData<T extends RoutableData = RebaseData>({ defaultData, sources, resolveKey }: RoutedRebaseDataParams<T>): T;
@@ -5,12 +5,22 @@
5
5
  * PostgREST-style dot-syntax strings (`eq.active`, `gt.18`, `in.(a,b)`).
6
6
  * Everything else speaks `FilterValues` exclusively.
7
7
  *
8
+ * Wire-format values are always strings — the wire format carries no type
9
+ * metadata, so type coercion is the responsibility of the server-side data
10
+ * driver which has access to the collection schema.
11
+ *
12
+ * Commas inside list values are backslash-escaped (`\,`), and literal
13
+ * backslashes are escaped as `\\`.
14
+ *
8
15
  * @module
9
16
  */
10
17
  import { FilterValues, LogicalCondition, FilterCondition } from "@rebasepro/types";
11
18
  /**
12
- * Convert `FilterValues` to a PostgREST-style querystring record.
19
+ * Convert `FilterValues` (or `WireFilterValues`) to a PostgREST-style
20
+ * querystring record.
13
21
  *
22
+ * - Canonical `[WhereFilterOp, value]` tuples are serialized strictly.
23
+ * - Pre-serialized PostgREST strings (e.g. `"eq.published"`) are passed through.
14
24
  * - Single conditions produce a string value.
15
25
  * - Multiple conditions on the same field produce a string array (repeated params).
16
26
  *
@@ -20,8 +30,12 @@ import { FilterValues, LogicalCondition, FilterCondition } from "@rebasepro/type
20
30
  *
21
31
  * serializeFilter({ age: [[">=", 18], ["<", 65]] })
22
32
  * // → { age: ["gte.18", "lt.65"] }
33
+ *
34
+ * // Pre-serialized strings pass through unchanged:
35
+ * serializeFilter({ status: "eq.published" })
36
+ * // → { status: "eq.published" }
23
37
  */
24
- export declare function serializeFilter(filter: FilterValues<string> | Record<string, any>): Record<string, string | string[]>;
38
+ export declare function serializeFilter(filter: FilterValues<string> | Record<string, unknown>): Record<string, string | string[]>;
25
39
  /**
26
40
  * Convert a PostgREST-style querystring record to `FilterValues`.
27
41
  *
@@ -33,9 +47,9 @@ export declare function serializeFilter(filter: FilterValues<string> | Record<st
33
47
  * // → { status: ["==", "active"] }
34
48
  *
35
49
  * deserializeFilter({ age: ["gte.18", "lt.65"] })
36
- * // → { age: [[">=", 18], ["<", 65]] }
50
+ * // → { age: [[">=", "18"], ["<", "65"]] }
37
51
  */
38
- export declare function deserializeFilter(query: Record<string, any>): FilterValues<string>;
52
+ export declare function deserializeFilter(query: Record<string, unknown>): FilterValues<string>;
39
53
  /**
40
54
  * Serialize a `LogicalCondition` or `FilterCondition` to its wire-format string.
41
55
  *
@@ -1,4 +1,4 @@
1
- import { FindResponse, CollectionAccessor, QueryBuilderInterface, WhereFilterOp, LogicalCondition, WhereValue, FilterCondition } from "@rebasepro/types";
1
+ import { CollectionAccessor, FilterCondition, FindResponse, LogicalCondition, QueryBuilderInterface, WhereFilterOp, WhereValue } from "@rebasepro/types";
2
2
  export declare function or(...conditions: (FilterCondition | LogicalCondition)[]): LogicalCondition;
3
3
  export declare function and(...conditions: (FilterCondition | LogicalCondition)[]): LogicalCondition;
4
4
  export declare function cond(column: string, operator: WhereFilterOp, value: unknown): FilterCondition;
@@ -1,7 +1,7 @@
1
1
  import { DataSourceDefinition, ResolvedDataSource } from "@rebasepro/types";
2
2
  /**
3
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
4
+ * structural type (rather than the full `CollectionConfig`) keeps this usable
5
5
  * from anywhere — frontend router, backend registry, editor — without coupling
6
6
  * to the collection union.
7
7
  */
@@ -0,0 +1,41 @@
1
+ import type { OrderByTuple } from "@rebasepro/types";
2
+ /**
3
+ * Sort-order wire codec.
4
+ *
5
+ * This is the ONLY module that knows about the colon-delimited wire format
6
+ * (`"field:direction"`) used in HTTP query parameters.
7
+ * Everything else speaks {@link OrderByTuple} exclusively.
8
+ *
9
+ * Mirrors the filter architecture in `filter-dialect.ts`.
10
+ *
11
+ * @module
12
+ */
13
+ /**
14
+ * Serialize an {@link OrderByTuple} to the wire format `"field:direction"`.
15
+ *
16
+ * **Runtime tolerance:** if the input is already a well-formed wire string
17
+ * (from an untyped JS caller), it is returned unchanged.
18
+ * This is undocumented tolerance, not public API — don't rely on it.
19
+ *
20
+ * @param orderBy - A canonical `[field, direction]` tuple, or at runtime
21
+ * possibly a pre-serialized string (undocumented tolerance).
22
+ * @returns The wire-format string, or `undefined` if the input is falsy.
23
+ *
24
+ * @remarks
25
+ * Field names containing `:` are representable in the tuple form but
26
+ * **not** on the wire — this is an inherent limitation of the colon-delimited
27
+ * encoding and is not resolved here.
28
+ */
29
+ export declare function serializeOrderBy(orderBy?: OrderByTuple | string): string | undefined;
30
+ /**
31
+ * Deserialize a wire-format `"field:direction"` string into an {@link OrderByTuple}.
32
+ *
33
+ * Lenient parsing (matches existing server behaviour):
34
+ * - Bare field name (no colon): `"name"` → `["name", "asc"]`
35
+ * - Unknown direction: `"name:foo"` → `["name", "asc"]`
36
+ * - Empty / falsy input: → `undefined`
37
+ *
38
+ * @param raw - The wire-format string from an HTTP query parameter.
39
+ * @returns The canonical tuple, or `undefined` if the input is empty/falsy.
40
+ */
41
+ export declare function deserializeOrderBy(raw?: string): OrderByTuple | undefined;
package/dist/index.d.ts CHANGED
@@ -5,4 +5,5 @@ export * from "./data/buildRoutedRebaseData";
5
5
  export * from "./data/resolveDataSource";
6
6
  export * from "./data/query_builder";
7
7
  export * from "./data/filter-dialect";
8
+ export * from "./data/sort-dialect";
8
9
  export * from "./table-classification";