@rebasepro/common 0.19.2-canary.g201749f → 0.19.2-canary.g2cd43e9
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.
- package/dist/data/buildRebaseData.d.ts +2 -12
- package/dist/index.es.js +132 -27
- package/dist/index.es.js.map +1 -1
- package/package.json +3 -3
|
@@ -19,20 +19,10 @@ export interface EntityDataOptions {
|
|
|
19
19
|
*/
|
|
20
20
|
resolveCollection?: (slug: string) => {
|
|
21
21
|
properties?: Record<string, unknown>;
|
|
22
|
+
relations?: unknown[];
|
|
23
|
+
slug?: string;
|
|
22
24
|
} | undefined;
|
|
23
25
|
}
|
|
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
|
-
*/
|
|
36
26
|
export declare function buildRebaseData(driver: DataDriver, options?: EntityDataOptions): RebaseData;
|
|
37
27
|
/**
|
|
38
28
|
* Wrap a flat {@link RebaseSdkData} into a Entity-shaped {@link RebaseData}.
|
package/dist/index.es.js
CHANGED
|
@@ -5825,6 +5825,89 @@ function createPrimaryKeyResolver(options) {
|
|
|
5825
5825
|
};
|
|
5826
5826
|
}
|
|
5827
5827
|
/**
|
|
5828
|
+
* Build the admin's view model out of the row the wire serves.
|
|
5829
|
+
*
|
|
5830
|
+
* The wire has ONE shape, for every consumer: flat columns, typed the way the
|
|
5831
|
+
* database typed them, and a relation rendered as the target's own columns (or
|
|
5832
|
+
* only its foreign key, when nothing asked for it). That is the REST contract,
|
|
5833
|
+
* what `find()` returns, what `listen()` pushes, and what the generated types
|
|
5834
|
+
* describe.
|
|
5835
|
+
*
|
|
5836
|
+
* The admin renders neither of those directly. Its date field requires a real
|
|
5837
|
+
* `Date` and rejects a string outright; its relation cells read `.data.values`
|
|
5838
|
+
* off a relation ref. Those requirements are the *admin's*, so they are met
|
|
5839
|
+
* here — in the browser, from the collection config the panel already has —
|
|
5840
|
+
* rather than by asking the server for a second wire shape.
|
|
5841
|
+
*
|
|
5842
|
+
* That second shape is what this replaces. Until 2026-09-09 the realtime wire
|
|
5843
|
+
* carried the view model and every other read carried flat rows, so `find()`
|
|
5844
|
+
* and `listen()` answered one query two ways; unifying the wire without doing
|
|
5845
|
+
* this conversion is what left every date cell reading "Invalid date value"
|
|
5846
|
+
* and every relation cell "Unexpected value".
|
|
5847
|
+
*
|
|
5848
|
+
* Values already in view-model form pass through untouched: a driver that
|
|
5849
|
+
* still sends `{ __type: "date" }` or a relation ref (the client revives both)
|
|
5850
|
+
* is served by the same walk.
|
|
5851
|
+
*/
|
|
5852
|
+
function toViewModelValues(values, properties, collection, resolveCollection) {
|
|
5853
|
+
if (!properties) return values;
|
|
5854
|
+
const relations = collection ? resolveCollectionRelations(collection) : {};
|
|
5855
|
+
let out;
|
|
5856
|
+
const write = (key, value) => {
|
|
5857
|
+
out = out ?? { ...values };
|
|
5858
|
+
out[key] = value;
|
|
5859
|
+
};
|
|
5860
|
+
for (const [key, rawProperty] of Object.entries(properties)) {
|
|
5861
|
+
const property = rawProperty;
|
|
5862
|
+
if (!property) continue;
|
|
5863
|
+
if (!(key in values)) {
|
|
5864
|
+
const fkRelation = relations[key];
|
|
5865
|
+
const column = fkRelation && "localKey" in fkRelation ? fkRelation.localKey : void 0;
|
|
5866
|
+
const fk = column !== void 0 ? values[column] ?? values[toWireKey(column)] : void 0;
|
|
5867
|
+
const fkTarget = fkRelation?.targetSlug;
|
|
5868
|
+
if (fkTarget && (typeof fk === "string" || typeof fk === "number")) write(key, new EntityRelation(fk, fkTarget));
|
|
5869
|
+
continue;
|
|
5870
|
+
}
|
|
5871
|
+
const value = values[key];
|
|
5872
|
+
if (value === null || value === void 0) continue;
|
|
5873
|
+
const relation = relations[key];
|
|
5874
|
+
if (relation && (property.type === "relation" || property.of?.type === "relation" || property.type === "array")) {
|
|
5875
|
+
const target = relation.targetSlug;
|
|
5876
|
+
if (!target) continue;
|
|
5877
|
+
const targetProperties = resolveCollection?.(target)?.properties;
|
|
5878
|
+
const targetCollection = resolveCollection?.(target);
|
|
5879
|
+
const toRef = (item) => {
|
|
5880
|
+
if (item instanceof EntityRelation) return item;
|
|
5881
|
+
if (typeof item === "object" && item !== null && "__type" in item) return item;
|
|
5882
|
+
if (typeof item === "object" && item !== null) {
|
|
5883
|
+
const row = item;
|
|
5884
|
+
const keys = targetCollection ? resolvePrimaryKeys(targetCollection) : [];
|
|
5885
|
+
const id = keys.length > 0 ? buildCompositeId(row, keys) : row.id;
|
|
5886
|
+
if (id === void 0 || id === null || id === "") return item;
|
|
5887
|
+
return new EntityRelation(id, target, {
|
|
5888
|
+
id,
|
|
5889
|
+
path: target,
|
|
5890
|
+
values: toViewModelValues(row, targetProperties, targetCollection, resolveCollection)
|
|
5891
|
+
});
|
|
5892
|
+
}
|
|
5893
|
+
if (typeof item === "string" || typeof item === "number") return new EntityRelation(item, target);
|
|
5894
|
+
return item;
|
|
5895
|
+
};
|
|
5896
|
+
write(key, Array.isArray(value) ? value.map(toRef) : toRef(value));
|
|
5897
|
+
continue;
|
|
5898
|
+
}
|
|
5899
|
+
if (property.type === "date" && !(value instanceof Date)) {
|
|
5900
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
5901
|
+
const date = new Date(value);
|
|
5902
|
+
write(key, isNaN(date.getTime()) ? null : date);
|
|
5903
|
+
}
|
|
5904
|
+
continue;
|
|
5905
|
+
}
|
|
5906
|
+
if (property.type === "map" && property.properties && typeof value === "object" && !Array.isArray(value)) write(key, toViewModelValues(value, property.properties, void 0, resolveCollection));
|
|
5907
|
+
}
|
|
5908
|
+
return out ?? values;
|
|
5909
|
+
}
|
|
5910
|
+
/**
|
|
5828
5911
|
* Give a flat row the Entity view-model the admin renders.
|
|
5829
5912
|
*
|
|
5830
5913
|
* The address is *derived here* — it is not a column, and the row it came from
|
|
@@ -5835,12 +5918,12 @@ function createPrimaryKeyResolver(options) {
|
|
|
5835
5918
|
* `primaryKeys` empty falls back to a literal `id` on the row: drivers other
|
|
5836
5919
|
* than postgres still serve rows with one, and this keeps them working.
|
|
5837
5920
|
*/
|
|
5838
|
-
function rowToEntity(row, slug, primaryKeys = []) {
|
|
5921
|
+
function rowToEntity(row, slug, primaryKeys = [], toViewModel) {
|
|
5839
5922
|
const { _matches, ...values } = row;
|
|
5840
5923
|
return {
|
|
5841
5924
|
id: primaryKeys.length > 0 ? buildCompositeId(row, primaryKeys) : row.id,
|
|
5842
5925
|
path: slug,
|
|
5843
|
-
values,
|
|
5926
|
+
values: toViewModel ? toViewModel(values) : values,
|
|
5844
5927
|
..._matches ? { searchMatches: _matches } : {}
|
|
5845
5928
|
};
|
|
5846
5929
|
}
|
|
@@ -5860,14 +5943,16 @@ function inlineEnvelope(envelope) {
|
|
|
5860
5943
|
* Replace every relation envelope on a row with the target's flat columns.
|
|
5861
5944
|
*
|
|
5862
5945
|
* The SDK serves one relation shape — the inlined one (see
|
|
5863
|
-
* {@link RestFetchService}) — and
|
|
5864
|
-
*
|
|
5865
|
-
*
|
|
5866
|
-
*
|
|
5946
|
+
* {@link RestFetchService}) — and Postgres now serves it on every read, so
|
|
5947
|
+
* against that driver this walk finds nothing to do. It stays for the drivers
|
|
5948
|
+
* whose own `fetchCollection` still answers with refs: a developer reading
|
|
5949
|
+
* through this accessor gets one shape whichever driver is underneath.
|
|
5867
5950
|
*
|
|
5868
5951
|
* Only applied where the REST pipeline is the contract (see `find`); a driver
|
|
5869
|
-
* without a `restFetchService` keeps whatever it returns
|
|
5870
|
-
*
|
|
5952
|
+
* without a `restFetchService` keeps whatever it returns.
|
|
5953
|
+
*
|
|
5954
|
+
* Note this is NOT how the admin gets its view model — that is built in the
|
|
5955
|
+
* browser by {@link toViewModelValues}, from the same flat row.
|
|
5871
5956
|
*/
|
|
5872
5957
|
function inlineRelationRefs(row) {
|
|
5873
5958
|
let out;
|
|
@@ -5880,7 +5965,7 @@ function inlineRelationRefs(row) {
|
|
|
5880
5965
|
}
|
|
5881
5966
|
return out ?? row;
|
|
5882
5967
|
}
|
|
5883
|
-
function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
5968
|
+
function createDriverAccessor(driver, slug, getPks = () => [], toViewModel) {
|
|
5884
5969
|
const accessor = {
|
|
5885
5970
|
async find(params) {
|
|
5886
5971
|
const filter = params?.where ? deserializeFilter(params.where) : void 0;
|
|
@@ -5929,7 +6014,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
5929
6014
|
const last = rows[rows.length - 1];
|
|
5930
6015
|
const nextCursor = hasMore && last && driver.restFetchService?.cursorFor ? driver.restFetchService.cursorFor(slug, last, orderBy) : void 0;
|
|
5931
6016
|
return {
|
|
5932
|
-
data: rows.map((row) => rowToEntity(row, slug, getPks())),
|
|
6017
|
+
data: rows.map((row) => rowToEntity(row, slug, getPks(), toViewModel)),
|
|
5933
6018
|
meta: {
|
|
5934
6019
|
total,
|
|
5935
6020
|
limit,
|
|
@@ -5945,7 +6030,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
5945
6030
|
path: slug,
|
|
5946
6031
|
id
|
|
5947
6032
|
});
|
|
5948
|
-
return row ? rowToEntity(row, slug, getPks()) : void 0;
|
|
6033
|
+
return row ? rowToEntity(row, slug, getPks(), toViewModel) : void 0;
|
|
5949
6034
|
},
|
|
5950
6035
|
aggregate: driver.restFetchService?.aggregate ? async (params) => driver.restFetchService.aggregate(slug, {
|
|
5951
6036
|
aggregates: params.select.map(toDriverAggregate),
|
|
@@ -5961,7 +6046,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
5961
6046
|
values: data,
|
|
5962
6047
|
id,
|
|
5963
6048
|
status: "new"
|
|
5964
|
-
}), slug, getPks());
|
|
6049
|
+
}), slug, getPks(), toViewModel);
|
|
5965
6050
|
},
|
|
5966
6051
|
createMany: driver.saveMany ? async (data, options) => {
|
|
5967
6052
|
return (await driver.saveMany({
|
|
@@ -5969,7 +6054,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
5969
6054
|
rows: data,
|
|
5970
6055
|
upsert: options?.upsert,
|
|
5971
6056
|
onConflict: options?.onConflict
|
|
5972
|
-
})).map((row) => rowToEntity(row, slug, getPks()));
|
|
6057
|
+
})).map((row) => rowToEntity(row, slug, getPks(), toViewModel));
|
|
5973
6058
|
} : void 0,
|
|
5974
6059
|
async update(id, data) {
|
|
5975
6060
|
return rowToEntity(await driver.save({
|
|
@@ -5977,7 +6062,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
5977
6062
|
values: data,
|
|
5978
6063
|
id,
|
|
5979
6064
|
status: "existing"
|
|
5980
|
-
}), slug, getPks());
|
|
6065
|
+
}), slug, getPks(), toViewModel);
|
|
5981
6066
|
},
|
|
5982
6067
|
async delete(id) {
|
|
5983
6068
|
return driver.delete({ row: {
|
|
@@ -5993,7 +6078,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
5993
6078
|
id: u.id,
|
|
5994
6079
|
values: u.data
|
|
5995
6080
|
}))
|
|
5996
|
-
})).map((row) => rowToEntity(row, slug, getPks()));
|
|
6081
|
+
})).map((row) => rowToEntity(row, slug, getPks(), toViewModel));
|
|
5997
6082
|
} : void 0,
|
|
5998
6083
|
deleteMany: driver.deleteMany ? async (ids) => {
|
|
5999
6084
|
await driver.deleteMany({
|
|
@@ -6025,7 +6110,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
6025
6110
|
vectorSearch: params?.vectorSearch,
|
|
6026
6111
|
onUpdate: (entities) => {
|
|
6027
6112
|
onUpdate({
|
|
6028
|
-
data: entities.map((row) => rowToEntity(normalize(row), slug, getPks())),
|
|
6113
|
+
data: entities.map((row) => rowToEntity(normalize(row), slug, getPks(), toViewModel)),
|
|
6029
6114
|
meta: {
|
|
6030
6115
|
total: offset + entities.length,
|
|
6031
6116
|
limit,
|
|
@@ -6042,7 +6127,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
6042
6127
|
return driver.listenOne({
|
|
6043
6128
|
path: slug,
|
|
6044
6129
|
id,
|
|
6045
|
-
onUpdate: (entity) => onUpdate(entity ? rowToEntity(normalize(entity), slug, getPks()) : void 0),
|
|
6130
|
+
onUpdate: (entity) => onUpdate(entity ? rowToEntity(normalize(entity), slug, getPks(), toViewModel) : void 0),
|
|
6046
6131
|
onError
|
|
6047
6132
|
});
|
|
6048
6133
|
} : void 0,
|
|
@@ -6084,13 +6169,32 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
6084
6169
|
* await data.products.create({ name: "Camera", price: 299 });
|
|
6085
6170
|
* const { data: items } = await data.products.find({ where: { status: ["==", "published"] } });
|
|
6086
6171
|
*/
|
|
6172
|
+
/**
|
|
6173
|
+
* The view-model converter for one collection, or `undefined` when there is no
|
|
6174
|
+
* collection config to build it from.
|
|
6175
|
+
*
|
|
6176
|
+
* Absent is the honest answer for every consumer that is not the admin: the
|
|
6177
|
+
* flat SDK derives itself from this same layer (`buildSdkData`) and must keep
|
|
6178
|
+
* the wire's own types, and it registers no collection resolver.
|
|
6179
|
+
*/
|
|
6180
|
+
function createViewModelConverter(options) {
|
|
6181
|
+
if (!options?.resolveCollection) return () => void 0;
|
|
6182
|
+
return function converterFor(slug) {
|
|
6183
|
+
return (values) => {
|
|
6184
|
+
const collection = options.resolveCollection?.(slug);
|
|
6185
|
+
if (!collection) return values;
|
|
6186
|
+
return toViewModelValues(values, collection.properties, collection, options.resolveCollection);
|
|
6187
|
+
};
|
|
6188
|
+
};
|
|
6189
|
+
}
|
|
6087
6190
|
function buildRebaseData(driver, options) {
|
|
6088
6191
|
const cache = /* @__PURE__ */ new Map();
|
|
6089
6192
|
const primaryKeysFor = createPrimaryKeyResolver(options);
|
|
6193
|
+
const viewModelFor = createViewModelConverter(options);
|
|
6090
6194
|
function getAccessor(slug) {
|
|
6091
6195
|
let accessor = cache.get(slug);
|
|
6092
6196
|
if (!accessor) {
|
|
6093
|
-
accessor = createDriverAccessor(driver, slug, () => primaryKeysFor(slug));
|
|
6197
|
+
accessor = createDriverAccessor(driver, slug, () => primaryKeysFor(slug), viewModelFor(slug));
|
|
6094
6198
|
cache.set(slug, accessor);
|
|
6095
6199
|
}
|
|
6096
6200
|
return accessor;
|
|
@@ -6355,29 +6459,29 @@ function toSdkCollectionClient(snap, slug = "collection") {
|
|
|
6355
6459
|
* {@link CollectionAccessor}. Every returned row is re-wrapped into the
|
|
6356
6460
|
* `{ id, path, values }` view-model the admin panel renders.
|
|
6357
6461
|
*/
|
|
6358
|
-
function toEntityAccessor(sdk, slug, getPks = () => []) {
|
|
6462
|
+
function toEntityAccessor(sdk, slug, getPks = () => [], toViewModel) {
|
|
6359
6463
|
const accessor = {
|
|
6360
6464
|
async find(params) {
|
|
6361
6465
|
const res = await sdk.find(params);
|
|
6362
6466
|
return {
|
|
6363
|
-
data: res.data.map((row) => rowToEntity(row, slug, getPks())),
|
|
6467
|
+
data: res.data.map((row) => rowToEntity(row, slug, getPks(), toViewModel)),
|
|
6364
6468
|
meta: res.meta
|
|
6365
6469
|
};
|
|
6366
6470
|
},
|
|
6367
6471
|
async findById(id) {
|
|
6368
6472
|
const row = await sdk.findById(id);
|
|
6369
|
-
return row ? rowToEntity(row, slug, getPks()) : void 0;
|
|
6473
|
+
return row ? rowToEntity(row, slug, getPks(), toViewModel) : void 0;
|
|
6370
6474
|
},
|
|
6371
6475
|
async create(data, id) {
|
|
6372
|
-
return rowToEntity(await sdk.create(data, id), slug, getPks());
|
|
6476
|
+
return rowToEntity(await sdk.create(data, id), slug, getPks(), toViewModel);
|
|
6373
6477
|
},
|
|
6374
6478
|
createMany: sdk.createMany ? async (data, options) => {
|
|
6375
|
-
return (await sdk.createMany(data, options)).map((row) => rowToEntity(row, slug, getPks()));
|
|
6479
|
+
return (await sdk.createMany(data, options)).map((row) => rowToEntity(row, slug, getPks(), toViewModel));
|
|
6376
6480
|
} : void 0,
|
|
6377
6481
|
async update(id, data) {
|
|
6378
6482
|
const row = await sdk.update(id, data);
|
|
6379
6483
|
if (!row) throw new Error(`Update returned no data for id ${id}`);
|
|
6380
|
-
return rowToEntity(row, slug, getPks());
|
|
6484
|
+
return rowToEntity(row, slug, getPks(), toViewModel);
|
|
6381
6485
|
},
|
|
6382
6486
|
delete(id) {
|
|
6383
6487
|
return sdk.delete(id);
|
|
@@ -6385,10 +6489,10 @@ function toEntityAccessor(sdk, slug, getPks = () => []) {
|
|
|
6385
6489
|
count: isUnsupported(sdk.count) ? void 0 : (params) => sdk.count(params),
|
|
6386
6490
|
aggregate: isUnsupported(sdk.aggregate) ? void 0 : (params) => sdk.aggregate(params),
|
|
6387
6491
|
listen: isUnsupported(sdk.listen) ? void 0 : (params, onUpdate, onError) => sdk.listen(params, (res) => onUpdate({
|
|
6388
|
-
data: res.data.map((row) => rowToEntity(row, slug, getPks())),
|
|
6492
|
+
data: res.data.map((row) => rowToEntity(row, slug, getPks(), toViewModel)),
|
|
6389
6493
|
meta: res.meta
|
|
6390
6494
|
}), onError),
|
|
6391
|
-
listenById: isUnsupported(sdk.listenById) ? void 0 : (id, onUpdate, onError) => sdk.listenById(id, (row) => onUpdate(row ? rowToEntity(row, slug, getPks()) : void 0), onError),
|
|
6495
|
+
listenById: isUnsupported(sdk.listenById) ? void 0 : (id, onUpdate, onError) => sdk.listenById(id, (row) => onUpdate(row ? rowToEntity(row, slug, getPks(), toViewModel) : void 0), onError),
|
|
6392
6496
|
where(columnOrCondition, operator, value) {
|
|
6393
6497
|
const builder = new QueryBuilder(accessor);
|
|
6394
6498
|
if (typeof columnOrCondition === "object") return builder.where(columnOrCondition);
|
|
@@ -6424,10 +6528,11 @@ function toEntityAccessor(sdk, slug, getPks = () => []) {
|
|
|
6424
6528
|
function wrapAsEntityData(sdkData, options) {
|
|
6425
6529
|
const cache = /* @__PURE__ */ new Map();
|
|
6426
6530
|
const primaryKeysFor = createPrimaryKeyResolver(options);
|
|
6531
|
+
const viewModelFor = createViewModelConverter(options);
|
|
6427
6532
|
function getAccessor(slug) {
|
|
6428
6533
|
let accessor = cache.get(slug);
|
|
6429
6534
|
if (!accessor) {
|
|
6430
|
-
accessor = toEntityAccessor(sdkData.collection(slug), slug, () => primaryKeysFor(slug));
|
|
6535
|
+
accessor = toEntityAccessor(sdkData.collection(slug), slug, () => primaryKeysFor(slug), viewModelFor(slug));
|
|
6431
6536
|
cache.set(slug, accessor);
|
|
6432
6537
|
}
|
|
6433
6538
|
return accessor;
|