@rebasepro/server-postgres 0.11.1-canary.gf2b0ff7 → 0.11.1-canary.gfadf355

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.
@@ -2,7 +2,7 @@ import { createRequire as __createRequire } from "module";
2
2
  import "process";
3
3
  __createRequire(import.meta.url);
4
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";
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-C5hyt1Um.js";
6
6
  //#region ../common/src/util/common.ts
7
7
  var DEFAULT_ONE_OF_TYPE = "type";
8
8
  var DEFAULT_ONE_OF_VALUE = "value";
@@ -1264,7 +1264,7 @@ function traverseValueProperty(inputValue, property, operation) {
1264
1264
  return value;
1265
1265
  }
1266
1266
  /**
1267
- * Create a lightweight relation stub for CMS views.
1267
+ * Create a lightweight relation stub for admin views.
1268
1268
  * Replaces inline `{ id, path, __type: "relation" }` object literals.
1269
1269
  */
1270
1270
  function createRelationRef(id, path) {
@@ -3444,6 +3444,136 @@ var QueryBuilder = class {
3444
3444
  return this.collection.listen(this.params, onUpdate, onError);
3445
3445
  }
3446
3446
  };
3447
+ /** Rows `findAll()` will materialise before it refuses to continue. */
3448
+ var DEFAULT_FIND_ALL_MAX_ROWS = 1e4;
3449
+ /**
3450
+ * Requests one walk may make before it gives up on the server ever saying
3451
+ * `hasMore: false`. At the default page size that is two million rows — far
3452
+ * past any legitimate walk, and short of running forever.
3453
+ */
3454
+ var DEFAULT_MAX_PAGES = 1e4;
3455
+ /**
3456
+ * Thrown when a walk stops for a reason the caller needs to know about.
3457
+ *
3458
+ * Every one of these is a case where the alternative would be silent: a
3459
+ * truncated array that looks complete, or a loop that never returns. Check
3460
+ * {@link code} to tell them apart.
3461
+ */
3462
+ var RebasePaginationError = class RebasePaginationError extends Error {
3463
+ code;
3464
+ constructor(code, message) {
3465
+ super(message);
3466
+ this.name = "RebasePaginationError";
3467
+ this.code = code;
3468
+ Object.setPrototypeOf(this, RebasePaginationError.prototype);
3469
+ }
3470
+ };
3471
+ function normalizePageSize(raw) {
3472
+ if (raw === void 0 || !Number.isFinite(raw)) return 200;
3473
+ return Math.max(1, Math.floor(raw));
3474
+ }
3475
+ function normalizeMaxPages(raw) {
3476
+ if (raw === void 0) return DEFAULT_MAX_PAGES;
3477
+ if (raw === Number.POSITIVE_INFINITY) return raw;
3478
+ if (!Number.isFinite(raw)) return DEFAULT_MAX_PAGES;
3479
+ return Math.max(1, Math.floor(raw));
3480
+ }
3481
+ function normalizeMaxRows(raw) {
3482
+ if (raw === void 0) return DEFAULT_FIND_ALL_MAX_ROWS;
3483
+ if (raw === Number.POSITIVE_INFINITY) return raw;
3484
+ if (!Number.isFinite(raw)) return DEFAULT_FIND_ALL_MAX_ROWS;
3485
+ return Math.max(0, Math.floor(raw));
3486
+ }
3487
+ /**
3488
+ * Add one condition to a `where` map without disturbing what is already there.
3489
+ *
3490
+ * The caller's own filter on the cursor column has to survive — dropping it
3491
+ * would widen the query, which is the silent-filter-loss failure mode — so a
3492
+ * second condition on the same column becomes the array-of-tuples form that
3493
+ * `FindParams.where` already accepts, and both are AND-ed.
3494
+ */
3495
+ function appendCondition(where, column, condition) {
3496
+ const next = { ...where ?? {} };
3497
+ const existing = next[column];
3498
+ if (existing === void 0) next[column] = condition;
3499
+ else if (Array.isArray(existing) && existing.length > 0 && Array.isArray(existing[0])) next[column] = [...existing, condition];
3500
+ else next[column] = [existing, condition];
3501
+ return next;
3502
+ }
3503
+ function cursorEquals(a, b) {
3504
+ if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
3505
+ return Object.is(a, b);
3506
+ }
3507
+ /**
3508
+ * Walk every row a query matches, yielding one row at a time and fetching the
3509
+ * next page only when the consumer asks for it.
3510
+ *
3511
+ * See {@link SDKCollectionClient.iterate} for the caller-facing contract,
3512
+ * including the offset-drift caveat and the `cursor` alternative.
3513
+ *
3514
+ * @param find the transport's single-page read
3515
+ * @param params `find()` parameters minus the window, plus the walk options
3516
+ * @param label the collection name, so an error says which walk failed
3517
+ */
3518
+ async function* paginateFind(find, params, label = "collection") {
3519
+ const { pageSize, cursor, maxPages, ...rest } = params ?? {};
3520
+ const findParams = { ...rest };
3521
+ const size = normalizePageSize(pageSize);
3522
+ const pageCap = normalizeMaxPages(maxPages);
3523
+ const cursorField = typeof cursor === "string" ? cursor : cursor?.field;
3524
+ const requestedDirection = typeof cursor === "object" && cursor !== null ? cursor.direction : void 0;
3525
+ let direction = "asc";
3526
+ if (cursorField) {
3527
+ const orderBy = findParams.orderBy;
3528
+ 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.`);
3529
+ direction = requestedDirection ?? orderBy?.[1] ?? "asc";
3530
+ findParams.orderBy = [cursorField, direction];
3531
+ }
3532
+ const seekOp = direction === "desc" ? "<" : ">";
3533
+ const baseWhere = findParams.where;
3534
+ let offset = 0;
3535
+ let pages = 0;
3536
+ let cursorValue;
3537
+ let seeking = false;
3538
+ for (;;) {
3539
+ 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\`.`);
3540
+ const pageParams = {
3541
+ ...findParams,
3542
+ limit: size
3543
+ };
3544
+ if (cursorField) {
3545
+ if (seeking) pageParams.where = appendCondition(baseWhere, cursorField, [seekOp, cursorValue]);
3546
+ } else pageParams.offset = offset;
3547
+ const page = await find(pageParams);
3548
+ pages += 1;
3549
+ const rows = page?.data ?? [];
3550
+ if (rows.length === 0) return;
3551
+ for (const row of rows) yield row;
3552
+ if (page?.meta?.hasMore !== true) return;
3553
+ if (cursorField) {
3554
+ const nextValue = rows[rows.length - 1]?.[cursorField];
3555
+ 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.`);
3556
+ 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.`);
3557
+ cursorValue = nextValue;
3558
+ seeking = true;
3559
+ } else offset += rows.length;
3560
+ }
3561
+ }
3562
+ /**
3563
+ * {@link paginateFind}, collected into an array under a ceiling.
3564
+ *
3565
+ * See {@link SDKCollectionClient.findAll}.
3566
+ */
3567
+ async function collectAllPages(find, params, label = "collection") {
3568
+ const { maxRows, ...rest } = params ?? {};
3569
+ const cap = normalizeMaxRows(maxRows);
3570
+ const out = [];
3571
+ for await (const row of paginateFind(find, rest, label)) {
3572
+ out.push(row);
3573
+ 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()\`.`);
3574
+ }
3575
+ return out;
3576
+ }
3447
3577
  //#endregion
3448
3578
  //#region ../common/src/data/filter-dialect.ts
3449
3579
  /**
@@ -3593,6 +3723,42 @@ function rowToEntity(row, slug, primaryKeys = []) {
3593
3723
  values: row
3594
3724
  };
3595
3725
  }
3726
+ /**
3727
+ * The relation envelope `toCmsRow` writes where a relation was:
3728
+ * `{ id, path, __type: "relation", data: { id, path, values } }`. It is the
3729
+ * admin's view-model, and the only pipeline that produces one is postgres'.
3730
+ */
3731
+ function isRelationEnvelope(value) {
3732
+ return typeof value === "object" && value !== null && !Array.isArray(value) && value.__type === "relation";
3733
+ }
3734
+ /** The target's own columns, as `toRestRow` would have inlined them. */
3735
+ function inlineEnvelope(envelope) {
3736
+ return envelope.data?.values ?? {};
3737
+ }
3738
+ /**
3739
+ * Replace every relation envelope on a row with the target's flat columns.
3740
+ *
3741
+ * The SDK serves one relation shape — the inlined one (see
3742
+ * {@link RestFetchService}) — and reads that come back through a *driver*
3743
+ * method rather than the REST pipeline still carry envelopes. Realtime is the
3744
+ * one such read left: there is no `listenForRest`, so the rows arrive shaped
3745
+ * for the admin and are flattened here instead.
3746
+ *
3747
+ * Only applied where the REST pipeline is the contract (see `find`); a driver
3748
+ * without a `restFetchService` keeps whatever it returns, so the admin's own
3749
+ * path through {@link buildRebaseData} is untouched.
3750
+ */
3751
+ function inlineRelationRefs(row) {
3752
+ let out;
3753
+ for (const [key, value] of Object.entries(row)) if (isRelationEnvelope(value)) {
3754
+ out = out ?? { ...row };
3755
+ out[key] = inlineEnvelope(value);
3756
+ } else if (Array.isArray(value) && value.some(isRelationEnvelope)) {
3757
+ out = out ?? { ...row };
3758
+ out[key] = value.map((item) => isRelationEnvelope(item) ? inlineEnvelope(item) : item);
3759
+ }
3760
+ return out ?? row;
3761
+ }
3596
3762
  function createDriverAccessor(driver, slug, getPks = () => []) {
3597
3763
  const accessor = {
3598
3764
  async find(params) {
@@ -3600,14 +3766,14 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
3600
3766
  const limit = params?.limit ?? 20;
3601
3767
  const offset = params?.offset ?? 0;
3602
3768
  const fetchService = driver.restFetchService;
3603
- const rows = fetchService && params?.include && params.include.length > 0 ? await fetchService.fetchCollectionForRest(slug, {
3769
+ const rows = fetchService ? await fetchService.fetchCollectionForRest(slug, {
3604
3770
  filter,
3605
3771
  limit: params?.limit,
3606
3772
  offset: params?.offset,
3607
3773
  orderBy: params?.orderBy?.[0],
3608
3774
  order: params?.orderBy?.[1],
3609
3775
  searchString: params?.searchString
3610
- }, params.include) : await driver.fetchCollection({
3776
+ }, params?.include) : await driver.fetchCollection({
3611
3777
  path: slug,
3612
3778
  limit: params?.limit,
3613
3779
  offset: params?.offset,
@@ -3636,7 +3802,8 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
3636
3802
  };
3637
3803
  },
3638
3804
  async findById(id) {
3639
- const row = await driver.fetchOne({
3805
+ const fetchService = driver.restFetchService;
3806
+ const row = fetchService ? await fetchService.fetchOneForRest(slug, id) : await driver.fetchOne({
3640
3807
  path: slug,
3641
3808
  id
3642
3809
  });
@@ -3682,6 +3849,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
3682
3849
  listen: driver.listenCollection ? (params, onUpdate, onError) => {
3683
3850
  const limit = params?.limit ?? 20;
3684
3851
  const offset = params?.offset ?? 0;
3852
+ const normalize = driver.restFetchService ? inlineRelationRefs : (row) => row;
3685
3853
  return driver.listenCollection({
3686
3854
  path: slug,
3687
3855
  limit: params?.limit,
@@ -3692,7 +3860,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
3692
3860
  searchString: params?.searchString,
3693
3861
  onUpdate: (entities) => {
3694
3862
  onUpdate({
3695
- data: entities.map((row) => rowToEntity(row, slug, getPks())),
3863
+ data: entities.map((row) => rowToEntity(normalize(row), slug, getPks())),
3696
3864
  meta: {
3697
3865
  total: entities.length,
3698
3866
  limit,
@@ -3705,10 +3873,11 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
3705
3873
  });
3706
3874
  } : void 0,
3707
3875
  listenById: driver.listenOne ? (id, onUpdate, onError) => {
3876
+ const normalize = driver.restFetchService ? inlineRelationRefs : (row) => row;
3708
3877
  return driver.listenOne({
3709
3878
  path: slug,
3710
3879
  id,
3711
- onUpdate: (entity) => onUpdate(entity ? rowToEntity(entity, slug, getPks()) : void 0),
3880
+ onUpdate: (entity) => onUpdate(entity ? rowToEntity(normalize(entity), slug, getPks()) : void 0),
3712
3881
  onError
3713
3882
  });
3714
3883
  } : void 0,
@@ -3839,7 +4008,7 @@ var SdkQueryBuilder = class {
3839
4008
  * {@link SDKCollectionClient}. Every returned record is unwrapped to a flat row
3840
4009
  * so the backend SDK is byte-for-byte the same shape as the frontend client.
3841
4010
  */
3842
- function toSdkCollectionClient(snap) {
4011
+ function toSdkCollectionClient(snap, slug = "collection") {
3843
4012
  const client = {
3844
4013
  async find(params) {
3845
4014
  const res = await snap.find(params);
@@ -3848,6 +4017,12 @@ function toSdkCollectionClient(snap) {
3848
4017
  meta: res.meta
3849
4018
  };
3850
4019
  },
4020
+ iterate(params) {
4021
+ return paginateFind((p) => client.find(p), params, slug);
4022
+ },
4023
+ findAll(params) {
4024
+ return collectAllPages((p) => client.find(p), params, slug);
4025
+ },
3851
4026
  async findById(id) {
3852
4027
  const s = await snap.findById(id);
3853
4028
  return s ? entityToRow(s) : void 0;
@@ -3899,7 +4074,7 @@ function wrapAsSdkData(entityData) {
3899
4074
  function getAccessor(slug) {
3900
4075
  let accessor = cache.get(slug);
3901
4076
  if (!accessor) {
3902
- accessor = toSdkCollectionClient(entityData.collection(slug));
4077
+ accessor = toSdkCollectionClient(entityData.collection(slug), slug);
3903
4078
  cache.set(slug, accessor);
3904
4079
  }
3905
4080
  return accessor;
@@ -3916,8 +4091,12 @@ function wrapAsSdkData(entityData) {
3916
4091
  *
3917
4092
  * This is the developer-facing SDK data layer used by backend framework
3918
4093
  * 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).
4094
+ * identical in shape to the frontend SDK client, down to how a relation is
4095
+ * served: a foreign key stays a foreign key, and a relation named in `include`
4096
+ * arrives as the target's own columns. The `{ __type: "relation" }` envelope is
4097
+ * the admin's view-model and never reaches here.
4098
+ *
4099
+ * The admin uses {@link buildRebaseData} (Entity) over its own driver.
3921
4100
  */
3922
4101
  function buildSdkData(driver) {
3923
4102
  return wrapAsSdkData(buildRebaseData(driver));
@@ -3989,6 +4168,6 @@ async function detectJunctionTables(executeSql) {
3989
4168
  return junctionTables;
3990
4169
  }
3991
4170
  //#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 };
4171
+ export { toSnakeCase as A, createRelationRefWithData as C, getPolicyNamesForRule as D, generateForeignKeyName as E, DEFAULT_ONE_OF_VALUE as M, mergeDeep 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_TYPE as j, camelCase 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 };
3993
4172
 
3994
- //# sourceMappingURL=src-3VmUJ8Xn.js.map
4173
+ //# sourceMappingURL=src-Cn0_95uv.js.map