@ftschopp/dynatable-core 2.3.0 → 2.3.2

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 CHANGED
@@ -1,3 +1,17 @@
1
+ ## @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)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **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))
7
+
8
+ ## @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)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **core:** stamp _type discriminator on update() so upserts stay visible ([#59](https://github.com/ftschopp/dynatable/issues/59)) ([f5f6116](https://github.com/ftschopp/dynatable/commit/f5f61163c61f298fc45dbf2a32dd7352b5231976))
14
+
1
15
  # @ftschopp/dynatable-core [2.3.0](https://github.com/ftschopp/dynatable/compare/@ftschopp/dynatable-core@2.2.1...@ftschopp/dynatable-core@2.3.0) (2026-06-03)
2
16
 
3
17
 
@@ -1 +1 @@
1
- {"version":3,"file":"create-entity-api.d.ts","sourceRoot":"","sources":["../../src/entity/create-entity-api.ts"],"names":[],"mappings":"AACA,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;AAMtD;;;;;;;;;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,CA0OtE,CAAC"}
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");
@@ -100,7 +101,18 @@ const createEntityAPI = (tableName, modelName, model, client, options = {}) => {
100
101
  (0, key_validation_1.validateKeyFields)(modelName, model, key, 'update()');
101
102
  // Resolve any key defaults or computed keys
102
103
  const fullKey = (0, model_utils_1.resolveKeys)(model, key);
103
- const builder = (0, builders_1.createUpdateBuilder)(tableName, fullKey, client, [], { set: [], remove: [], add: [], delete: [] }, 'NONE', 0, timestamps, logger,
104
+ const builder = (0, builders_1.createUpdateBuilder)(tableName, fullKey, client, [],
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.
110
+ {
111
+ set: (0, build_upsert_seed_1.buildUpsertSeedActions)(model, modelName, key),
112
+ remove: [],
113
+ add: [],
114
+ delete: [],
115
+ }, 'NONE', 0, timestamps, logger,
104
116
  // Carry the original template-vars-bearing key (e.g. { username: 'jane' })
105
117
  // so the builder can recompute index keys when .set() touches their template fields.
106
118
  { model, keyVars: key });
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ftschopp/dynatable-core",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "description": "Core library for DynamoDB single table design",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",