@rebasepro/server-postgres 0.11.1-canary.gfd39654 → 0.12.1-canary.gdfba2a1

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 (44) hide show
  1. package/dist/PostgresBootstrapper.d.ts +8 -0
  2. package/dist/auth/services.d.ts +16 -0
  3. package/dist/backup-service-DH9kPg-E.js +8866 -0
  4. package/dist/backup-service-DH9kPg-E.js.map +1 -0
  5. package/dist/collections/buildRegistry.d.ts +1 -1
  6. package/dist/connection-B5Wndbr1.js +196 -0
  7. package/dist/connection-B5Wndbr1.js.map +1 -0
  8. package/dist/{ensure-collection-tables-DGMYK0fr.js → ensure-collection-tables-CIHSH962.js} +5 -5
  9. package/dist/ensure-collection-tables-CIHSH962.js.map +1 -0
  10. package/dist/history/HistoryService.d.ts +9 -29
  11. package/dist/index.es.js +698 -9604
  12. package/dist/index.es.js.map +1 -1
  13. package/dist/schema/auth-schema.d.ts +83 -144
  14. package/dist/schema/dynamic-tables.d.ts +1 -1
  15. package/dist/schema/introspect-runtime.d.ts +1 -1
  16. package/dist/services/FetchService.d.ts +36 -1
  17. package/dist/services/row-pipeline.d.ts +3 -1
  18. package/dist/{src-3VmUJ8Xn.js → src-DihrDFuP.js} +354 -165
  19. package/dist/src-DihrDFuP.js.map +1 -0
  20. package/dist/{src-D5xBTl32.js → src-DoU9yPqq.js} +79 -189
  21. package/dist/src-DoU9yPqq.js.map +1 -0
  22. package/dist/utils/drizzle-conditions.d.ts +157 -3
  23. package/dist/utils/pg-error-utils.d.ts +6 -3
  24. package/dist/websocket-BKcGvILX.js +528 -0
  25. package/dist/websocket-BKcGvILX.js.map +1 -0
  26. package/package.json +14 -14
  27. package/src/PostgresBootstrapper.ts +23 -6
  28. package/src/auth/ensure-tables.ts +164 -9
  29. package/src/auth/services.ts +21 -2
  30. package/src/collections/buildRegistry.ts +1 -1
  31. package/src/history/HistoryService.ts +13 -31
  32. package/src/schema/auth-schema.ts +30 -19
  33. package/src/schema/dynamic-tables.ts +1 -1
  34. package/src/schema/generate-drizzle-schema-logic.ts +17 -5
  35. package/src/schema/generate-postgres-ddl-logic.ts +7 -3
  36. package/src/schema/introspect-runtime.ts +1 -1
  37. package/src/services/FetchService.ts +79 -11
  38. package/src/services/row-pipeline.ts +3 -1
  39. package/src/utils/drizzle-conditions.ts +509 -45
  40. package/src/utils/pg-error-utils.ts +52 -3
  41. package/dist/chunk-DSJWtz9O.js +0 -40
  42. package/dist/ensure-collection-tables-DGMYK0fr.js.map +0 -1
  43. package/dist/src-3VmUJ8Xn.js.map +0 -1
  44. package/dist/src-D5xBTl32.js.map +0 -1
@@ -1,8 +1,135 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  import "process";
3
3
  __createRequire(import.meta.url);
4
- import { r as __require, t as __commonJSMin } from "./chunk-DSJWtz9O.js";
5
- import { d as isPostgresCollectionConfig, f as NULL_OPS, h as EntityRelation, l as isManyToMany, m as toCanonicalOp, n as getDataSourceCapabilities, o as ANONYMOUS_USER_ID, p as REST_TO_CANONICAL, s as policy, u as getDeclaredSubcollections } from "./src-D5xBTl32.js";
4
+ import { c as __require, o as __commonJSMin } from "./connection-B5Wndbr1.js";
5
+ import { a as getDataSourceCapabilities, c as toCanonicalOp, n as isPostgresCollectionConfig, o as NULL_OPS, r as isRelationalCollectionConfig, s as REST_TO_CANONICAL, t as getDeclaredSubcollections } from "./src-DoU9yPqq.js";
6
+ //#region ../types/src/types/entities.ts
7
+ /**
8
+ * Class used to create a reference to a entity in a different path
9
+ */
10
+ var EntityRelation = class {
11
+ __type = "relation";
12
+ /**
13
+ * ID of the entity
14
+ */
15
+ id;
16
+ /**
17
+ * A string representing the path of the referenced document (relative
18
+ * to the root of the database).
19
+ */
20
+ path;
21
+ /**
22
+ * Pre-fetched data payload to eliminate N+1 queries.
23
+ * When present, clients can use this directly instead of fetching.
24
+ */
25
+ data;
26
+ constructor(id, path, data) {
27
+ this.id = id;
28
+ this.path = path;
29
+ this.data = data;
30
+ }
31
+ get pathWithId() {
32
+ return `${this.path}/${this.id}`;
33
+ }
34
+ isEntityReference() {
35
+ return false;
36
+ }
37
+ isEntityRelation() {
38
+ return true;
39
+ }
40
+ };
41
+ var Vector = class {
42
+ value;
43
+ constructor(value) {
44
+ this.value = value;
45
+ }
46
+ };
47
+ //#endregion
48
+ //#region ../types/src/types/relations.ts
49
+ /** @group Models */
50
+ function hasForeignKeyOnTarget(relation) {
51
+ return relation.kind === "hasOne" || relation.kind === "hasMany";
52
+ }
53
+ /** @group Models */
54
+ function isManyToMany(relation) {
55
+ return relation.kind === "manyToMany";
56
+ }
57
+ //#endregion
58
+ //#region ../types/src/types/policy.ts
59
+ /**
60
+ * The id a request without a logged-in user reports as `auth.uid()`.
61
+ *
62
+ * A user-context request always sets `app.uid`: blank would read back as
63
+ * `NULL`, and `NULL` is how the trusted server context is recognised, so an
64
+ * anonymous visitor would be promoted to server privileges. The driver
65
+ * therefore substitutes this sentinel at the single chokepoint where the GUC
66
+ * is set.
67
+ *
68
+ * The consequence for policy authors is that **`auth.uid() IS NOT NULL` is a
69
+ * tautology on the user path** — it is true for anonymous visitors too. Use
70
+ * {@link policy.authenticated} (or `auth.uid() <> 'anonymous'`) to mean "signed
71
+ * in", and {@link policy.serverContext} to mean "the trusted server context".
72
+ *
73
+ * @group Models
74
+ */
75
+ var ANONYMOUS_USER_ID = "anonymous";
76
+ /** @group Models */
77
+ var policy = {
78
+ true: () => ({ kind: "true" }),
79
+ false: () => ({ kind: "false" }),
80
+ and: (...operands) => ({
81
+ kind: "and",
82
+ operands
83
+ }),
84
+ or: (...operands) => ({
85
+ kind: "or",
86
+ operands
87
+ }),
88
+ not: (operand) => ({
89
+ kind: "not",
90
+ operand
91
+ }),
92
+ compare: (left, op, right) => ({
93
+ kind: "compare",
94
+ op,
95
+ left,
96
+ right
97
+ }),
98
+ rolesOverlap: (roles) => ({
99
+ kind: "rolesOverlap",
100
+ roles
101
+ }),
102
+ rolesContain: (roles) => ({
103
+ kind: "rolesContain",
104
+ roles
105
+ }),
106
+ authenticated: () => ({ kind: "authenticated" }),
107
+ serverContext: () => ({ kind: "serverContext" }),
108
+ existsIn: (args) => ({
109
+ kind: "existsIn",
110
+ collection: args.collection,
111
+ where: args.where
112
+ }),
113
+ raw: (sql) => ({
114
+ kind: "raw",
115
+ sql
116
+ }),
117
+ field: (name) => ({
118
+ kind: "field",
119
+ name
120
+ }),
121
+ outerField: (name) => ({
122
+ kind: "outerField",
123
+ name
124
+ }),
125
+ literal: (value) => ({
126
+ kind: "literal",
127
+ value
128
+ }),
129
+ authUid: () => ({ kind: "authUid" }),
130
+ authRoles: () => ({ kind: "authRoles" })
131
+ };
132
+ //#endregion
6
133
  //#region ../common/src/util/common.ts
7
134
  var DEFAULT_ONE_OF_TYPE = "type";
8
135
  var DEFAULT_ONE_OF_VALUE = "value";
@@ -1264,7 +1391,7 @@ function traverseValueProperty(inputValue, property, operation) {
1264
1391
  return value;
1265
1392
  }
1266
1393
  /**
1267
- * Create a lightweight relation stub for CMS views.
1394
+ * Create a lightweight relation stub for admin views.
1268
1395
  * Replaces inline `{ id, path, __type: "relation" }` object literals.
1269
1396
  */
1270
1397
  function createRelationRef(id, path) {
@@ -1527,7 +1654,7 @@ var _resolvedRelationsCache = /* @__PURE__ */ new WeakMap();
1527
1654
  function resolveCollectionRelations(collection) {
1528
1655
  const cached = _resolvedRelationsCache.get(collection);
1529
1656
  if (cached) return cached;
1530
- if (!getDataSourceCapabilities(collection.engine).supportsRelations) return {};
1657
+ if (!isRelationalCollectionConfig(collection)) return {};
1531
1658
  const relations = {};
1532
1659
  for (const relation of collection.relations ?? []) {
1533
1660
  const resolved = resolveRelation(relation, collection);
@@ -1543,7 +1670,7 @@ function resolveCollectionRelations(collection) {
1543
1670
  return relations;
1544
1671
  }
1545
1672
  function getTableName(collection) {
1546
- if (getDataSourceCapabilities(collection.engine).supportsRelations) return collection.table ?? toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
1673
+ if (isRelationalCollectionConfig(collection)) return collection.table ?? toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
1547
1674
  return toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
1548
1675
  }
1549
1676
  function getTableVarName(tableName) {
@@ -2016,75 +2143,6 @@ function rolesArraySql(roles) {
2016
2143
  return `ARRAY[${[...roles].sort().map((r) => `'${r}'`).join(",")}]`;
2017
2144
  }
2018
2145
  //#endregion
2019
- //#region ../common/src/util/callbacks.ts
2020
- /**
2021
- * Helper function to recursively check if there are any callbacks in the properties.
2022
- */
2023
- function hasPropertyCallbacks(properties, callbackName) {
2024
- if (!properties) return false;
2025
- for (const property of Object.values(properties)) {
2026
- if (property.callbacks?.[callbackName]) return true;
2027
- if (property.type === "map" && property.properties) {
2028
- if (hasPropertyCallbacks(property.properties, callbackName)) return true;
2029
- } else if (property.type === "array" && property.of) {
2030
- const ofs = Array.isArray(property.of) ? property.of : [property.of];
2031
- for (const of of ofs) {
2032
- if (of.callbacks?.[callbackName]) return true;
2033
- if (of.type === "map" && of.properties && hasPropertyCallbacks(of.properties, callbackName)) return true;
2034
- }
2035
- }
2036
- }
2037
- return false;
2038
- }
2039
- /**
2040
- * Recursively process properties to apply field-level hooks.
2041
- */
2042
- async function processProperties(properties, values, previousValues, propsContext, callbackName) {
2043
- if (!values || typeof values !== "object") return values;
2044
- const result = { ...values };
2045
- for (const [key, property] of Object.entries(properties)) {
2046
- if (result[key] === void 0) continue;
2047
- let currentValue = result[key];
2048
- const previousValue = previousValues?.[key];
2049
- if (property.type === "array" && Array.isArray(currentValue)) {
2050
- if (property.of && !Array.isArray(property.of)) currentValue = await Promise.all(currentValue.map(async (item, index) => {
2051
- const prevItem = Array.isArray(previousValue) ? previousValue[index] : void 0;
2052
- return (await processProperties({ "_tmp": property.of }, { "_tmp": item }, { "_tmp": prevItem }, propsContext, callbackName))["_tmp"];
2053
- }));
2054
- } else if (property.type === "map" && property.properties && typeof currentValue === "object") currentValue = await processProperties(property.properties, currentValue, previousValue ?? {}, propsContext, callbackName);
2055
- if (property.callbacks?.[callbackName]) {
2056
- const cbRes = await Promise.resolve(property.callbacks[callbackName]({
2057
- ...propsContext,
2058
- value: currentValue,
2059
- previousValue
2060
- }));
2061
- if (cbRes !== void 0) currentValue = cbRes;
2062
- }
2063
- result[key] = currentValue;
2064
- }
2065
- return result;
2066
- }
2067
- /**
2068
- * Helper function to extract field-level PropertyCallbacks from a properties schema
2069
- * and wrap them into an CollectionCallbacks object recursively.
2070
- */
2071
- var buildPropertyCallbacks = (properties) => {
2072
- if (!properties) return void 0;
2073
- const propertyCallbacks = {};
2074
- if (hasPropertyCallbacks(properties, "afterRead")) propertyCallbacks.afterRead = async (props) => {
2075
- const row = props.row;
2076
- const processedValues = await processProperties(properties, row, row, props, "afterRead");
2077
- return {
2078
- ...props.row,
2079
- ...processedValues
2080
- };
2081
- };
2082
- if (hasPropertyCallbacks(properties, "beforeSave")) propertyCallbacks.beforeSave = async (props) => {
2083
- return await processProperties(properties, props.values, props.previousValues ?? {}, props, "beforeSave");
2084
- };
2085
- return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
2086
- };
2087
- //#endregion
2088
2146
  //#region ../common/src/util/auth-default-policies.ts
2089
2147
  /**
2090
2148
  * Default RLS policies injected by the schema generator.
@@ -2152,8 +2210,8 @@ function getIdPropertyName$1(collection) {
2152
2210
  * Collections that opt out via `disableDefaultPolicies` are returned unchanged.
2153
2211
  */
2154
2212
  function getEffectiveSecurityRules(collection) {
2155
- const explicit = [...(isPostgresCollectionConfig(collection) ? collection.securityRules : void 0) ?? []];
2156
- if (collection.disableDefaultPolicies) return explicit;
2213
+ const explicit = [...collection.securityRules ?? []];
2214
+ if (isPostgresCollectionConfig(collection) && collection.disableDefaultPolicies) return explicit;
2157
2215
  const tableName = getTableName(collection);
2158
2216
  const injected = [];
2159
2217
  injected.push({
@@ -2324,7 +2382,7 @@ function coversUpdate(rule) {
2324
2382
  * stays locked (RLS is still enabled) until they write policies for it.
2325
2383
  */
2326
2384
  function getJunctionSecurityRules(spec) {
2327
- if (spec.declaringSides.every((side) => side.collection.disableDefaultPolicies)) return [];
2385
+ if (spec.declaringSides.every((side) => isPostgresCollectionConfig(side.collection) && side.collection.disableDefaultPolicies)) return [];
2328
2386
  const rules = [];
2329
2387
  rules.push({
2330
2388
  name: `${spec.table}_default_admin_read`,
@@ -2659,10 +2717,27 @@ function getJunctionSecurityRules(spec) {
2659
2717
  });
2660
2718
  })))();
2661
2719
  /**
2662
- * Apply PropertyConditions to a resolved property, evaluating all JSON Logic rules.
2720
+ * How wide a `varchar`/`char` column should be for a given property.
2721
+ *
2722
+ * One definition, three call sites, because they used to disagree. For the same
2723
+ * `columnType: "varchar"` property the DDL generator emitted `VARCHAR(255)`
2724
+ * while the Drizzle generator emitted a bare `varchar("col")` — which Postgres
2725
+ * reads as *unbounded* — so which of the two you ran decided whether the column
2726
+ * had a limit at all. Introspection then dropped the length entirely, so reading
2727
+ * an existing `character varying(500)` column back and regenerating it produced
2728
+ * a `VARCHAR(255)`: a silent narrowing of a column with data already in it.
2729
+ *
2730
+ * `validation.max` is the property's own statement about how long the value may
2731
+ * be, so it is the only sensible source for the column's width — and it keeps
2732
+ * the constraint the database enforces in step with the one the app enforces,
2733
+ * rather than inventing a second, different limit underneath it.
2663
2734
  */
2735
+ function resolveStringColumnLength(prop) {
2736
+ const max = prop.validation?.max;
2737
+ return typeof max === "number" && Number.isInteger(max) && max > 0 ? max : 255;
2738
+ }
2664
2739
  //#endregion
2665
- //#region ../../node_modules/.pnpm/fast-equals@6.0.0/node_modules/fast-equals/dist/es/index.mjs
2740
+ //#region ../../node_modules/.pnpm/fast-equals@6.0.2/node_modules/fast-equals/dist/es/index.mjs
2666
2741
  var { getOwnPropertyNames, getOwnPropertySymbols } = Object;
2667
2742
  var { hasOwnProperty } = Object.prototype;
2668
2743
  /**
@@ -2698,7 +2773,8 @@ function createIsCircular(areItemsEqual) {
2698
2773
  * not enumerable and symbol properties.
2699
2774
  */
2700
2775
  function getStrictProperties(object) {
2701
- return getOwnPropertyNames(object).concat(getOwnPropertySymbols(object));
2776
+ const symbols = getOwnPropertySymbols(object);
2777
+ return symbols.length ? getOwnPropertyNames(object).concat(symbols) : getOwnPropertyNames(object);
2702
2778
  }
2703
2779
  /**
2704
2780
  * Whether the object contains the property passed as an own property.
@@ -2771,7 +2847,7 @@ function areMapsEqual(a, b, state) {
2771
2847
  const size = a.size;
2772
2848
  if (size !== b.size) return false;
2773
2849
  if (!size) return true;
2774
- const matchedIndices = new Array(size);
2850
+ const matchedIndices = new Uint8Array(size);
2775
2851
  const aIterable = a.entries();
2776
2852
  let aResult;
2777
2853
  let bResult;
@@ -2779,7 +2855,7 @@ function areMapsEqual(a, b, state) {
2779
2855
  while (aResult = aIterable.next()) {
2780
2856
  if (aResult.done) break;
2781
2857
  const bIterable = b.entries();
2782
- let hasMatch = false;
2858
+ let hasMatch = 0;
2783
2859
  let matchIndex = 0;
2784
2860
  while (bResult = bIterable.next()) {
2785
2861
  if (bResult.done) break;
@@ -2790,7 +2866,7 @@ function areMapsEqual(a, b, state) {
2790
2866
  const aEntry = aResult.value;
2791
2867
  const bEntry = bResult.value;
2792
2868
  if (state.equals(aEntry[0], bEntry[0], index, matchIndex, a, b, state) && state.equals(aEntry[1], bEntry[1], aEntry[0], bEntry[0], a, b, state)) {
2793
- hasMatch = matchedIndices[matchIndex] = true;
2869
+ hasMatch = matchedIndices[matchIndex] = 1;
2794
2870
  break;
2795
2871
  }
2796
2872
  matchIndex++;
@@ -2848,19 +2924,19 @@ function areSetsEqual(a, b, state) {
2848
2924
  const size = a.size;
2849
2925
  if (size !== b.size) return false;
2850
2926
  if (!size) return true;
2851
- const matchedIndices = new Array(size);
2927
+ const matchedIndices = new Uint8Array(size);
2852
2928
  const aIterable = a.values();
2853
2929
  let aResult;
2854
2930
  let bResult;
2855
2931
  while (aResult = aIterable.next()) {
2856
2932
  if (aResult.done) break;
2857
2933
  const bIterable = b.values();
2858
- let hasMatch = false;
2934
+ let hasMatch = 0;
2859
2935
  let matchIndex = 0;
2860
2936
  while (bResult = bIterable.next()) {
2861
2937
  if (bResult.done) break;
2862
2938
  if (!matchedIndices[matchIndex] && state.equals(aResult.value, bResult.value, aResult.value, bResult.value, a, b, state)) {
2863
- hasMatch = matchedIndices[matchIndex] = true;
2939
+ hasMatch = matchedIndices[matchIndex] = 1;
2864
2940
  break;
2865
2941
  }
2866
2942
  matchIndex++;
@@ -2873,8 +2949,8 @@ function areSetsEqual(a, b, state) {
2873
2949
  * Whether the TypedArray instances are equal in value.
2874
2950
  */
2875
2951
  function areTypedArraysEqual(a, b) {
2876
- let index = a.byteLength;
2877
- if (b.byteLength !== index || a.byteOffset !== b.byteOffset) return false;
2952
+ let index = a.length;
2953
+ if (b.length !== index || a.byteOffset !== b.byteOffset) return false;
2878
2954
  while (index-- > 0) if (a[index] !== b[index]) return false;
2879
2955
  return true;
2880
2956
  }
@@ -3444,6 +3520,136 @@ var QueryBuilder = class {
3444
3520
  return this.collection.listen(this.params, onUpdate, onError);
3445
3521
  }
3446
3522
  };
3523
+ /** Rows `findAll()` will materialise before it refuses to continue. */
3524
+ var DEFAULT_FIND_ALL_MAX_ROWS = 1e4;
3525
+ /**
3526
+ * Requests one walk may make before it gives up on the server ever saying
3527
+ * `hasMore: false`. At the default page size that is two million rows — far
3528
+ * past any legitimate walk, and short of running forever.
3529
+ */
3530
+ var DEFAULT_MAX_PAGES = 1e4;
3531
+ /**
3532
+ * Thrown when a walk stops for a reason the caller needs to know about.
3533
+ *
3534
+ * Every one of these is a case where the alternative would be silent: a
3535
+ * truncated array that looks complete, or a loop that never returns. Check
3536
+ * {@link code} to tell them apart.
3537
+ */
3538
+ var RebasePaginationError = class RebasePaginationError extends Error {
3539
+ code;
3540
+ constructor(code, message) {
3541
+ super(message);
3542
+ this.name = "RebasePaginationError";
3543
+ this.code = code;
3544
+ Object.setPrototypeOf(this, RebasePaginationError.prototype);
3545
+ }
3546
+ };
3547
+ function normalizePageSize(raw) {
3548
+ if (raw === void 0 || !Number.isFinite(raw)) return 200;
3549
+ return Math.max(1, Math.floor(raw));
3550
+ }
3551
+ function normalizeMaxPages(raw) {
3552
+ if (raw === void 0) return DEFAULT_MAX_PAGES;
3553
+ if (raw === Number.POSITIVE_INFINITY) return raw;
3554
+ if (!Number.isFinite(raw)) return DEFAULT_MAX_PAGES;
3555
+ return Math.max(1, Math.floor(raw));
3556
+ }
3557
+ function normalizeMaxRows(raw) {
3558
+ if (raw === void 0) return DEFAULT_FIND_ALL_MAX_ROWS;
3559
+ if (raw === Number.POSITIVE_INFINITY) return raw;
3560
+ if (!Number.isFinite(raw)) return DEFAULT_FIND_ALL_MAX_ROWS;
3561
+ return Math.max(0, Math.floor(raw));
3562
+ }
3563
+ /**
3564
+ * Add one condition to a `where` map without disturbing what is already there.
3565
+ *
3566
+ * The caller's own filter on the cursor column has to survive — dropping it
3567
+ * would widen the query, which is the silent-filter-loss failure mode — so a
3568
+ * second condition on the same column becomes the array-of-tuples form that
3569
+ * `FindParams.where` already accepts, and both are AND-ed.
3570
+ */
3571
+ function appendCondition(where, column, condition) {
3572
+ const next = { ...where ?? {} };
3573
+ const existing = next[column];
3574
+ if (existing === void 0) next[column] = condition;
3575
+ else if (Array.isArray(existing) && existing.length > 0 && Array.isArray(existing[0])) next[column] = [...existing, condition];
3576
+ else next[column] = [existing, condition];
3577
+ return next;
3578
+ }
3579
+ function cursorEquals(a, b) {
3580
+ if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
3581
+ return Object.is(a, b);
3582
+ }
3583
+ /**
3584
+ * Walk every row a query matches, yielding one row at a time and fetching the
3585
+ * next page only when the consumer asks for it.
3586
+ *
3587
+ * See {@link SDKCollectionClient.iterate} for the caller-facing contract,
3588
+ * including the offset-drift caveat and the `cursor` alternative.
3589
+ *
3590
+ * @param find the transport's single-page read
3591
+ * @param params `find()` parameters minus the window, plus the walk options
3592
+ * @param label the collection name, so an error says which walk failed
3593
+ */
3594
+ async function* paginateFind(find, params, label = "collection") {
3595
+ const { pageSize, cursor, maxPages, ...rest } = params ?? {};
3596
+ const findParams = { ...rest };
3597
+ const size = normalizePageSize(pageSize);
3598
+ const pageCap = normalizeMaxPages(maxPages);
3599
+ const cursorField = typeof cursor === "string" ? cursor : cursor?.field;
3600
+ const requestedDirection = typeof cursor === "object" && cursor !== null ? cursor.direction : void 0;
3601
+ let direction = "asc";
3602
+ if (cursorField) {
3603
+ const orderBy = findParams.orderBy;
3604
+ if (orderBy && orderBy[0] !== cursorField) throw new RebasePaginationError("cursor-order-mismatch", `Cannot seek on "${cursorField}" while ordering "${label}" by "${orderBy[0]}": keyset pagination only advances along the column the query is sorted by. Order by "${cursorField}", or drop the cursor and page by offset.`);
3605
+ direction = requestedDirection ?? orderBy?.[1] ?? "asc";
3606
+ findParams.orderBy = [cursorField, direction];
3607
+ }
3608
+ const seekOp = direction === "desc" ? "<" : ">";
3609
+ const baseWhere = findParams.where;
3610
+ let offset = 0;
3611
+ let pages = 0;
3612
+ let cursorValue;
3613
+ let seeking = false;
3614
+ for (;;) {
3615
+ if (pages >= pageCap) throw new RebasePaginationError("max-pages", `Iterating "${label}" made ${pages} requests without the server reporting the end of the collection. Stopping rather than looping forever — raise \`maxPages\` if the walk is genuinely this long, or check that the backend sets \`meta.hasMore\`.`);
3616
+ const pageParams = {
3617
+ ...findParams,
3618
+ limit: size
3619
+ };
3620
+ if (cursorField) {
3621
+ if (seeking) pageParams.where = appendCondition(baseWhere, cursorField, [seekOp, cursorValue]);
3622
+ } else pageParams.offset = offset;
3623
+ const page = await find(pageParams);
3624
+ pages += 1;
3625
+ const rows = page?.data ?? [];
3626
+ if (rows.length === 0) return;
3627
+ for (const row of rows) yield row;
3628
+ if (page?.meta?.hasMore !== true) return;
3629
+ if (cursorField) {
3630
+ const nextValue = rows[rows.length - 1]?.[cursorField];
3631
+ if (nextValue === void 0 || nextValue === null) throw new RebasePaginationError("cursor-missing", `Cannot seek past the last row of "${label}": it has no value for the cursor column "${cursorField}". Pick a column that is present and non-null on every row.`);
3632
+ if (seeking && cursorEquals(nextValue, cursorValue)) throw new RebasePaginationError("cursor-stalled", `Iterating "${label}" is stuck: two pages in a row ended at ${cursorField}=${String(nextValue)}. The cursor column has to be unique — a repeated value cannot be seeked past, and continuing would either loop forever or skip the duplicates. Use the primary key, or page by offset.`);
3633
+ cursorValue = nextValue;
3634
+ seeking = true;
3635
+ } else offset += rows.length;
3636
+ }
3637
+ }
3638
+ /**
3639
+ * {@link paginateFind}, collected into an array under a ceiling.
3640
+ *
3641
+ * See {@link SDKCollectionClient.findAll}.
3642
+ */
3643
+ async function collectAllPages(find, params, label = "collection") {
3644
+ const { maxRows, ...rest } = params ?? {};
3645
+ const cap = normalizeMaxRows(maxRows);
3646
+ const out = [];
3647
+ for await (const row of paginateFind(find, rest, label)) {
3648
+ out.push(row);
3649
+ if (out.length > cap) throw new RebasePaginationError("max-rows", `findAll("${label}") matched more than ${cap} rows. Returning the first ${cap} would look like the whole answer and quietly not be one, so this throws instead. Raise \`maxRows\` if you meant to load them all, or stream with \`iterate()\`.`);
3650
+ }
3651
+ return out;
3652
+ }
3447
3653
  //#endregion
3448
3654
  //#region ../common/src/data/filter-dialect.ts
3449
3655
  /**
@@ -3593,6 +3799,42 @@ function rowToEntity(row, slug, primaryKeys = []) {
3593
3799
  values: row
3594
3800
  };
3595
3801
  }
3802
+ /**
3803
+ * The relation envelope `toCmsRow` writes where a relation was:
3804
+ * `{ id, path, __type: "relation", data: { id, path, values } }`. It is the
3805
+ * admin's view-model, and the only pipeline that produces one is postgres'.
3806
+ */
3807
+ function isRelationEnvelope(value) {
3808
+ return typeof value === "object" && value !== null && !Array.isArray(value) && value.__type === "relation";
3809
+ }
3810
+ /** The target's own columns, as `toRestRow` would have inlined them. */
3811
+ function inlineEnvelope(envelope) {
3812
+ return envelope.data?.values ?? {};
3813
+ }
3814
+ /**
3815
+ * Replace every relation envelope on a row with the target's flat columns.
3816
+ *
3817
+ * The SDK serves one relation shape — the inlined one (see
3818
+ * {@link RestFetchService}) — and reads that come back through a *driver*
3819
+ * method rather than the REST pipeline still carry envelopes. Realtime is the
3820
+ * one such read left: there is no `listenForRest`, so the rows arrive shaped
3821
+ * for the admin and are flattened here instead.
3822
+ *
3823
+ * Only applied where the REST pipeline is the contract (see `find`); a driver
3824
+ * without a `restFetchService` keeps whatever it returns, so the admin's own
3825
+ * path through {@link buildRebaseData} is untouched.
3826
+ */
3827
+ function inlineRelationRefs(row) {
3828
+ let out;
3829
+ for (const [key, value] of Object.entries(row)) if (isRelationEnvelope(value)) {
3830
+ out = out ?? { ...row };
3831
+ out[key] = inlineEnvelope(value);
3832
+ } else if (Array.isArray(value) && value.some(isRelationEnvelope)) {
3833
+ out = out ?? { ...row };
3834
+ out[key] = value.map((item) => isRelationEnvelope(item) ? inlineEnvelope(item) : item);
3835
+ }
3836
+ return out ?? row;
3837
+ }
3596
3838
  function createDriverAccessor(driver, slug, getPks = () => []) {
3597
3839
  const accessor = {
3598
3840
  async find(params) {
@@ -3600,14 +3842,14 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
3600
3842
  const limit = params?.limit ?? 20;
3601
3843
  const offset = params?.offset ?? 0;
3602
3844
  const fetchService = driver.restFetchService;
3603
- const rows = fetchService && params?.include && params.include.length > 0 ? await fetchService.fetchCollectionForRest(slug, {
3845
+ const rows = fetchService ? await fetchService.fetchCollectionForRest(slug, {
3604
3846
  filter,
3605
3847
  limit: params?.limit,
3606
3848
  offset: params?.offset,
3607
3849
  orderBy: params?.orderBy?.[0],
3608
3850
  order: params?.orderBy?.[1],
3609
3851
  searchString: params?.searchString
3610
- }, params.include) : await driver.fetchCollection({
3852
+ }, params?.include) : await driver.fetchCollection({
3611
3853
  path: slug,
3612
3854
  limit: params?.limit,
3613
3855
  offset: params?.offset,
@@ -3636,7 +3878,8 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
3636
3878
  };
3637
3879
  },
3638
3880
  async findById(id) {
3639
- const row = await driver.fetchOne({
3881
+ const fetchService = driver.restFetchService;
3882
+ const row = fetchService ? await fetchService.fetchOneForRest(slug, id) : await driver.fetchOne({
3640
3883
  path: slug,
3641
3884
  id
3642
3885
  });
@@ -3682,6 +3925,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
3682
3925
  listen: driver.listenCollection ? (params, onUpdate, onError) => {
3683
3926
  const limit = params?.limit ?? 20;
3684
3927
  const offset = params?.offset ?? 0;
3928
+ const normalize = driver.restFetchService ? inlineRelationRefs : (row) => row;
3685
3929
  return driver.listenCollection({
3686
3930
  path: slug,
3687
3931
  limit: params?.limit,
@@ -3692,7 +3936,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
3692
3936
  searchString: params?.searchString,
3693
3937
  onUpdate: (entities) => {
3694
3938
  onUpdate({
3695
- data: entities.map((row) => rowToEntity(row, slug, getPks())),
3939
+ data: entities.map((row) => rowToEntity(normalize(row), slug, getPks())),
3696
3940
  meta: {
3697
3941
  total: entities.length,
3698
3942
  limit,
@@ -3705,10 +3949,11 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
3705
3949
  });
3706
3950
  } : void 0,
3707
3951
  listenById: driver.listenOne ? (id, onUpdate, onError) => {
3952
+ const normalize = driver.restFetchService ? inlineRelationRefs : (row) => row;
3708
3953
  return driver.listenOne({
3709
3954
  path: slug,
3710
3955
  id,
3711
- onUpdate: (entity) => onUpdate(entity ? rowToEntity(entity, slug, getPks()) : void 0),
3956
+ onUpdate: (entity) => onUpdate(entity ? rowToEntity(normalize(entity), slug, getPks()) : void 0),
3712
3957
  onError
3713
3958
  });
3714
3959
  } : void 0,
@@ -3839,7 +4084,7 @@ var SdkQueryBuilder = class {
3839
4084
  * {@link SDKCollectionClient}. Every returned record is unwrapped to a flat row
3840
4085
  * so the backend SDK is byte-for-byte the same shape as the frontend client.
3841
4086
  */
3842
- function toSdkCollectionClient(snap) {
4087
+ function toSdkCollectionClient(snap, slug = "collection") {
3843
4088
  const client = {
3844
4089
  async find(params) {
3845
4090
  const res = await snap.find(params);
@@ -3848,6 +4093,12 @@ function toSdkCollectionClient(snap) {
3848
4093
  meta: res.meta
3849
4094
  };
3850
4095
  },
4096
+ iterate(params) {
4097
+ return paginateFind((p) => client.find(p), params, slug);
4098
+ },
4099
+ findAll(params) {
4100
+ return collectAllPages((p) => client.find(p), params, slug);
4101
+ },
3851
4102
  async findById(id) {
3852
4103
  const s = await snap.findById(id);
3853
4104
  return s ? entityToRow(s) : void 0;
@@ -3899,7 +4150,7 @@ function wrapAsSdkData(entityData) {
3899
4150
  function getAccessor(slug) {
3900
4151
  let accessor = cache.get(slug);
3901
4152
  if (!accessor) {
3902
- accessor = toSdkCollectionClient(entityData.collection(slug));
4153
+ accessor = toSdkCollectionClient(entityData.collection(slug), slug);
3903
4154
  cache.set(slug, accessor);
3904
4155
  }
3905
4156
  return accessor;
@@ -3916,79 +4167,17 @@ function wrapAsSdkData(entityData) {
3916
4167
  *
3917
4168
  * This is the developer-facing SDK data layer used by backend framework
3918
4169
  * callbacks & scripts (`context.data` / `rebase.data`). It returns flat rows —
3919
- * identical in shape to the frontend SDK client — so the API is symmetric
3920
- * across front and back. The admin CMS uses {@link buildRebaseData} (Entity).
4170
+ * identical in shape to the frontend SDK client, down to how a relation is
4171
+ * served: a foreign key stays a foreign key, and a relation named in `include`
4172
+ * arrives as the target's own columns. The `{ __type: "relation" }` envelope is
4173
+ * the admin's view-model and never reaches here.
4174
+ *
4175
+ * The admin uses {@link buildRebaseData} (Entity) over its own driver.
3921
4176
  */
3922
4177
  function buildSdkData(driver) {
3923
4178
  return wrapAsSdkData(buildRebaseData(driver));
3924
4179
  }
3925
4180
  //#endregion
3926
- //#region ../common/src/table-classification.ts
3927
- /** Schemas that are always considered Rebase-internal. */
3928
- var REBASE_INTERNAL_SCHEMAS = ["rebase", "auth"];
3929
- /** Table-name prefixes that mark a table as Rebase-internal regardless of schema. */
3930
- var REBASE_INTERNAL_PREFIXES = [
3931
- "_rebase_",
3932
- "_auth_",
3933
- "drizzle_"
3934
- ];
3935
- /**
3936
- * Synchronously classify a table based on naming conventions.
3937
- *
3938
- * @param tableName - The unqualified name of the table.
3939
- * @param schemaName - The schema the table belongs to (e.g. `"public"`, `"rebase"`).
3940
- * @returns `"rebase-internal"` when the table belongs to a reserved schema or
3941
- * carries a reserved prefix; `"user"` otherwise.
3942
- *
3943
- * @remarks
3944
- * Junction-table detection requires an async database query and is therefore
3945
- * **not** handled by this function. Use {@link detectJunctionTables} to obtain
3946
- * the set of junction tables, then reclassify as needed.
3947
- */
3948
- function classifyTable(tableName, schemaName) {
3949
- if (REBASE_INTERNAL_SCHEMAS.includes(schemaName) || REBASE_INTERNAL_PREFIXES.some((prefix) => tableName.startsWith(prefix))) return "rebase-internal";
3950
- return "user";
3951
- }
3952
- /** SQL query that detects junction tables in the `public` schema. */
3953
- var JUNCTION_TABLES_SQL = `
3954
- SELECT t.table_name
3955
- FROM information_schema.tables t
3956
- WHERE t.table_schema = 'public'
3957
- AND t.table_type = 'BASE TABLE'
3958
- AND NOT EXISTS (
3959
- SELECT 1
3960
- FROM information_schema.columns c
3961
- WHERE c.table_schema = t.table_schema
3962
- AND c.table_name = t.table_name
3963
- AND c.column_name NOT IN (
3964
- SELECT kcu.column_name
3965
- FROM information_schema.key_column_usage kcu
3966
- JOIN information_schema.table_constraints tc
3967
- ON tc.constraint_name = kcu.constraint_name
3968
- AND tc.table_schema = kcu.table_schema
3969
- WHERE tc.constraint_type = 'FOREIGN KEY'
3970
- AND kcu.table_schema = t.table_schema
3971
- AND kcu.table_name = t.table_name
3972
- )
3973
- )
3974
- `;
3975
- /**
3976
- * Asynchronously detect junction (link) tables in the `public` schema.
3977
- *
3978
- * A junction table is defined as a table where **every** column participates in
3979
- * at least one foreign-key constraint.
3980
- *
3981
- * @param executeSql - A callback that executes a raw SQL string and returns the
3982
- * resulting rows.
3983
- * @returns A `Set` containing the names of all detected junction tables.
3984
- */
3985
- async function detectJunctionTables(executeSql) {
3986
- const rows = await executeSql(JUNCTION_TABLES_SQL);
3987
- const junctionTables = /* @__PURE__ */ new Set();
3988
- for (const row of rows) if (typeof row.table_name === "string") junctionTables.add(row.table_name);
3989
- return junctionTables;
3990
- }
3991
- //#endregion
3992
- export { DEFAULT_ONE_OF_TYPE as A, createRelationRefWithData as C, mergeDeep as D, getPolicyNamesForRule as E, camelCase as O, createRelationRef as S, updateDateAutoValues as T, getTableVarName as _, getJunctionCollectionConfig as a, getDeclaredPrimaryKeys as b, getEffectiveSecurityRules as c, securityRuleToConditions as d, findAnonymousGrants as f, getTableName as g, getEnumVarName as h, CollectionRegistry as i, DEFAULT_ONE_OF_VALUE as j, toSnakeCase as k, buildPropertyCallbacks as l, getColumnName as m, detectJunctionTables as n, getJunctionSecurityRules as o, findRelation as p, buildSdkData as r, resolveJunctionSpecs as s, classifyTable as t, policyToPostgres as u, resolveCollectionRelations as v, normalizeToEntityRelation as w, parseIdValues as x, buildCompositeId as y };
4181
+ export { DEFAULT_ONE_OF_VALUE as A, updateDateAutoValues as C, camelCase as D, mergeDeep as E, hasForeignKeyOnTarget as M, isManyToMany as N, toSnakeCase as O, Vector as P, normalizeToEntityRelation as S, getPolicyNamesForRule as T, buildCompositeId as _, getJunctionSecurityRules as a, createRelationRef as b, policyToPostgres as c, findRelation as d, getColumnName as f, resolveCollectionRelations as g, getTableVarName as h, getJunctionCollectionConfig as i, ANONYMOUS_USER_ID as j, DEFAULT_ONE_OF_TYPE as k, securityRuleToConditions as l, getTableName as m, CollectionRegistry as n, resolveJunctionSpecs as o, getEnumVarName as p, resolveStringColumnLength as r, getEffectiveSecurityRules as s, buildSdkData as t, findAnonymousGrants as u, getDeclaredPrimaryKeys as v, generateForeignKeyName as w, createRelationRefWithData as x, parseIdValues as y };
3993
4182
 
3994
- //# sourceMappingURL=src-3VmUJ8Xn.js.map
4183
+ //# sourceMappingURL=src-DihrDFuP.js.map