@ftschopp/dynatable-core 2.3.1 → 2.3.3
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/CHANGELOG.md +15 -0
- package/dist/builders/shared/operators.d.ts +7 -1
- package/dist/builders/shared/operators.d.ts.map +1 -1
- package/dist/builders/shared/operators.js +8 -2
- package/dist/builders/update/create-update-builder.d.ts.map +1 -1
- package/dist/builders/update/create-update-builder.js +26 -2
- package/dist/entity/create-entity-api.d.ts.map +1 -1
- package/dist/entity/create-entity-api.js +7 -15
- package/dist/entity/transforms/build-upsert-seed.d.ts +38 -0
- package/dist/entity/transforms/build-upsert-seed.d.ts.map +1 -0
- package/dist/entity/transforms/build-upsert-seed.js +67 -0
- package/dist/utils/model-utils.d.ts.map +1 -1
- package/dist/utils/model-utils.js +13 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## @ftschopp/dynatable-core [2.3.3](https://github.com/ftschopp/dynatable/compare/@ftschopp/dynatable-core@2.3.2...@ftschopp/dynatable-core@2.3.3) (2026-07-22)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **core:** clone array/object schema defaults so items don't share a reference ([#63](https://github.com/ftschopp/dynatable/issues/63)) ([dd62fe1](https://github.com/ftschopp/dynatable/commit/dd62fe1b90b0836f500ad4ade243bc0c2c300cb8))
|
|
7
|
+
* **core:** prevent placeholder collision between update .where() and .set() ([#61](https://github.com/ftschopp/dynatable/issues/61)) ([396eef3](https://github.com/ftschopp/dynatable/commit/396eef33b78ed966ffafb1f9669dfc789d93b40d))
|
|
8
|
+
|
|
9
|
+
## @ftschopp/dynatable-core [2.3.2](https://github.com/ftschopp/dynatable/compare/@ftschopp/dynatable-core@2.3.1...@ftschopp/dynatable-core@2.3.2) (2026-06-08)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* **core:** materialize primary-key template vars on update() upserts ([#60](https://github.com/ftschopp/dynatable/issues/60)) ([425ec39](https://github.com/ftschopp/dynatable/commit/425ec396b9cb58898575e0fa2ef9cd5d9317a1ce))
|
|
15
|
+
|
|
1
16
|
## @ftschopp/dynatable-core [2.3.1](https://github.com/ftschopp/dynatable/compare/@ftschopp/dynatable-core@2.3.0...@ftschopp/dynatable-core@2.3.1) (2026-06-05)
|
|
2
17
|
|
|
3
18
|
|
|
@@ -2,8 +2,14 @@ import { OpBuilder } from './types';
|
|
|
2
2
|
/**
|
|
3
3
|
* Creates a scoped OpBuilder with its own counter for unique value placeholders.
|
|
4
4
|
* Each builder instance maintains isolated state to prevent naming conflicts.
|
|
5
|
+
*
|
|
6
|
+
* @param startCounter - Initial value for the placeholder counter. Defaults to
|
|
7
|
+
* 0 for standalone use (query/scan/get, where conditions are the only source
|
|
8
|
+
* of value placeholders). The update builder passes its own `valueCounter`
|
|
9
|
+
* here so condition placeholders (`:status_0`) share one numbering space with
|
|
10
|
+
* the SET/ADD/DELETE action placeholders and can never collide with them.
|
|
5
11
|
*/
|
|
6
|
-
export declare function createOpBuilder(): OpBuilder;
|
|
12
|
+
export declare function createOpBuilder(startCounter?: number): OpBuilder;
|
|
7
13
|
/**
|
|
8
14
|
* @deprecated Use createOpBuilder() instead to avoid global state.
|
|
9
15
|
* This export is maintained for backward compatibility.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operators.d.ts","sourceRoot":"","sources":["../../../src/builders/shared/operators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC
|
|
1
|
+
{"version":3,"file":"operators.d.ts","sourceRoot":"","sources":["../../../src/builders/shared/operators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,YAAY,SAAI,GAAG,SAAS,CA6L3D;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,SAA6B,CAAC"}
|
|
@@ -5,9 +5,15 @@ exports.createOpBuilder = createOpBuilder;
|
|
|
5
5
|
/**
|
|
6
6
|
* Creates a scoped OpBuilder with its own counter for unique value placeholders.
|
|
7
7
|
* Each builder instance maintains isolated state to prevent naming conflicts.
|
|
8
|
+
*
|
|
9
|
+
* @param startCounter - Initial value for the placeholder counter. Defaults to
|
|
10
|
+
* 0 for standalone use (query/scan/get, where conditions are the only source
|
|
11
|
+
* of value placeholders). The update builder passes its own `valueCounter`
|
|
12
|
+
* here so condition placeholders (`:status_0`) share one numbering space with
|
|
13
|
+
* the SET/ADD/DELETE action placeholders and can never collide with them.
|
|
8
14
|
*/
|
|
9
|
-
function createOpBuilder() {
|
|
10
|
-
let valueCounter =
|
|
15
|
+
function createOpBuilder(startCounter = 0) {
|
|
16
|
+
let valueCounter = startCounter;
|
|
11
17
|
/**
|
|
12
18
|
* Generates a unique value placeholder name within this builder's scope
|
|
13
19
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-update-builder.d.ts","sourceRoot":"","sources":["../../../src/builders/update/create-update-builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAgC,SAAS,EAA4B,MAAM,WAAW,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"create-update-builder.d.ts","sourceRoot":"","sources":["../../../src/builders/update/create-update-builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAgC,SAAS,EAA4B,MAAM,WAAW,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAkF7D;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EACvC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,EACnB,MAAM,EAAE,cAAc,EACtB,cAAc,GAAE,SAAS,EAAO,EAChC,aAAa,GAAE;IACb,GAAG,EAAE,YAAY,EAAE,CAAC;IACpB,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,GAAG,EAAE,YAAY,EAAE,CAAC;IACpB,MAAM,EAAE,YAAY,EAAE,CAAC;CACuB,EAChD,UAAU,GAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,aAAsB,EACnF,YAAY,SAAI,EAChB,gBAAgB,UAAQ,EACxB,MAAM,CAAC,EAAE,cAAc,EACvB,YAAY,CAAC,EAAE,YAAY,EAC3B,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EACnC,gBAAgB,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,EAO/C,oBAAoB,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAC7C,aAAa,CAAC,KAAK,CAAC,CAsmBtB"}
|
|
@@ -25,6 +25,21 @@ function actionAttrName(action) {
|
|
|
25
25
|
const values = Object.values(action.names);
|
|
26
26
|
return values[0];
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Counts how many ExpressionAttributeValues placeholders a condition tree
|
|
30
|
+
* introduces. Each leaf operator advances the opBuilder's counter once per
|
|
31
|
+
* value it emits (`eq` → 1, `between` → 2, `in` → N, `exists`/`notExists` → 0);
|
|
32
|
+
* combinators (AND/OR/NOT) emit none of their own. The total therefore equals
|
|
33
|
+
* how far the shared placeholder counter advanced while building the tree —
|
|
34
|
+
* exactly how much the update builder must advance its own `valueCounter` so a
|
|
35
|
+
* later `.set()` on the same attribute never reuses a condition placeholder.
|
|
36
|
+
*/
|
|
37
|
+
function countConditionValues(cond) {
|
|
38
|
+
if (cond.children && cond.children.length > 0) {
|
|
39
|
+
return cond.children.reduce((n, child) => n + countConditionValues(child), 0);
|
|
40
|
+
}
|
|
41
|
+
return cond.values ? Object.keys(cond.values).length : 0;
|
|
42
|
+
}
|
|
28
43
|
/**
|
|
29
44
|
* Reject `undefined` before it reaches DynamoDB. SET / ADD / DELETE /
|
|
30
45
|
* setIfNotExists all encode the value into `ExpressionAttributeValues` — an
|
|
@@ -90,9 +105,18 @@ setIfNotExistsInputs = {}) {
|
|
|
90
105
|
return { name: prop };
|
|
91
106
|
},
|
|
92
107
|
});
|
|
93
|
-
|
|
108
|
+
// Seed the condition's placeholder counter from THIS builder's
|
|
109
|
+
// valueCounter so condition values (`:status_0`) share one numbering
|
|
110
|
+
// space with the SET/ADD/DELETE action values that draw from the same
|
|
111
|
+
// counter. Then advance valueCounter past the values this condition
|
|
112
|
+
// consumed so a subsequent `.set()` on the same attribute gets a fresh
|
|
113
|
+
// placeholder. Without this, `.where(eq(status,'pending')).set('status',
|
|
114
|
+
// 'active')` emitted `:status_0` twice and the merge in dbParams()
|
|
115
|
+
// silently kept one value — writing the condition's (old) value.
|
|
116
|
+
const opBuilder = (0, shared_1.createOpBuilder)(valueCounter);
|
|
94
117
|
const condition = fn(attrs, opBuilder);
|
|
95
|
-
|
|
118
|
+
const consumed = countConditionValues(condition);
|
|
119
|
+
return createUpdateBuilder(tableName, key, client, [...conditions, condition], updateActions, returnMode, valueCounter + consumed, enableTimestamps, logger, indexContext, setInputs, consumedCapacity, setIfNotExistsInputs);
|
|
96
120
|
},
|
|
97
121
|
set(attrOrUpdates, value) {
|
|
98
122
|
// When no value is provided and the first arg is a plain object,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-entity-api.d.ts","sourceRoot":"","sources":["../../src/entity/create-entity-api.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"create-entity-api.d.ts","sourceRoot":"","sources":["../../src/entity/create-entity-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AActF,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAOtD;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,SAAS,eAAe,EAC3D,WAAW,MAAM,EACjB,WAAW,MAAM,EACjB,OAAO,KAAK,EACZ,QAAQ,cAAc,EACtB,UAAS,gBAAqB,KAC7B,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,KAAK,CAAC,CAoPtE,CAAC"}
|
|
@@ -4,6 +4,7 @@ exports.createEntityAPI = void 0;
|
|
|
4
4
|
const model_utils_1 = require("../utils/model-utils");
|
|
5
5
|
const zod_utils_1 = require("../utils/zod-utils");
|
|
6
6
|
const builders_1 = require("../builders");
|
|
7
|
+
const build_upsert_seed_1 = require("./transforms/build-upsert-seed");
|
|
7
8
|
const with_middleware_1 = require("./middleware/with-middleware");
|
|
8
9
|
const factories_1 = require("./middleware/factories");
|
|
9
10
|
const key_validation_1 = require("./validation/key-validation");
|
|
@@ -101,22 +102,13 @@ const createEntityAPI = (tableName, modelName, model, client, options = {}) => {
|
|
|
101
102
|
// Resolve any key defaults or computed keys
|
|
102
103
|
const fullKey = (0, model_utils_1.resolveKeys)(model, key);
|
|
103
104
|
const builder = (0, builders_1.createUpdateBuilder)(tableName, fullKey, client, [],
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
// it just processes whatever actions it is handed. A fixed `:_type`
|
|
110
|
-
// placeholder keeps it out of the value-name counter so it never
|
|
111
|
-
// perturbs the numbering of the caller's own actions.
|
|
105
|
+
// UpdateItem is an upsert: seed the entity's identity columns (`_type`
|
|
106
|
+
// discriminator + primary-key template vars) so a create-via-update
|
|
107
|
+
// round-trips with its `id` intact and stays visible to the
|
|
108
|
+
// type-filtered query()/scan(). See buildUpsertSeedActions for the full
|
|
109
|
+
// rationale and why these never trip the builder's PK-immutability guard.
|
|
112
110
|
{
|
|
113
|
-
set:
|
|
114
|
-
{
|
|
115
|
-
expression: '#_type = :_type',
|
|
116
|
-
names: { '#_type': '_type' },
|
|
117
|
-
values: { ':_type': modelName },
|
|
118
|
-
},
|
|
119
|
-
],
|
|
111
|
+
set: (0, build_upsert_seed_1.buildUpsertSeedActions)(model, modelName, key),
|
|
120
112
|
remove: [],
|
|
121
113
|
add: [],
|
|
122
114
|
delete: [],
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ModelDefinition } from '../../core/types';
|
|
2
|
+
import { UpdateAction } from '../../builders';
|
|
3
|
+
/**
|
|
4
|
+
* Builds the SET actions that seed an `update()` so a create-via-update
|
|
5
|
+
* (DynamoDB's UpdateItem is an upsert) produces a fully-formed item rather than
|
|
6
|
+
* a half-written one.
|
|
7
|
+
*
|
|
8
|
+
* UpdateItem on a non-existent key writes only the Key (PK/SK) plus the caller's
|
|
9
|
+
* explicit actions. That leaves two gaps versus `put()`, which writes the whole
|
|
10
|
+
* validated model:
|
|
11
|
+
*
|
|
12
|
+
* 1. `_type` discriminator — `query()`/`scan()` filter on `#_type = :_type`, so
|
|
13
|
+
* an item created without it is silently invisible to both.
|
|
14
|
+
* 2. Primary-key template variables (e.g. `id` in `PK: 'OPERATION#${id}'`) —
|
|
15
|
+
* they stay embedded inside the PK/SK strings and never become standalone
|
|
16
|
+
* columns, so a read with `cleanInternalKeys` (which strips PK/SK without
|
|
17
|
+
* reconstructing the vars) returns the item with `id: undefined`.
|
|
18
|
+
*
|
|
19
|
+
* Seeding both here mirrors how `put()` materializes the model. The update
|
|
20
|
+
* builder stays agnostic — it just processes the actions it is handed. Two
|
|
21
|
+
* properties make the seed safe to inject:
|
|
22
|
+
*
|
|
23
|
+
* - Fixed `:_type` / `:_key_<attr>` placeholders keep these values out of the
|
|
24
|
+
* builder's value-name counter, so they never perturb the numbering of the
|
|
25
|
+
* caller's own `.set()` values (`:name_0`, …).
|
|
26
|
+
* - The key-var actions write the exact value already encoded in the Key, so
|
|
27
|
+
* they can never drift, and they bypass the PK-template immutability guard
|
|
28
|
+
* because that guard only inspects the caller's `.set()` payload — not the
|
|
29
|
+
* seeded actions handed to the builder.
|
|
30
|
+
*
|
|
31
|
+
* @param model - The entity's model definition (source of the key templates)
|
|
32
|
+
* @param modelName - The entity name, written as the `_type` discriminator
|
|
33
|
+
* @param key - The template-variable-bearing key passed to `update()`
|
|
34
|
+
* (e.g. `{ id: '123' }`)
|
|
35
|
+
* @returns The SET actions to seed the update builder's `set` list with.
|
|
36
|
+
*/
|
|
37
|
+
export declare const buildUpsertSeedActions: (model: ModelDefinition, modelName: string, key: Record<string, unknown>) => UpdateAction[];
|
|
38
|
+
//# sourceMappingURL=build-upsert-seed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-upsert-seed.d.ts","sourceRoot":"","sources":["../../../src/entity/transforms/build-upsert-seed.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,sBAAsB,GACjC,OAAO,eAAe,EACtB,WAAW,MAAM,EACjB,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC3B,YAAY,EA4Bd,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildUpsertSeedActions = void 0;
|
|
4
|
+
const model_utils_1 = require("../../utils/model-utils");
|
|
5
|
+
/**
|
|
6
|
+
* Builds the SET actions that seed an `update()` so a create-via-update
|
|
7
|
+
* (DynamoDB's UpdateItem is an upsert) produces a fully-formed item rather than
|
|
8
|
+
* a half-written one.
|
|
9
|
+
*
|
|
10
|
+
* UpdateItem on a non-existent key writes only the Key (PK/SK) plus the caller's
|
|
11
|
+
* explicit actions. That leaves two gaps versus `put()`, which writes the whole
|
|
12
|
+
* validated model:
|
|
13
|
+
*
|
|
14
|
+
* 1. `_type` discriminator — `query()`/`scan()` filter on `#_type = :_type`, so
|
|
15
|
+
* an item created without it is silently invisible to both.
|
|
16
|
+
* 2. Primary-key template variables (e.g. `id` in `PK: 'OPERATION#${id}'`) —
|
|
17
|
+
* they stay embedded inside the PK/SK strings and never become standalone
|
|
18
|
+
* columns, so a read with `cleanInternalKeys` (which strips PK/SK without
|
|
19
|
+
* reconstructing the vars) returns the item with `id: undefined`.
|
|
20
|
+
*
|
|
21
|
+
* Seeding both here mirrors how `put()` materializes the model. The update
|
|
22
|
+
* builder stays agnostic — it just processes the actions it is handed. Two
|
|
23
|
+
* properties make the seed safe to inject:
|
|
24
|
+
*
|
|
25
|
+
* - Fixed `:_type` / `:_key_<attr>` placeholders keep these values out of the
|
|
26
|
+
* builder's value-name counter, so they never perturb the numbering of the
|
|
27
|
+
* caller's own `.set()` values (`:name_0`, …).
|
|
28
|
+
* - The key-var actions write the exact value already encoded in the Key, so
|
|
29
|
+
* they can never drift, and they bypass the PK-template immutability guard
|
|
30
|
+
* because that guard only inspects the caller's `.set()` payload — not the
|
|
31
|
+
* seeded actions handed to the builder.
|
|
32
|
+
*
|
|
33
|
+
* @param model - The entity's model definition (source of the key templates)
|
|
34
|
+
* @param modelName - The entity name, written as the `_type` discriminator
|
|
35
|
+
* @param key - The template-variable-bearing key passed to `update()`
|
|
36
|
+
* (e.g. `{ id: '123' }`)
|
|
37
|
+
* @returns The SET actions to seed the update builder's `set` list with.
|
|
38
|
+
*/
|
|
39
|
+
const buildUpsertSeedActions = (model, modelName, key) => {
|
|
40
|
+
const seed = [
|
|
41
|
+
{
|
|
42
|
+
expression: '#_type = :_type',
|
|
43
|
+
names: { '#_type': '_type' },
|
|
44
|
+
values: { ':_type': modelName },
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
// Collect every variable referenced by the primary-key templates (a var may
|
|
48
|
+
// appear in both PK and SK; the Set dedupes it to a single SET action).
|
|
49
|
+
const keyTemplateVars = new Set();
|
|
50
|
+
for (const keyDef of Object.values(model.key)) {
|
|
51
|
+
for (const v of (0, model_utils_1.extractTemplateVars)(keyDef.value))
|
|
52
|
+
keyTemplateVars.add(v);
|
|
53
|
+
}
|
|
54
|
+
for (const v of keyTemplateVars) {
|
|
55
|
+
// A missing var would already have been caught by validateKeyFields; guard
|
|
56
|
+
// anyway so we never emit an action with an undefined value.
|
|
57
|
+
if (key[v] === undefined)
|
|
58
|
+
continue;
|
|
59
|
+
seed.push({
|
|
60
|
+
expression: `#${v} = :_key_${v}`,
|
|
61
|
+
names: { [`#${v}`]: v },
|
|
62
|
+
values: { [`:_key_${v}`]: key[v] },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return seed;
|
|
66
|
+
};
|
|
67
|
+
exports.buildUpsertSeedActions = buildUpsertSeedActions;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-utils.d.ts","sourceRoot":"","sources":["../../src/utils/model-utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAiB,eAAe,EAAE,MAAM,cAAc,CAAC;AAG7F;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,UAAU,MAAM,KAAG,MAAM,EAG5D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,MAAM,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAG,MAoB7E,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,eAAe,EACnD,OAAO,CAAC,EACR,OAAO,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC1B,OAAM,KAAK,GAAG,OAAO,GAAG,MAAc,KACrC,MAAM,CAAC,MAAM,EAAE,MAAM,CAQvB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;CACnE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,GAAI,CAAC,SAAS,eAAe,EAC3D,OAAO,CAAC,EACR,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5B,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAC3B,mBAwBF,CAAC;
|
|
1
|
+
{"version":3,"file":"model-utils.d.ts","sourceRoot":"","sources":["../../src/utils/model-utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAiB,eAAe,EAAE,MAAM,cAAc,CAAC;AAG7F;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,UAAU,MAAM,KAAG,MAAM,EAG5D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,MAAM,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAG,MAoB7E,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,eAAe,EACnD,OAAO,CAAC,EACR,OAAO,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC1B,OAAM,KAAK,GAAG,OAAO,GAAG,MAAc,KACrC,MAAM,CAAC,MAAM,EAAE,MAAM,CAQvB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;CACnE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,GAAI,CAAC,SAAS,eAAe,EAC3D,OAAO,CAAC,EACR,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5B,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAC3B,mBAwBF,CAAC;AAgBF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,SAAS,eAAe,EACzD,OAAO,CAAC,EACR,eAAe,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAClC,UAAU;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,KACrD,UAAU,CAAC,CAAC,CAiCd,CAAC;AAWF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,GACpC,SAAS,iBAAiB,GAAG,SAAS,KACrC,MAAM,EAUR,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,EACjC,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,SAAS,EACzB,eAAc,SAAS,MAAM,EAA0B,KACtD,CAAC,GAAG,CAAC,EAAE,GAAG,SAqBZ,CAAC"}
|
|
@@ -78,6 +78,18 @@ const computeIndexUpdates = (model, keyVars, updates) => {
|
|
|
78
78
|
return { actions, missing };
|
|
79
79
|
};
|
|
80
80
|
exports.computeIndexUpdates = computeIndexUpdates;
|
|
81
|
+
/**
|
|
82
|
+
* Produces a fresh copy of a schema default so two items created without that
|
|
83
|
+
* field never share the same array/object instance.
|
|
84
|
+
*
|
|
85
|
+
* A non-function `default` declared in the schema (e.g. `{ type: Array,
|
|
86
|
+
* default: [] }`) is a single value that lives on the schema object. Assigning
|
|
87
|
+
* it by reference means every item defaulted from it aliases that one instance
|
|
88
|
+
* — mutating one item's field (`item.history.push(...)`) then poisons the
|
|
89
|
+
* schema default and every later create. Primitives are immutable and returned
|
|
90
|
+
* as-is; arrays/objects/Dates are structurally cloned.
|
|
91
|
+
*/
|
|
92
|
+
const cloneDefault = (value) => value !== null && typeof value === 'object' ? structuredClone(value) : value;
|
|
81
93
|
/**
|
|
82
94
|
* Applies default and generated values to validated input
|
|
83
95
|
*/
|
|
@@ -93,7 +105,7 @@ const applyPostDefaults = (model, validatedItem, options) => {
|
|
|
93
105
|
result[key] = crypto.randomUUID();
|
|
94
106
|
}
|
|
95
107
|
else if (attr.default !== undefined) {
|
|
96
|
-
result[key] = typeof attr.default === 'function' ? attr.default() : attr.default;
|
|
108
|
+
result[key] = typeof attr.default === 'function' ? attr.default() : cloneDefault(attr.default);
|
|
97
109
|
}
|
|
98
110
|
}
|
|
99
111
|
}
|