@dforge-core/metadata 0.0.24 → 0.0.26
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 +55 -0
- package/dist/dsl/index.js +17 -7
- package/dist/dsl/index.js.map +1 -1
- package/dist/index.d.ts +42 -2
- package/dist/index.js +54 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dsl/check.ts +28 -7
- package/src/index.ts +2 -1
- package/src/traits.ts +131 -20
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,61 @@ All notable changes to `@dforge-core/metadata` are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this package adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.0.26] — 2026-09-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`traitFieldConflicts()` and the `TraitFieldConflict` type** report the collisions
|
|
13
|
+
that make the installer refuse an entity: a trait contributing a field the entity
|
|
14
|
+
already has under a different type identity (`columnType ?? dbDatatype ??
|
|
15
|
+
fieldTypeCd`, the identity `TraitExpander.TryAddField` compares). Install throws on
|
|
16
|
+
one, so a module that hits it cannot install — offline readers now name the field,
|
|
17
|
+
both types, and whether the definition being kept is an authored field or another
|
|
18
|
+
trait's. Expansion itself stays total, so an editor can still draw a field list.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **A field code that names an inherited property is a field like any other.** The
|
|
23
|
+
merge read `sink.fields[key]` against a plain object, so a trait defining
|
|
24
|
+
`constructor` found `Object.prototype`'s, contributed no column, and reported a
|
|
25
|
+
conflict against a type identity read off a function. Own-property lookups
|
|
26
|
+
throughout, on a prototype-less merge map — which also fixes the write side, where a
|
|
27
|
+
field named `__proto__` set the prototype instead of a key — and on the
|
|
28
|
+
`localTraits` lookup, which is parsed JSON with the same hazard.
|
|
29
|
+
|
|
30
|
+
- **Two templated keys that collide on one entity are a conflict, not a silent
|
|
31
|
+
overwrite.** A trait defining both `{entity}_id` and `x_id` collapsed them while
|
|
32
|
+
templating, for entity `x`, before the merge ever saw them: the later definition won
|
|
33
|
+
and nothing was reported, while install rejected the entity. Each field is now
|
|
34
|
+
resolved and merged on its own, the way the installer resolves and adds one key at a
|
|
35
|
+
time — the first definition wins, the second is reported.
|
|
36
|
+
|
|
37
|
+
## [0.0.25] — 2026-09-15
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- **`templateChunkValue()` and the `StringArg` type** are documented as exports of
|
|
42
|
+
`@dforge-core/metadata/dsl`. Both landed in 0.0.24 without an entry:
|
|
43
|
+
`CallRef.firstStringArg` became a `StringArg`, which reads a template literal as
|
|
44
|
+
well as a quoted string — reassembled from the chunks the lexer splits one into,
|
|
45
|
+
with `interpolated` marking the `${…}` holes and a space standing in for each —
|
|
46
|
+
so a rule that reads a call's first argument sees the multi-line form too.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **`dsl/unknown-builtin` scopes a declared helper to its block, because the
|
|
51
|
+
compiler does.** `execute:` and `onBeforeStart:` are compiled to separate
|
|
52
|
+
scripts and each gets its own `CheckUndefinedReferences` pass, so a `function`
|
|
53
|
+
declared in one is undefined in the other — and the rule, which read
|
|
54
|
+
declarations across the whole file, accepted the call that install rejects. A
|
|
55
|
+
declaration above the first header belongs to no block and stays visible to
|
|
56
|
+
both; nothing legal can sit there.
|
|
57
|
+
|
|
58
|
+
- **`dsl/job-record-context` stands down on a `records` the script binds
|
|
59
|
+
itself.** `var records = select(…)` is the very refactor the rule asks for, so
|
|
60
|
+
reporting it left the author with a diagnostic that its own fix could not
|
|
61
|
+
clear. Shadowing is read exactly as the execution-mode rule already reads it.
|
|
62
|
+
|
|
8
63
|
## [0.0.24] — 2026-09-15
|
|
9
64
|
|
|
10
65
|
### Added
|
package/dist/dsl/index.js
CHANGED
|
@@ -1163,12 +1163,20 @@ function checkDsl(text, ctx = {}) {
|
|
|
1163
1163
|
);
|
|
1164
1164
|
}
|
|
1165
1165
|
}
|
|
1166
|
-
const declaredFunctions = /* @__PURE__ */ new
|
|
1166
|
+
const declaredFunctions = /* @__PURE__ */ new Map();
|
|
1167
1167
|
for (let i = 0; i < parsed.tokens.length; i++) {
|
|
1168
|
-
|
|
1168
|
+
const token = parsed.tokens[i];
|
|
1169
|
+
if (token.text !== "function") continue;
|
|
1169
1170
|
const name = parsed.tokens[i + 1];
|
|
1170
|
-
if (name?.kind
|
|
1171
|
+
if (name?.kind !== "ident") continue;
|
|
1172
|
+
const blocks = declaredFunctions.get(name.text) ?? /* @__PURE__ */ new Set();
|
|
1173
|
+
blocks.add(blockOf(parsed, token.start));
|
|
1174
|
+
declaredFunctions.set(name.text, blocks);
|
|
1171
1175
|
}
|
|
1176
|
+
const isDeclaredIn = (name, block) => {
|
|
1177
|
+
const blocks = declaredFunctions.get(name);
|
|
1178
|
+
return blocks !== void 0 && (blocks.has(null) || blocks.has(block));
|
|
1179
|
+
};
|
|
1172
1180
|
const reportedUnknown = /* @__PURE__ */ new Set();
|
|
1173
1181
|
for (const call of parsed.calls) {
|
|
1174
1182
|
if (call.block !== "execute" && call.block !== "onBeforeStart") continue;
|
|
@@ -1180,7 +1188,7 @@ function checkDsl(text, ctx = {}) {
|
|
|
1180
1188
|
if (isMethodName(parsed.tokens, idx)) continue;
|
|
1181
1189
|
}
|
|
1182
1190
|
if (BUILTIN_BY_NAME.has(name) || CONTROL_KEYWORDS.has(name) || KEYWORDS_BEFORE_PAREN.has(name) || JS_GLOBALS.has(name) || FORMULA_ONLY.has(name) || // Its own rule reports this one, with better wording.
|
|
1183
|
-
name === "userId" ||
|
|
1191
|
+
name === "userId" || isDeclaredIn(name, call.block) || parsed.locals.has(name) || reportedUnknown.has(name)) {
|
|
1184
1192
|
continue;
|
|
1185
1193
|
}
|
|
1186
1194
|
reportedUnknown.add(name);
|
|
@@ -1205,9 +1213,11 @@ function checkDsl(text, ctx = {}) {
|
|
|
1205
1213
|
if (ref.global !== "old" || !inScript(ref.block)) continue;
|
|
1206
1214
|
bound.push({ span: ref.span, read: `old[${ref.property}]` });
|
|
1207
1215
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1216
|
+
if (!parsed.locals.has("records")) {
|
|
1217
|
+
for (const span of parsed.recordsRefs) {
|
|
1218
|
+
if (!inScript(blockOf(parsed, span.start))) continue;
|
|
1219
|
+
bound.push({ span, read: "records" });
|
|
1220
|
+
}
|
|
1211
1221
|
}
|
|
1212
1222
|
const first = bound.sort((a, b) => a.span.start - b.span.start)[0];
|
|
1213
1223
|
if (first) {
|