joist-core 2.3.0-next.31 → 2.3.0-next.32
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/build/EntityManager.d.ts +31 -0
- package/build/EntityManager.js +93 -22
- package/build/EntityManager.js.map +1 -1
- package/build/InstanceData.d.ts +17 -2
- package/build/InstanceData.js +19 -3
- package/build/InstanceData.js.map +1 -1
- package/build/PluginManager.d.ts +13 -4
- package/build/PluginManager.js +4 -0
- package/build/PluginManager.js.map +1 -1
- package/build/RowData.d.ts +49 -0
- package/build/RowData.js +37 -0
- package/build/RowData.js.map +1 -0
- package/build/batchloaders/loadBatchLoader.js +19 -13
- package/build/batchloaders/loadBatchLoader.js.map +1 -1
- package/build/batchloaders/oneToManyBatchLoader.js +2 -2
- package/build/batchloaders/oneToManyBatchLoader.js.map +1 -1
- package/build/batchloaders/oneToOneBatchLoader.js +2 -2
- package/build/batchloaders/oneToOneBatchLoader.js.map +1 -1
- package/build/batchloaders/recursiveChildrenBatchLoader.js +2 -2
- package/build/batchloaders/recursiveChildrenBatchLoader.js.map +1 -1
- package/build/batchloaders/recursiveParentsBatchLoader.js +2 -2
- package/build/batchloaders/recursiveParentsBatchLoader.js.map +1 -1
- package/build/dataloaders/findDataLoader.js +17 -14
- package/build/dataloaders/findDataLoader.js.map +1 -1
- package/build/drivers/Driver.d.ts +12 -0
- package/build/fields.js +3 -2
- package/build/fields.js.map +1 -1
- package/build/index.d.ts +3 -2
- package/build/index.js +5 -2
- package/build/index.js.map +1 -1
- package/build/plugins/PreloadPlugin.d.ts +10 -5
- package/build/preloading/JsonAggregatePreloader.js +10 -6
- package/build/preloading/JsonAggregatePreloader.js.map +1 -1
- package/build/relations/LazyField.js +6 -4
- package/build/relations/LazyField.js.map +1 -1
- package/build/serde.d.ts +21 -16
- package/build/serde.js +27 -26
- package/build/serde.js.map +1 -1
- package/build/temporalMappers.d.ts +11 -3
- package/build/temporalMappers.js +22 -11
- package/build/temporalMappers.js.map +1 -1
- package/package.json +2 -2
package/build/EntityManager.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { PreloadPlugin } from "./plugins/PreloadPlugin";
|
|
|
29
29
|
import { ReactionsManager } from "./ReactionsManager";
|
|
30
30
|
import { Collection } from "./relations/Collection";
|
|
31
31
|
import { lazyColumnLoadOperation } from "./relations/LazyField";
|
|
32
|
+
import { RowData } from "./RowData";
|
|
32
33
|
import { OptsOf, OrderOf } from "./typeMap";
|
|
33
34
|
import { MaybePromise } from "./utils";
|
|
34
35
|
/**
|
|
@@ -167,8 +168,16 @@ export declare class EntityManager<C = unknown, Entity extends EntityW = EntityW
|
|
|
167
168
|
}): Promise<Loaded<T, H>[]>;
|
|
168
169
|
/** Runs the post-parse find pipeline: plugins mutate the logical AST, then Joist optimizes/prunes before SQL. */
|
|
169
170
|
private executeFind;
|
|
171
|
+
/** Like {@link executeFind}, but returns a {@link RowData}; see {@link executePreparedFindRowData}. */
|
|
172
|
+
private executeFindRowData;
|
|
170
173
|
/** Executes a query that has already had find hooks and optimizations applied. */
|
|
171
174
|
private executePreparedFind;
|
|
175
|
+
/**
|
|
176
|
+
* Like {@link executePreparedFind}, but returns a {@link RowData}: lazy wire rows when the
|
|
177
|
+
* driver has `lazyRows` enabled, else classic POJO rows wrapped in a `PojoRowData` — so
|
|
178
|
+
* loaders can hydrate from one shape without caring which the driver provides.
|
|
179
|
+
*/
|
|
180
|
+
private executePreparedFindRowData;
|
|
172
181
|
/**
|
|
173
182
|
* Runs pre-SQL find hooks and optimizations against a parsed query.
|
|
174
183
|
*
|
|
@@ -547,6 +556,28 @@ export declare class EntityManager<C = unknown, Entity extends EntityW = EntityW
|
|
|
547
556
|
hydrate<T extends EntityW>(type: MaybeAbstractEntityConstructor<T>, rows: readonly any[], options?: {
|
|
548
557
|
overwriteExisting?: boolean;
|
|
549
558
|
}): T[];
|
|
559
|
+
/**
|
|
560
|
+
* Hydrates a query's `RowData`, running any sidecar reads, and always `finalize`s the result.
|
|
561
|
+
*
|
|
562
|
+
* This is the internal API for Joist's find/load loaders: `sidecars` is where reads of
|
|
563
|
+
* non-entity columns (preload aggregates, `_tags`, found-id checks) belong — they must run
|
|
564
|
+
* before `finalize`, because compaction drops unretained rows — and the `try/finally`
|
|
565
|
+
* guarantees a failed hydration or sidecar read still releases the result's unretained
|
|
566
|
+
* buffers instead of pinning them.
|
|
567
|
+
*/
|
|
568
|
+
private hydrateAndFinalize;
|
|
569
|
+
/**
|
|
570
|
+
* Like {@link hydrate}, but reads rows from a {@link RowData}, i.e. a driver-produced
|
|
571
|
+
* lazy query result, instead of an array of POJO rows.
|
|
572
|
+
*
|
|
573
|
+
* This is an internal API used by Joist's own find loaders (usually via
|
|
574
|
+
* {@link hydrateAndFinalize}); rows whose entities are kept (newly created or
|
|
575
|
+
* overwrite-refreshed) are `retain`-ed on the `RowData` so the caller can later `finalize`
|
|
576
|
+
* (trim/compact) the result.
|
|
577
|
+
*/
|
|
578
|
+
hydrateFromRowData<T extends EntityW>(type: MaybeAbstractEntityConstructor<T>, rowData: RowData, options?: {
|
|
579
|
+
overwriteExisting?: boolean;
|
|
580
|
+
}): T[];
|
|
550
581
|
/**
|
|
551
582
|
* Mark an entity as needing to be flushed regardless of its state.
|
|
552
583
|
*
|
package/build/EntityManager.js
CHANGED
|
@@ -54,6 +54,7 @@ const AsyncProperty_1 = require("./relations/AsyncProperty");
|
|
|
54
54
|
const hasAsyncMethod_1 = require("./relations/hasAsyncMethod");
|
|
55
55
|
const LazyField_1 = require("./relations/LazyField");
|
|
56
56
|
const RecursiveCollection_1 = require("./relations/RecursiveCollection");
|
|
57
|
+
const RowData_1 = require("./RowData");
|
|
57
58
|
const scopes_1 = require("./scopes");
|
|
58
59
|
const Todo_1 = require("./Todo");
|
|
59
60
|
const trusted_1 = require("./trusted");
|
|
@@ -296,6 +297,11 @@ class EntityManager {
|
|
|
296
297
|
const { checkLimit, findSettings } = this.prepareFind(meta, operation, parsed, settings);
|
|
297
298
|
return this.executePreparedFind(meta, operation, parsed, findSettings, checkLimit);
|
|
298
299
|
}
|
|
300
|
+
/** Like {@link executeFind}, but returns a {@link RowData}; see {@link executePreparedFindRowData}. */
|
|
301
|
+
async executeFindRowData(meta, operation, parsed, settings) {
|
|
302
|
+
const { checkLimit, findSettings } = this.prepareFind(meta, operation, parsed, settings);
|
|
303
|
+
return this.executePreparedFindRowData(meta, operation, parsed, findSettings, checkLimit);
|
|
304
|
+
}
|
|
299
305
|
/** Executes a query that has already had find hooks and optimizations applied. */
|
|
300
306
|
async executePreparedFind(meta, operation, parsed, findSettings, checkLimit) {
|
|
301
307
|
const { pluginManager } = getEmInternalApi(this);
|
|
@@ -305,9 +311,33 @@ class EntityManager {
|
|
|
305
311
|
if (shouldCheck && rows.length >= this.entityLimit) {
|
|
306
312
|
throw new Error(`Query returned more than ${this.entityLimit} entityLimit rows`);
|
|
307
313
|
}
|
|
308
|
-
pluginManager.afterFind
|
|
314
|
+
if (pluginManager.hasHook("afterFind")) {
|
|
315
|
+
pluginManager.afterFind(meta, operation, new RowData_1.PojoRowData(rows));
|
|
316
|
+
}
|
|
309
317
|
return rows;
|
|
310
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Like {@link executePreparedFind}, but returns a {@link RowData}: lazy wire rows when the
|
|
321
|
+
* driver has `lazyRows` enabled, else classic POJO rows wrapped in a `PojoRowData` — so
|
|
322
|
+
* loaders can hydrate from one shape without caring which the driver provides.
|
|
323
|
+
*/
|
|
324
|
+
async executePreparedFindRowData(meta, operation, parsed, findSettings, checkLimit) {
|
|
325
|
+
const { executeFindRowData } = this.driver;
|
|
326
|
+
if (executeFindRowData === undefined) {
|
|
327
|
+
return new RowData_1.PojoRowData(await this.executePreparedFind(meta, operation, parsed, findSettings, checkLimit));
|
|
328
|
+
}
|
|
329
|
+
const { pluginManager } = getEmInternalApi(this);
|
|
330
|
+
const rowData = await executeFindRowData.call(this.driver, this, parsed, findSettings);
|
|
331
|
+
// Check by default unless explicitly disabled or the caller removed the LIMIT via `limit: undefined`
|
|
332
|
+
const shouldCheck = checkLimit ?? !("limit" in findSettings && findSettings.limit === undefined);
|
|
333
|
+
if (shouldCheck && rowData.rowCount >= this.entityLimit) {
|
|
334
|
+
throw new Error(`Query returned more than ${this.entityLimit} entityLimit rows`);
|
|
335
|
+
}
|
|
336
|
+
// The hook receives the lazy RowData view directly, so observe-only hooks (i.e. metrics
|
|
337
|
+
// reading `rowCount`) never materialize cells; hooks call `toRows()` if they want POJOs
|
|
338
|
+
pluginManager.afterFind(meta, operation, rowData);
|
|
339
|
+
return rowData;
|
|
340
|
+
}
|
|
311
341
|
/**
|
|
312
342
|
* Runs pre-SQL find hooks and optimizations against a parsed query.
|
|
313
343
|
*
|
|
@@ -1415,22 +1445,55 @@ class EntityManager {
|
|
|
1415
1445
|
* the WIP entity state.
|
|
1416
1446
|
*/
|
|
1417
1447
|
hydrate(type, rows, options) {
|
|
1448
|
+
return this.hydrateFromRowData(type, new RowData_1.PojoRowData(rows), options);
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Hydrates a query's `RowData`, running any sidecar reads, and always `finalize`s the result.
|
|
1452
|
+
*
|
|
1453
|
+
* This is the internal API for Joist's find/load loaders: `sidecars` is where reads of
|
|
1454
|
+
* non-entity columns (preload aggregates, `_tags`, found-id checks) belong — they must run
|
|
1455
|
+
* before `finalize`, because compaction drops unretained rows — and the `try/finally`
|
|
1456
|
+
* guarantees a failed hydration or sidecar read still releases the result's unretained
|
|
1457
|
+
* buffers instead of pinning them.
|
|
1458
|
+
*/
|
|
1459
|
+
hydrateAndFinalize(type, rowData, opts) {
|
|
1460
|
+
try {
|
|
1461
|
+
const entities = this.hydrateFromRowData(type, rowData, opts);
|
|
1462
|
+
opts?.sidecars?.(entities);
|
|
1463
|
+
return entities;
|
|
1464
|
+
}
|
|
1465
|
+
finally {
|
|
1466
|
+
rowData.finalize?.();
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Like {@link hydrate}, but reads rows from a {@link RowData}, i.e. a driver-produced
|
|
1471
|
+
* lazy query result, instead of an array of POJO rows.
|
|
1472
|
+
*
|
|
1473
|
+
* This is an internal API used by Joist's own find loaders (usually via
|
|
1474
|
+
* {@link hydrateAndFinalize}); rows whose entities are kept (newly created or
|
|
1475
|
+
* overwrite-refreshed) are `retain`-ed on the `RowData` so the caller can later `finalize`
|
|
1476
|
+
* (trim/compact) the result.
|
|
1477
|
+
*/
|
|
1478
|
+
hydrateFromRowData(type, rowData, options) {
|
|
1418
1479
|
const maybeBaseMeta = (0, index_1.getMetadata)(type);
|
|
1419
1480
|
const overwriteExisting = options?.overwriteExisting === true;
|
|
1420
|
-
|
|
1421
|
-
const entities = new Array(
|
|
1422
|
-
for (
|
|
1423
|
-
const id =
|
|
1481
|
+
const count = rowData.rowCount;
|
|
1482
|
+
const entities = new Array(count);
|
|
1483
|
+
for (let i = 0; i < count; i++) {
|
|
1484
|
+
const id = rowData.get(i, "id");
|
|
1424
1485
|
const taggedId = id === undefined || id === null ? (0, utils_1.fail)("No id column was available") : (0, index_1.keyToTaggedId)(maybeBaseMeta, id);
|
|
1425
1486
|
// See if this is already in our UoW
|
|
1426
1487
|
let entity = this.findExistingInstance(taggedId);
|
|
1427
1488
|
if (!entity) {
|
|
1428
1489
|
// Look for __class from the driver telling us which subtype to instantiate
|
|
1429
|
-
const meta = findConcreteMeta(maybeBaseMeta,
|
|
1490
|
+
const meta = findConcreteMeta(maybeBaseMeta, rowData, i);
|
|
1430
1491
|
// Pass id as a hint that we're in hydrate mode
|
|
1431
1492
|
entity = (0, newEntity_1.newEntity)(this, (0, index_1.asConcreteCstr)(meta.cstr), false);
|
|
1432
1493
|
const instanceData = (0, BaseEntity_1.getInstanceData)(entity);
|
|
1433
|
-
instanceData.
|
|
1494
|
+
instanceData.rowData = rowData;
|
|
1495
|
+
instanceData.rowIndex = i;
|
|
1496
|
+
rowData.retain?.(i);
|
|
1434
1497
|
// Seed the id to share the identity-map key string and skip the id serde on first read
|
|
1435
1498
|
instanceData.data["id"] = taggedId;
|
|
1436
1499
|
this.#doRegister(entity, taggedId, meta, true);
|
|
@@ -1440,7 +1503,9 @@ class EntityManager {
|
|
|
1440
1503
|
// `EntityManager.refresh` is telling us to explicitly load the latest data.
|
|
1441
1504
|
// First swap out the old row with the new row
|
|
1442
1505
|
const instanceData = (0, BaseEntity_1.getInstanceData)(entity);
|
|
1443
|
-
instanceData.
|
|
1506
|
+
instanceData.rowData = rowData;
|
|
1507
|
+
instanceData.rowIndex = i;
|
|
1508
|
+
rowData.retain?.(i);
|
|
1444
1509
|
// And then only refresh the data keys that have already been serde-d from rows
|
|
1445
1510
|
// (this keeps us from deserializing data out of rows that we don't need).
|
|
1446
1511
|
const { data } = instanceData;
|
|
@@ -1457,7 +1522,7 @@ class EntityManager {
|
|
|
1457
1522
|
if (fieldName === "id")
|
|
1458
1523
|
continue;
|
|
1459
1524
|
const serde = allFields[fieldName].serde ?? (0, utils_1.fail)(`Missing serde for ${fieldName}`);
|
|
1460
|
-
serde.
|
|
1525
|
+
serde.setOnEntityFromRowData(data, rowData, i);
|
|
1461
1526
|
}
|
|
1462
1527
|
}
|
|
1463
1528
|
else {
|
|
@@ -1465,7 +1530,7 @@ class EntityManager {
|
|
|
1465
1530
|
if (fieldName === "id")
|
|
1466
1531
|
continue;
|
|
1467
1532
|
const serde = allFields[fieldName].serde ?? (0, utils_1.fail)(`Missing serde for ${fieldName}`);
|
|
1468
|
-
serde.
|
|
1533
|
+
serde.setOnEntityFromRowData(data, rowData, i);
|
|
1469
1534
|
// Make the field look not-dirty
|
|
1470
1535
|
if (changedFields.includes(fieldName)) {
|
|
1471
1536
|
instanceData.markFieldClean(fieldName);
|
|
@@ -1474,7 +1539,7 @@ class EntityManager {
|
|
|
1474
1539
|
}
|
|
1475
1540
|
}
|
|
1476
1541
|
}
|
|
1477
|
-
entities[i
|
|
1542
|
+
entities[i] = entity;
|
|
1478
1543
|
}
|
|
1479
1544
|
return entities;
|
|
1480
1545
|
}
|
|
@@ -2357,22 +2422,28 @@ function getNow() {
|
|
|
2357
2422
|
return now;
|
|
2358
2423
|
}
|
|
2359
2424
|
/** Given a `row` from the db, resolves the CTI/STI subtype, if applicable. */
|
|
2360
|
-
function findConcreteMeta(maybeBaseMeta,
|
|
2361
|
-
// Common case of no CTI or STI inheritance
|
|
2362
|
-
|
|
2425
|
+
function findConcreteMeta(maybeBaseMeta, rowData, rowIndex) {
|
|
2426
|
+
// Common case of no CTI or STI inheritance; only probe __class for CTI metas so that
|
|
2427
|
+
// lazy/columnar stores don't pay a missing-column lookup per row for regular entities
|
|
2428
|
+
if (maybeBaseMeta.inheritanceType === undefined) {
|
|
2429
|
+
return maybeBaseMeta;
|
|
2430
|
+
}
|
|
2431
|
+
const __class = rowData.get(rowIndex, "__class");
|
|
2432
|
+
if (!__class && maybeBaseMeta.inheritanceType !== "sti") {
|
|
2363
2433
|
return maybeBaseMeta;
|
|
2364
2434
|
}
|
|
2365
|
-
if (
|
|
2366
|
-
if (
|
|
2367
|
-
|
|
2435
|
+
if (__class) {
|
|
2436
|
+
if (__class === "_" && maybeBaseMeta.ctiAbstract) {
|
|
2437
|
+
const id = rowData.get(rowIndex, "id");
|
|
2438
|
+
throw new Error(`${maybeBaseMeta.type} ${(0, index_1.tagId)(maybeBaseMeta, id)} must be instantiated via a subtype`);
|
|
2368
2439
|
}
|
|
2369
2440
|
// Look for the CTI __class from the driver telling us which subtype to instantiate
|
|
2370
|
-
return maybeBaseMeta.subTypesByType.get(
|
|
2441
|
+
return maybeBaseMeta.subTypesByType.get(__class) ?? maybeBaseMeta;
|
|
2371
2442
|
}
|
|
2372
2443
|
else if (maybeBaseMeta.inheritanceType === "sti") {
|
|
2373
2444
|
// Look for the STI discriminator value
|
|
2374
2445
|
const baseMeta = (0, index_1.getBaseMeta)(maybeBaseMeta);
|
|
2375
|
-
const value =
|
|
2446
|
+
const value = rowData.get(rowIndex, baseMeta.stiDiscriminatorColumnName);
|
|
2376
2447
|
return baseMeta.subTypesByStiValue.get(value) ?? baseMeta;
|
|
2377
2448
|
}
|
|
2378
2449
|
else {
|
|
@@ -2461,7 +2532,7 @@ const fieldMap = {};
|
|
|
2461
2532
|
// Generates what a row from the db would look like for a given entity
|
|
2462
2533
|
function createRowFromEntityData(e, opts = {}) {
|
|
2463
2534
|
const { preferOriginalData = true } = opts;
|
|
2464
|
-
const {
|
|
2535
|
+
const { rowData, rowIndex, data, originalData } = e.__data;
|
|
2465
2536
|
const __class = e.constructor.name;
|
|
2466
2537
|
const { metadata: meta } = e.__data;
|
|
2467
2538
|
if (!fieldMap[__class]) {
|
|
@@ -2486,8 +2557,8 @@ function createRowFromEntityData(e, opts = {}) {
|
|
|
2486
2557
|
// reflect what would come from the db if we queried it right now, so use originalData when present
|
|
2487
2558
|
column.rowValue(preferOriginalData && field.fieldName in originalData ? originalData : data)
|
|
2488
2559
|
: // `data` is lazy and isn't set until it's accessed, so if the field isn't present there, then we should
|
|
2489
|
-
// be safe to pull the raw data out of
|
|
2490
|
-
|
|
2560
|
+
// be safe to pull the raw data out of the as-loaded RowData
|
|
2561
|
+
rowData.get(rowIndex, column.columnName);
|
|
2491
2562
|
row[column.columnName] = value ?? null;
|
|
2492
2563
|
}
|
|
2493
2564
|
return row;
|