@rebasepro/common 0.28.2 → 0.29.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.
@@ -1,13 +1,9 @@
1
1
  import { DataDriver, RebaseData, RebaseSdkData } from "@rebasepro/types";
2
2
  /**
3
- * Derive the response key an aggregate comes back under.
4
- *
5
- * `sum(total)` → `sum_total`, `count()` → `count`. Written once, here, because
6
- * the REST parser derives the same alias from `?select=sum(total)` and the two
7
- * have to agree — a caller reading `row.sum_total` off an SDK result and off an
8
- * HTTP response is reading the same key or the SDK is broken.
3
+ * The key an aggregate comes back under — `@rebasepro/types`' derivation,
4
+ * which the REST parser and the HTTP client use too.
9
5
  */
10
- export declare function aggregateAlias(fn: string, field?: string): string;
6
+ export { aggregateAlias } from "@rebasepro/types";
11
7
  export interface EntityDataOptions {
12
8
  /**
13
9
  * Look up a collection's config by slug, to derive row addresses from its
@@ -45,6 +41,18 @@ export interface EntityDataOptions {
45
41
  */
46
42
  resolveAccessor?: (property: string) => string;
47
43
  }
44
+ /**
45
+ * A row as the server sends it, in the admin's view model of its collection:
46
+ * the `values` `buildRebaseData` and `wrapAsEntityData` give an `Entity` of
47
+ * that collection — a date as a `Date`, a relation as an `EntityRelation`,
48
+ * also under its property's key where the row carries only the foreign key —
49
+ * and a `reference` property's id as an `EntityReference`. A collection the
50
+ * resolver does not know leaves the row as it is.
51
+ *
52
+ * For code that holds a flat row and hands it to what expects the admin's
53
+ * values, such as a collection's `browserCallbacks` or a preview.
54
+ */
55
+ export declare function rowToViewModel(row: Record<string, unknown>, slug: string, resolveCollection: NonNullable<EntityDataOptions["resolveCollection"]>): Record<string, unknown>;
48
56
  export declare function buildRebaseData(driver: DataDriver, options?: EntityDataOptions): RebaseData;
49
57
  /**
50
58
  * Wrap a flat {@link RebaseSdkData} into a Entity-shaped {@link RebaseData}.
@@ -1,5 +1,5 @@
1
1
  import { buildCompositeId, resolvePrimaryKeys } from "../util/identity.es.js";
2
- import { resolveCollectionRelations } from "../util/relations.es.js";
2
+ import { fieldKeyForColumn, resolveCollectionRelations } from "../util/relations.es.js";
3
3
  import { cursorToStartAfter, decodeCursor, reconcileCursorOrder } from "./cursor.es.js";
4
4
  import { normalizeOrderBy } from "./sort-dialect.es.js";
5
5
  import { mergeIncludeSpecs } from "./include-spec.es.js";
@@ -7,7 +7,7 @@ import { etagOf } from "./row-etag.es.js";
7
7
  import { QueryBuilder } from "./query_builder.es.js";
8
8
  import { collectAllPages, paginateFind, resolveFindWindow } from "./paginate.es.js";
9
9
  import { deserializeFilter } from "./filter-dialect.es.js";
10
- import { DEFAULT_VECTOR_LIST_LIMIT, EntityRelation, RebaseApiError, RebaseClientError, isUnsupported, sortKeyToString, unsupportedMethod } from "@rebasepro/types";
10
+ import { DEFAULT_VECTOR_LIST_LIMIT, EntityReference, EntityRelation, RebaseApiError, RebaseClientError, aggregateAlias, isUnsupported, sortKeyToString, toDriverAggregate, toDriverAggregateGroup, unsupportedMethod } from "@rebasepro/types";
11
11
  import { toSnakeCase, toWireKey } from "@rebasepro/utils";
12
12
  //#region src/data/buildRebaseData.ts
13
13
  /**
@@ -20,25 +20,6 @@ var noRealtime = (slug) => `Realtime is not available for "${slug}": its data so
20
20
  /** What a client says when its data source cannot count. */
21
21
  var noCount = (slug) => `Counting is not available for "${slug}": its data source does not support it.`;
22
22
  /**
23
- * Derive the response key an aggregate comes back under.
24
- *
25
- * `sum(total)` → `sum_total`, `count()` → `count`. Written once, here, because
26
- * the REST parser derives the same alias from `?select=sum(total)` and the two
27
- * have to agree — a caller reading `row.sum_total` off an SDK result and off an
28
- * HTTP response is reading the same key or the SDK is broken.
29
- */
30
- function aggregateAlias(fn, field) {
31
- return field ? `${fn}_${field}` : fn;
32
- }
33
- function toDriverAggregate(select) {
34
- const field = select.field;
35
- return {
36
- fn: select.fn,
37
- field,
38
- alias: aggregateAlias(select.fn, field)
39
- };
40
- }
41
- /**
42
23
  * What a client says when its data source cannot aggregate.
43
24
  *
44
25
  * A stub rather than a fallback that fetches and reduces in JavaScript: that
@@ -50,6 +31,8 @@ var noTrash = (slug) => `The trash is not available for "${slug}": its data sour
50
31
  /** What `softDelete: true` names the field a delete stamps; a restore clears it. */
51
32
  var DEFAULT_SOFT_DELETE_FIELD = "deletedAt";
52
33
  var noAggregate = (slug) => `Aggregates are not available for "${slug}": its data source does not implement them.`;
34
+ /** What a client says when its data source has no materialized views to refresh. */
35
+ var noRefresh = (slug) => `"${slug}" cannot be refreshed: its data source has no materialized views.`;
53
36
  function createPrimaryKeyResolver(options) {
54
37
  const cache = /* @__PURE__ */ new Map();
55
38
  const warned = /* @__PURE__ */ new Set();
@@ -109,7 +92,7 @@ function toViewModelValues(values, properties, collection, resolveCollection) {
109
92
  if (!(key in values)) {
110
93
  const fkRelation = relations[key];
111
94
  const column = fkRelation && "localKey" in fkRelation ? fkRelation.localKey : void 0;
112
- const fk = column !== void 0 ? values[column] ?? values[toWireKey(column)] : void 0;
95
+ const fk = column !== void 0 ? values[fieldKeyForColumn(collection, column)] ?? values[column] ?? values[toWireKey(column)] : void 0;
113
96
  const fkTarget = fkRelation?.targetSlug;
114
97
  if (fkTarget && (typeof fk === "string" || typeof fk === "number")) write(key, new EntityRelation(fk, fkTarget));
115
98
  continue;
@@ -175,6 +158,10 @@ function rowToEntity(row, slug, primaryKeys = [], toViewModel) {
175
158
  ...etag ? { etag } : {}
176
159
  };
177
160
  }
161
+ /** {@link rowToEntity} over a page of rows, the primary keys looked up once for all of them. */
162
+ function rowsToEntities(rows, slug, primaryKeys, toViewModel) {
163
+ return rows.map((row) => rowToEntity(row, slug, primaryKeys, toViewModel));
164
+ }
178
165
  /**
179
166
  * The relation envelope `toFlatRow` writes where a relation was:
180
167
  * `{ id, path, __type: "relation", data: { id, path, values } }`. It is the
@@ -274,14 +261,14 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel, stor
274
261
  }) : void 0
275
262
  };
276
263
  };
277
- const { fetched, counted } = driver.readTogether ? await driver.readTogether(read) : await read(driver);
264
+ const { fetched, counted } = counting && driver.readTogether ? await driver.readTogether(read) : await read(driver);
278
265
  const rows = probing ? fetched.slice(0, limit) : fetched;
279
266
  const total = counted ?? rows.length + offset;
280
267
  const hasMore = probing ? fetched.length > limit : offset + rows.length < total;
281
268
  const last = rows[rows.length - 1];
282
269
  const nextCursor = hasMore && last && driver.restFetchService?.cursorFor ? driver.restFetchService.cursorFor(storedPath(), last, orderBy) : void 0;
283
270
  return {
284
- data: rows.map((row) => rowToEntity(row, slug, getPks(), toViewModel)),
271
+ data: rowsToEntities(rows, slug, getPks(), toViewModel),
285
272
  meta: {
286
273
  total,
287
274
  limit,
@@ -319,7 +306,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel, stor
319
306
  const limit = (params.groupBy?.length ?? 0) > 0 ? params.limit : void 0;
320
307
  const rows = await driver.restFetchService.aggregate(storedPath(), {
321
308
  aggregates: params.select.map(toDriverAggregate),
322
- groupBy: params.groupBy,
309
+ groupBy: params.groupBy?.map((group) => toDriverAggregateGroup(group)),
323
310
  filter: params.where ? deserializeFilter(params.where) : void 0,
324
311
  logical: params.logical,
325
312
  searchString: params.searchString,
@@ -340,6 +327,7 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel, stor
340
327
  });
341
328
  return page;
342
329
  } : void 0,
330
+ refresh: driver.refreshView ? (options) => driver.refreshView(storedPath(), options) : void 0,
343
331
  async create(data, id) {
344
332
  return rowToEntity(await driver.save({
345
333
  path: storedPath(),
@@ -349,12 +337,12 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel, stor
349
337
  }), slug, getPks(), toViewModel);
350
338
  },
351
339
  createMany: driver.saveMany ? async (data, options) => {
352
- return (await driver.saveMany({
340
+ return rowsToEntities(await driver.saveMany({
353
341
  path: storedPath(),
354
342
  rows: data,
355
343
  upsert: options?.upsert,
356
344
  onConflict: options?.onConflict
357
- })).map((row) => rowToEntity(row, slug, getPks(), toViewModel));
345
+ }), slug, getPks(), toViewModel);
358
346
  } : void 0,
359
347
  async update(id, data) {
360
348
  return rowToEntity(await driver.save({
@@ -371,13 +359,13 @@ function createDriverAccessor(driver, slug, getPks = () => [], toViewModel, stor
371
359
  } });
372
360
  },
373
361
  updateMany: driver.updateMany ? async (updates) => {
374
- return (await driver.updateMany({
362
+ return rowsToEntities(await driver.updateMany({
375
363
  path: storedPath(),
376
364
  updates: updates.map((u) => ({
377
365
  id: u.id,
378
366
  values: u.data
379
367
  }))
380
- })).map((row) => rowToEntity(row, slug, getPks(), toViewModel));
368
+ }), slug, getPks(), toViewModel);
381
369
  } : void 0,
382
370
  deleteMany: driver.deleteMany ? async (ids) => {
383
371
  await driver.deleteMany({
@@ -497,6 +485,69 @@ function createViewModelConverter(options) {
497
485
  };
498
486
  };
499
487
  }
488
+ /**
489
+ * A row as the server sends it, in the admin's view model of its collection:
490
+ * the `values` `buildRebaseData` and `wrapAsEntityData` give an `Entity` of
491
+ * that collection — a date as a `Date`, a relation as an `EntityRelation`,
492
+ * also under its property's key where the row carries only the foreign key —
493
+ * and a `reference` property's id as an `EntityReference`. A collection the
494
+ * resolver does not know leaves the row as it is.
495
+ *
496
+ * For code that holds a flat row and hands it to what expects the admin's
497
+ * values, such as a collection's `browserCallbacks` or a preview.
498
+ */
499
+ function rowToViewModel(row, slug, resolveCollection) {
500
+ const collection = resolveCollection(slug);
501
+ if (!collection) return row;
502
+ return withReferences(toViewModelValues(row, collection.properties, collection, resolveCollection), collection.properties);
503
+ }
504
+ /**
505
+ * Each `reference` property's value — the referenced id, or `{ id, path }` —
506
+ * as the `EntityReference` the admin's reference field and preview read; an
507
+ * array of references item by item.
508
+ */
509
+ function withReferences(values, properties) {
510
+ if (!properties) return values;
511
+ let out;
512
+ for (const [key, property] of Object.entries(properties)) {
513
+ if (!(key in values)) continue;
514
+ const value = values[key];
515
+ const path = referencePath(property);
516
+ const arrayPath = isPlainRecord(property) && property.type === "array" ? referencePath(property.of) : void 0;
517
+ if (path !== void 0) {
518
+ out = out ?? { ...values };
519
+ out[key] = toReference(value, path);
520
+ } else if (arrayPath !== void 0 && Array.isArray(value)) {
521
+ out = out ?? { ...values };
522
+ out[key] = value.map((item) => toReference(item, arrayPath));
523
+ }
524
+ }
525
+ return out ?? values;
526
+ }
527
+ /** The target path of a `reference` property; `undefined` for any other. */
528
+ function referencePath(property) {
529
+ if (!isPlainRecord(property) || property.type !== "reference") return void 0;
530
+ return typeof property.path === "string" ? property.path : "";
531
+ }
532
+ function toReference(value, path) {
533
+ if (value === null || value === void 0 || value === "") return null;
534
+ if (value instanceof EntityReference) return value;
535
+ if ((typeof value === "string" || typeof value === "number") && path) return new EntityReference({
536
+ id: String(value),
537
+ path
538
+ });
539
+ if (isPlainRecord(value) && (typeof value.id === "string" || typeof value.id === "number")) {
540
+ const target = typeof value.path === "string" ? value.path : path;
541
+ if (target) return new EntityReference({
542
+ id: String(value.id),
543
+ path: target
544
+ });
545
+ }
546
+ return value;
547
+ }
548
+ function isPlainRecord(value) {
549
+ return typeof value === "object" && value !== null && !Array.isArray(value);
550
+ }
500
551
  function buildRebaseData(driver, options) {
501
552
  const cache = /* @__PURE__ */ new Map();
502
553
  const primaryKeysFor = createPrimaryKeyResolver(options);
@@ -797,6 +848,7 @@ function toSdkCollectionClient(snap, slug = "collection") {
797
848
  distinct: (enabled) => new SdkQueryBuilder(client).distinct(enabled),
798
849
  after: (cursor) => new SdkQueryBuilder(client).after(cursor),
799
850
  aggregate: snap.aggregate ? (params) => snap.aggregate(params) : unsupportedMethod(noAggregate(slug)),
851
+ refresh: snap.refresh ? (options) => snap.refresh(options) : unsupportedMethod(noRefresh(slug)),
800
852
  trash: toSdkTrashClient(snap, slug, () => client)
801
853
  };
802
854
  return client;
@@ -865,7 +917,7 @@ function toEntityAccessor(sdk, slug, getPks = () => [], toViewModel, viewModelSo
865
917
  async find(params) {
866
918
  const res = await sdk.find(params);
867
919
  return {
868
- data: res.data.map((row) => rowToEntity(row, slug, getPks(), toViewModel)),
920
+ data: rowsToEntities(res.data, slug, getPks(), toViewModel),
869
921
  meta: res.meta
870
922
  };
871
923
  },
@@ -877,7 +929,7 @@ function toEntityAccessor(sdk, slug, getPks = () => [], toViewModel, viewModelSo
877
929
  return rowToEntity(await sdk.create(data, id), slug, getPks(), toViewModel);
878
930
  },
879
931
  createMany: sdk.createMany ? async (data, options) => {
880
- return (await sdk.createMany(data, options)).map((row) => rowToEntity(row, slug, getPks(), toViewModel));
932
+ return rowsToEntities(await sdk.createMany(data, options), slug, getPks(), toViewModel);
881
933
  } : void 0,
882
934
  async update(id, data, options) {
883
935
  const row = options?.ifMatch ? await sdk.update(id, data, { ifMatch: options.ifMatch }) : await sdk.update(id, data);
@@ -889,6 +941,7 @@ function toEntityAccessor(sdk, slug, getPks = () => [], toViewModel, viewModelSo
889
941
  },
890
942
  count: isUnsupported(sdk.count) ? void 0 : (params) => sdk.count(params),
891
943
  aggregate: isUnsupported(sdk.aggregate) ? void 0 : (params) => sdk.aggregate(params),
944
+ refresh: isUnsupported(sdk.refresh) ? void 0 : (options) => sdk.refresh(options),
892
945
  listen: isUnsupported(sdk.listen) ? void 0 : (params, onUpdate, onError) => {
893
946
  let built = /* @__PURE__ */ new WeakMap();
894
947
  let builtWith;
@@ -918,7 +971,7 @@ function toEntityAccessor(sdk, slug, getPks = () => [], toViewModel, viewModelSo
918
971
  async find(params) {
919
972
  const res = await sdk.trash.find(params);
920
973
  return {
921
- data: res.data.map((row) => rowToEntity(row, slug, getPks(), toViewModel)),
974
+ data: rowsToEntities(res.data, slug, getPks(), toViewModel),
922
975
  meta: res.meta
923
976
  };
924
977
  },
@@ -1033,6 +1086,6 @@ function buildSdkData(driver, options) {
1033
1086
  return wrapAsSdkData(buildRebaseData(driver), options);
1034
1087
  }
1035
1088
  //#endregion
1036
- export { aggregateAlias, buildRebaseData, buildSdkData, wrapAsEntityData, wrapAsSdkData };
1089
+ export { aggregateAlias, buildRebaseData, buildSdkData, rowToViewModel, wrapAsEntityData, wrapAsSdkData };
1037
1090
 
1038
1091
  //# sourceMappingURL=buildRebaseData.es.js.map