@smallpen/core 0.1.0-alpha.1

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.
@@ -0,0 +1,102 @@
1
+ // DSP-017: Authoring Intent data contract and mapping to canonical operations.
2
+ //
3
+ // The workbench expresses edits as Authoring Intents; this module validates
4
+ // each intent (owner/readOnly/alias-vs-value) and compiles it into existing
5
+ // canonical Operation Batch operations. No new write paths are introduced.
6
+ import { fail } from "./errors.mjs";
7
+
8
+ const SUPPORTED_ACTIONS = new Set([
9
+ "set-token-value",
10
+ "set-token-alias",
11
+ "clear-token-alias",
12
+ "add-variant-override",
13
+ "remove-variant-override",
14
+ "update-component-definition",
15
+ ]);
16
+
17
+ const READ_ONLY_SOURCES = new Set(["library", "foundation"]);
18
+
19
+ export function validateAuthoringIntent(intent) {
20
+ if (!intent || typeof intent !== "object") {
21
+ return { ok: false, code: "invalid_intent", message: "Intent must be an object" };
22
+ }
23
+ if (!SUPPORTED_ACTIONS.has(intent.action)) {
24
+ return {
25
+ ok: false,
26
+ code: "unsupported_action",
27
+ message: `Unsupported authoring action: ${intent.action}`,
28
+ };
29
+ }
30
+ if (!intent.target || typeof intent.target !== "object") {
31
+ return { ok: false, code: "missing_target", message: "Intent target is required" };
32
+ }
33
+ const target = intent.target;
34
+ if (!target.ownerPackageId || typeof target.ownerPackageId !== "string") {
35
+ return { ok: false, code: "missing_owner", message: "Intent target ownerPackageId is required" };
36
+ }
37
+ if (READ_ONLY_SOURCES.has(target.source)) {
38
+ return {
39
+ ok: false,
40
+ code: "read_only_source",
41
+ message: `Cannot write to read-only source: ${target.source}`,
42
+ };
43
+ }
44
+ if (intent.action === "set-token-value" || intent.action === "set-token-alias") {
45
+ if (intent.value === undefined) {
46
+ return { ok: false, code: "missing_value", message: "Token value is required" };
47
+ }
48
+ // Resolved values must not overwrite alias expressions.
49
+ if (
50
+ intent.action === "set-token-alias" &&
51
+ typeof intent.value === "string" &&
52
+ !/^\{[^{}]+\}$/.test(intent.value)
53
+ ) {
54
+ return {
55
+ ok: false,
56
+ code: "invalid_alias_expression",
57
+ message: `Alias expression must be {path}, got: ${intent.value}`,
58
+ };
59
+ }
60
+ }
61
+ return { ok: true };
62
+ }
63
+
64
+ export function intentToOperations(intent, snapshot) {
65
+ const validation = validateAuthoringIntent(intent);
66
+ if (!validation.ok) {
67
+ fail(validation.code, validation.message);
68
+ }
69
+ const target = intent.target;
70
+ const packageId = target.ownerPackageId;
71
+ switch (intent.action) {
72
+ case "set-token-value": {
73
+ return [{
74
+ type: "set-token-value",
75
+ filePath: target.filePath ?? "tokens/tokens.json",
76
+ path: target.path,
77
+ tokenId: target.tokenId,
78
+ value: intent.value,
79
+ }];
80
+ }
81
+ case "set-token-alias": {
82
+ return [{
83
+ type: "set-token-value",
84
+ filePath: target.filePath ?? "tokens/tokens.json",
85
+ path: target.path,
86
+ tokenId: target.tokenId,
87
+ value: intent.value,
88
+ }];
89
+ }
90
+ case "clear-token-alias": {
91
+ return [{
92
+ type: "set-token-value",
93
+ filePath: target.filePath ?? "tokens/tokens.json",
94
+ path: target.path,
95
+ tokenId: target.tokenId,
96
+ value: intent.fallbackValue,
97
+ }];
98
+ }
99
+ default:
100
+ fail("unsupported_intent_action", `No compiler for action: ${intent.action}`);
101
+ }
102
+ }