@rebasepro/common 0.19.2-canary.g98b88fd → 0.19.2-canary.gef769df

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.
@@ -19,10 +19,20 @@ export interface EntityDataOptions {
19
19
  */
20
20
  resolveCollection?: (slug: string) => {
21
21
  properties?: Record<string, unknown>;
22
- relations?: unknown[];
23
- slug?: string;
24
22
  } | undefined;
25
23
  }
24
+ /**
25
+ * Build a `RebaseData` object from a `DataDriver` using JavaScript Proxy.
26
+ *
27
+ * This is the key bridge: any property access like `data.products` returns
28
+ * a `CollectionAccessor` backed by the underlying DataDriver, without
29
+ * needing per-collection code generation.
30
+ *
31
+ * @example
32
+ * const data = buildRebaseData(driver);
33
+ * await data.products.create({ name: "Camera", price: 299 });
34
+ * const { data: items } = await data.products.find({ where: { status: ["==", "published"] } });
35
+ */
26
36
  export declare function buildRebaseData(driver: DataDriver, options?: EntityDataOptions): RebaseData;
27
37
  /**
28
38
  * Wrap a flat {@link RebaseSdkData} into a Entity-shaped {@link RebaseData}.
package/dist/index.es.js CHANGED
@@ -10,28 +10,10 @@ var DEFAULT_ONE_OF_VALUE = "value";
10
10
  function isPropertyBuilder(property) {
11
11
  return typeof property?.dynamicProps === "function";
12
12
  }
13
- /**
14
- * What a form opens with: a value for every property it can write.
15
- *
16
- * `excludeFromApi` columns are left out, and that is the whole of the rule —
17
- * they are not part of the API surface in either direction, so there is nothing
18
- * for a form to open showing and nothing it may send back. Including them was
19
- * not cosmetic: the baseline is what gets submitted, so a new record carried
20
- * `passwordHash: null` and `emailVerificationToken: null` into the create, and
21
- * the server refused the whole write with "these columns are the server's to
22
- * set" — the users collection could not be added to from the panel at all. The
23
- * fields were invisible on screen (`admin.disabled.hidden`), which is what made
24
- * the error read as being about the roles the operator *had* just edited.
25
- *
26
- * Server-side defaulting does not come through here: `applyDefaultValuesOnCreate`
27
- * asks each property for its own default, so an excluded column with a declared
28
- * `defaultValue` is still filled in on an in-process write.
29
- */
30
13
  function getDefaultValuesFor(properties) {
31
14
  if (!properties) return {};
32
15
  return Object.entries(properties).map(([key, property]) => {
33
16
  if (!property) return {};
34
- if (property.excludeFromApi) return {};
35
17
  const value = getDefaultValueFor(property);
36
18
  return value === void 0 ? {} : { [key]: value };
37
19
  }).reduce((a, b) => ({
@@ -142,18 +124,18 @@ function updateUserAutoValues({ inputValues, properties, status, uid }) {
142
124
  function applyDefaultValuesOnCreate(values, properties) {
143
125
  if (!properties) return values ?? {};
144
126
  const result = { ...values ?? {} };
127
+ const defaults = getDefaultValuesFor(properties);
145
128
  for (const [key, property] of Object.entries(properties)) {
146
129
  if (!property) continue;
147
130
  if (!declaresDefault(property)) continue;
148
- const defaultValue = getDefaultValueFor(property);
149
131
  if (result[key] !== void 0) {
150
132
  if (property.type === "map" && property.defaultValue === void 0 && isPlainObject(result[key])) result[key] = {
151
- ...defaultValue ?? {},
133
+ ...defaults[key] ?? {},
152
134
  ...result[key]
153
135
  };
154
136
  continue;
155
137
  }
156
- if (defaultValue !== void 0) result[key] = defaultValue;
138
+ if (defaults[key] !== void 0) result[key] = defaults[key];
157
139
  }
158
140
  return result;
159
141
  }
@@ -5843,89 +5825,6 @@ function createPrimaryKeyResolver(options) {
5843
5825
  };
5844
5826
  }
5845
5827
  /**
5846
- * Build the admin's view model out of the row the wire serves.
5847
- *
5848
- * The wire has ONE shape, for every consumer: flat columns, typed the way the
5849
- * database typed them, and a relation rendered as the target's own columns (or
5850
- * only its foreign key, when nothing asked for it). That is the REST contract,
5851
- * what `find()` returns, what `listen()` pushes, and what the generated types
5852
- * describe.
5853
- *
5854
- * The admin renders neither of those directly. Its date field requires a real
5855
- * `Date` and rejects a string outright; its relation cells read `.data.values`
5856
- * off a relation ref. Those requirements are the *admin's*, so they are met
5857
- * here — in the browser, from the collection config the panel already has —
5858
- * rather than by asking the server for a second wire shape.
5859
- *
5860
- * That second shape is what this replaces. Until 2026-09-09 the realtime wire
5861
- * carried the view model and every other read carried flat rows, so `find()`
5862
- * and `listen()` answered one query two ways; unifying the wire without doing
5863
- * this conversion is what left every date cell reading "Invalid date value"
5864
- * and every relation cell "Unexpected value".
5865
- *
5866
- * Values already in view-model form pass through untouched: a driver that
5867
- * still sends `{ __type: "date" }` or a relation ref (the client revives both)
5868
- * is served by the same walk.
5869
- */
5870
- function toViewModelValues(values, properties, collection, resolveCollection) {
5871
- if (!properties) return values;
5872
- const relations = collection ? resolveCollectionRelations(collection) : {};
5873
- let out;
5874
- const write = (key, value) => {
5875
- out = out ?? { ...values };
5876
- out[key] = value;
5877
- };
5878
- for (const [key, rawProperty] of Object.entries(properties)) {
5879
- const property = rawProperty;
5880
- if (!property) continue;
5881
- if (!(key in values)) {
5882
- const fkRelation = relations[key];
5883
- const column = fkRelation && "localKey" in fkRelation ? fkRelation.localKey : void 0;
5884
- const fk = column !== void 0 ? values[column] ?? values[toWireKey(column)] : void 0;
5885
- const fkTarget = fkRelation?.targetSlug;
5886
- if (fkTarget && (typeof fk === "string" || typeof fk === "number")) write(key, new EntityRelation(fk, fkTarget));
5887
- continue;
5888
- }
5889
- const value = values[key];
5890
- if (value === null || value === void 0) continue;
5891
- const relation = relations[key];
5892
- if (relation && (property.type === "relation" || property.of?.type === "relation" || property.type === "array")) {
5893
- const target = relation.targetSlug;
5894
- if (!target) continue;
5895
- const targetProperties = resolveCollection?.(target)?.properties;
5896
- const targetCollection = resolveCollection?.(target);
5897
- const toRef = (item) => {
5898
- if (item instanceof EntityRelation) return item;
5899
- if (typeof item === "object" && item !== null && "__type" in item) return item;
5900
- if (typeof item === "object" && item !== null) {
5901
- const row = item;
5902
- const keys = targetCollection ? resolvePrimaryKeys(targetCollection) : [];
5903
- const id = keys.length > 0 ? buildCompositeId(row, keys) : row.id;
5904
- if (id === void 0 || id === null || id === "") return item;
5905
- return new EntityRelation(id, target, {
5906
- id,
5907
- path: target,
5908
- values: toViewModelValues(row, targetProperties, targetCollection, resolveCollection)
5909
- });
5910
- }
5911
- if (typeof item === "string" || typeof item === "number") return new EntityRelation(item, target);
5912
- return item;
5913
- };
5914
- write(key, Array.isArray(value) ? value.map(toRef) : toRef(value));
5915
- continue;
5916
- }
5917
- if (property.type === "date" && !(value instanceof Date)) {
5918
- if (typeof value === "string" || typeof value === "number") {
5919
- const date = new Date(value);
5920
- write(key, isNaN(date.getTime()) ? null : date);
5921
- }
5922
- continue;
5923
- }
5924
- if (property.type === "map" && property.properties && typeof value === "object" && !Array.isArray(value)) write(key, toViewModelValues(value, property.properties, void 0, resolveCollection));
5925
- }
5926
- return out ?? values;
5927
- }
5928
- /**
5929
5828
  * Give a flat row the Entity view-model the admin renders.
5930
5829
  *
5931
5830
  * The address is *derived here* — it is not a column, and the row it came from
@@ -5936,12 +5835,12 @@ function toViewModelValues(values, properties, collection, resolveCollection) {
5936
5835
  * `primaryKeys` empty falls back to a literal `id` on the row: drivers other
5937
5836
  * than postgres still serve rows with one, and this keeps them working.
5938
5837
  */
5939
- function rowToEntity(row, slug, primaryKeys = [], toViewModel) {
5838
+ function rowToEntity(row, slug, primaryKeys = []) {
5940
5839
  const { _matches, ...values } = row;
5941
5840
  return {
5942
5841
  id: primaryKeys.length > 0 ? buildCompositeId(row, primaryKeys) : row.id,
5943
5842
  path: slug,
5944
- values: toViewModel ? toViewModel(values) : values,
5843
+ values,
5945
5844
  ..._matches ? { searchMatches: _matches } : {}
5946
5845
  };
5947
5846
  }
@@ -5961,16 +5860,14 @@ function inlineEnvelope(envelope) {
5961
5860
  * Replace every relation envelope on a row with the target's flat columns.
5962
5861
  *
5963
5862
  * The SDK serves one relation shape — the inlined one (see
5964
- * {@link RestFetchService}) — and Postgres now serves it on every read, so
5965
- * against that driver this walk finds nothing to do. It stays for the drivers
5966
- * whose own `fetchCollection` still answers with refs: a developer reading
5967
- * through this accessor gets one shape whichever driver is underneath.
5863
+ * {@link RestFetchService}) — and reads that come back through a *driver*
5864
+ * method rather than the REST pipeline still carry envelopes. Realtime is the
5865
+ * one such read left: there is no `listenForRest`, so the rows arrive shaped
5866
+ * for the admin and are flattened here instead.
5968
5867
  *
5969
5868
  * Only applied where the REST pipeline is the contract (see `find`); a driver
5970
- * without a `restFetchService` keeps whatever it returns.
5971
- *
5972
- * Note this is NOT how the admin gets its view model — that is built in the
5973
- * browser by {@link toViewModelValues}, from the same flat row.
5869
+ * without a `restFetchService` keeps whatever it returns, so the admin's own
5870
+ * path through {@link buildRebaseData} is untouched.
5974
5871
  */
5975
5872
  function inlineRelationRefs(row) {
5976
5873
  let out;
@@ -5983,7 +5880,7 @@ function inlineRelationRefs(row) {
5983
5880
  }
5984
5881
  return out ?? row;
5985
5882
  }
5986
- function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
5883
+ function createDriverAccessor(driver, slug, getPks = () => []) {
5987
5884
  const accessor = {
5988
5885
  async find(params) {
5989
5886
  const filter = params?.where ? deserializeFilter(params.where) : void 0;
@@ -6032,7 +5929,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6032
5929
  const last = rows[rows.length - 1];
6033
5930
  const nextCursor = hasMore && last && driver.restFetchService?.cursorFor ? driver.restFetchService.cursorFor(slug, last, orderBy) : void 0;
6034
5931
  return {
6035
- data: rows.map((row) => rowToEntity(row, slug, getPks(), toViewModel)),
5932
+ data: rows.map((row) => rowToEntity(row, slug, getPks())),
6036
5933
  meta: {
6037
5934
  total,
6038
5935
  limit,
@@ -6048,7 +5945,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6048
5945
  path: slug,
6049
5946
  id
6050
5947
  });
6051
- return row ? rowToEntity(row, slug, getPks(), toViewModel) : void 0;
5948
+ return row ? rowToEntity(row, slug, getPks()) : void 0;
6052
5949
  },
6053
5950
  aggregate: driver.restFetchService?.aggregate ? async (params) => driver.restFetchService.aggregate(slug, {
6054
5951
  aggregates: params.select.map(toDriverAggregate),
@@ -6064,7 +5961,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6064
5961
  values: data,
6065
5962
  id,
6066
5963
  status: "new"
6067
- }), slug, getPks(), toViewModel);
5964
+ }), slug, getPks());
6068
5965
  },
6069
5966
  createMany: driver.saveMany ? async (data, options) => {
6070
5967
  return (await driver.saveMany({
@@ -6072,7 +5969,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6072
5969
  rows: data,
6073
5970
  upsert: options?.upsert,
6074
5971
  onConflict: options?.onConflict
6075
- })).map((row) => rowToEntity(row, slug, getPks(), toViewModel));
5972
+ })).map((row) => rowToEntity(row, slug, getPks()));
6076
5973
  } : void 0,
6077
5974
  async update(id, data) {
6078
5975
  return rowToEntity(await driver.save({
@@ -6080,7 +5977,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6080
5977
  values: data,
6081
5978
  id,
6082
5979
  status: "existing"
6083
- }), slug, getPks(), toViewModel);
5980
+ }), slug, getPks());
6084
5981
  },
6085
5982
  async delete(id) {
6086
5983
  return driver.delete({ row: {
@@ -6096,7 +5993,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6096
5993
  id: u.id,
6097
5994
  values: u.data
6098
5995
  }))
6099
- })).map((row) => rowToEntity(row, slug, getPks(), toViewModel));
5996
+ })).map((row) => rowToEntity(row, slug, getPks()));
6100
5997
  } : void 0,
6101
5998
  deleteMany: driver.deleteMany ? async (ids) => {
6102
5999
  await driver.deleteMany({
@@ -6128,7 +6025,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6128
6025
  vectorSearch: params?.vectorSearch,
6129
6026
  onUpdate: (entities) => {
6130
6027
  onUpdate({
6131
- data: entities.map((row) => rowToEntity(normalize(row), slug, getPks(), toViewModel)),
6028
+ data: entities.map((row) => rowToEntity(normalize(row), slug, getPks())),
6132
6029
  meta: {
6133
6030
  total: offset + entities.length,
6134
6031
  limit,
@@ -6145,7 +6042,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6145
6042
  return driver.listenOne({
6146
6043
  path: slug,
6147
6044
  id,
6148
- onUpdate: (entity) => onUpdate(entity ? rowToEntity(normalize(entity), slug, getPks(), toViewModel) : void 0),
6045
+ onUpdate: (entity) => onUpdate(entity ? rowToEntity(normalize(entity), slug, getPks()) : void 0),
6149
6046
  onError
6150
6047
  });
6151
6048
  } : void 0,
@@ -6187,32 +6084,13 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
6187
6084
  * await data.products.create({ name: "Camera", price: 299 });
6188
6085
  * const { data: items } = await data.products.find({ where: { status: ["==", "published"] } });
6189
6086
  */
6190
- /**
6191
- * The view-model converter for one collection, or `undefined` when there is no
6192
- * collection config to build it from.
6193
- *
6194
- * Absent is the honest answer for every consumer that is not the admin: the
6195
- * flat SDK derives itself from this same layer (`buildSdkData`) and must keep
6196
- * the wire's own types, and it registers no collection resolver.
6197
- */
6198
- function createViewModelConverter(options) {
6199
- if (!options?.resolveCollection) return () => void 0;
6200
- return function converterFor(slug) {
6201
- return (values) => {
6202
- const collection = options.resolveCollection?.(slug);
6203
- if (!collection) return values;
6204
- return toViewModelValues(values, collection.properties, collection, options.resolveCollection);
6205
- };
6206
- };
6207
- }
6208
6087
  function buildRebaseData(driver, options) {
6209
6088
  const cache = /* @__PURE__ */ new Map();
6210
6089
  const primaryKeysFor = createPrimaryKeyResolver(options);
6211
- const viewModelFor = createViewModelConverter(options);
6212
6090
  function getAccessor(slug) {
6213
6091
  let accessor = cache.get(slug);
6214
6092
  if (!accessor) {
6215
- accessor = createDriverAccessor(driver, slug, () => primaryKeysFor(slug), viewModelFor(slug));
6093
+ accessor = createDriverAccessor(driver, slug, () => primaryKeysFor(slug));
6216
6094
  cache.set(slug, accessor);
6217
6095
  }
6218
6096
  return accessor;
@@ -6477,29 +6355,29 @@ function toSdkCollectionClient(snap, slug = "collection") {
6477
6355
  * {@link CollectionAccessor}. Every returned row is re-wrapped into the
6478
6356
  * `{ id, path, values }` view-model the admin panel renders.
6479
6357
  */
6480
- function toEntityAccessor(sdk, slug, getPks = () => [], toViewModel) {
6358
+ function toEntityAccessor(sdk, slug, getPks = () => []) {
6481
6359
  const accessor = {
6482
6360
  async find(params) {
6483
6361
  const res = await sdk.find(params);
6484
6362
  return {
6485
- data: res.data.map((row) => rowToEntity(row, slug, getPks(), toViewModel)),
6363
+ data: res.data.map((row) => rowToEntity(row, slug, getPks())),
6486
6364
  meta: res.meta
6487
6365
  };
6488
6366
  },
6489
6367
  async findById(id) {
6490
6368
  const row = await sdk.findById(id);
6491
- return row ? rowToEntity(row, slug, getPks(), toViewModel) : void 0;
6369
+ return row ? rowToEntity(row, slug, getPks()) : void 0;
6492
6370
  },
6493
6371
  async create(data, id) {
6494
- return rowToEntity(await sdk.create(data, id), slug, getPks(), toViewModel);
6372
+ return rowToEntity(await sdk.create(data, id), slug, getPks());
6495
6373
  },
6496
6374
  createMany: sdk.createMany ? async (data, options) => {
6497
- return (await sdk.createMany(data, options)).map((row) => rowToEntity(row, slug, getPks(), toViewModel));
6375
+ return (await sdk.createMany(data, options)).map((row) => rowToEntity(row, slug, getPks()));
6498
6376
  } : void 0,
6499
6377
  async update(id, data) {
6500
6378
  const row = await sdk.update(id, data);
6501
6379
  if (!row) throw new Error(`Update returned no data for id ${id}`);
6502
- return rowToEntity(row, slug, getPks(), toViewModel);
6380
+ return rowToEntity(row, slug, getPks());
6503
6381
  },
6504
6382
  delete(id) {
6505
6383
  return sdk.delete(id);
@@ -6507,10 +6385,10 @@ function toEntityAccessor(sdk, slug, getPks = () => [], toViewModel) {
6507
6385
  count: isUnsupported(sdk.count) ? void 0 : (params) => sdk.count(params),
6508
6386
  aggregate: isUnsupported(sdk.aggregate) ? void 0 : (params) => sdk.aggregate(params),
6509
6387
  listen: isUnsupported(sdk.listen) ? void 0 : (params, onUpdate, onError) => sdk.listen(params, (res) => onUpdate({
6510
- data: res.data.map((row) => rowToEntity(row, slug, getPks(), toViewModel)),
6388
+ data: res.data.map((row) => rowToEntity(row, slug, getPks())),
6511
6389
  meta: res.meta
6512
6390
  }), onError),
6513
- listenById: isUnsupported(sdk.listenById) ? void 0 : (id, onUpdate, onError) => sdk.listenById(id, (row) => onUpdate(row ? rowToEntity(row, slug, getPks(), toViewModel) : void 0), onError),
6391
+ listenById: isUnsupported(sdk.listenById) ? void 0 : (id, onUpdate, onError) => sdk.listenById(id, (row) => onUpdate(row ? rowToEntity(row, slug, getPks()) : void 0), onError),
6514
6392
  where(columnOrCondition, operator, value) {
6515
6393
  const builder = new QueryBuilder(accessor);
6516
6394
  if (typeof columnOrCondition === "object") return builder.where(columnOrCondition);
@@ -6546,11 +6424,10 @@ function toEntityAccessor(sdk, slug, getPks = () => [], toViewModel) {
6546
6424
  function wrapAsEntityData(sdkData, options) {
6547
6425
  const cache = /* @__PURE__ */ new Map();
6548
6426
  const primaryKeysFor = createPrimaryKeyResolver(options);
6549
- const viewModelFor = createViewModelConverter(options);
6550
6427
  function getAccessor(slug) {
6551
6428
  let accessor = cache.get(slug);
6552
6429
  if (!accessor) {
6553
- accessor = toEntityAccessor(sdkData.collection(slug), slug, () => primaryKeysFor(slug), viewModelFor(slug));
6430
+ accessor = toEntityAccessor(sdkData.collection(slug), slug, () => primaryKeysFor(slug));
6554
6431
  cache.set(slug, accessor);
6555
6432
  }
6556
6433
  return accessor;