projectionlab-mcp 1.0.1 → 1.2.0
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/HOW-IT-WORKS.md +2 -2
- package/MANUAL-SETUP.md +64 -0
- package/README.md +34 -271
- package/REFERENCE.md +221 -0
- package/SETUP-FOR-AGENTS.md +37 -0
- package/dist/bootstrap.js +20 -3
- package/dist/bootstrap.js.map +1 -1
- package/dist/bridge.d.ts +2 -0
- package/dist/clonePlan.d.ts +9 -0
- package/dist/clonePlan.js +15 -14
- package/dist/clonePlan.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +41 -6
- package/dist/config.js.map +1 -1
- package/dist/currentFinances.d.ts +12 -1
- package/dist/currentFinances.js +60 -0
- package/dist/currentFinances.js.map +1 -1
- package/dist/dataModel.d.ts +58 -0
- package/dist/dataModel.js +1429 -0
- package/dist/dataModel.js.map +1 -0
- package/dist/deletePlan.d.ts +17 -0
- package/dist/deletePlan.js +72 -0
- package/dist/deletePlan.js.map +1 -0
- package/dist/diff.d.ts +288 -0
- package/dist/diff.js +744 -0
- package/dist/diff.js.map +1 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +14 -1
- package/dist/errors.js.map +1 -1
- package/dist/extensionBridge.d.ts +16 -2
- package/dist/extensionBridge.js +104 -23
- package/dist/extensionBridge.js.map +1 -1
- package/dist/index.js +27 -1
- package/dist/index.js.map +1 -1
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.js +3 -2
- package/dist/instructions.js.map +1 -1
- package/dist/largeOutput.js +25 -0
- package/dist/largeOutput.js.map +1 -1
- package/dist/logger.js +11 -4
- package/dist/logger.js.map +1 -1
- package/dist/pathPatch.d.ts +5 -2
- package/dist/pathPatch.js +16 -2
- package/dist/pathPatch.js.map +1 -1
- package/dist/planSettings.d.ts +1 -1
- package/dist/planSettings.js +3 -3
- package/dist/planSettings.js.map +1 -1
- package/dist/plans.d.ts +73 -10
- package/dist/plans.js +206 -42
- package/dist/plans.js.map +1 -1
- package/dist/prompts/index.js +7 -4
- package/dist/prompts/index.js.map +1 -1
- package/dist/realEstate.d.ts +22 -0
- package/dist/realEstate.js +118 -0
- package/dist/realEstate.js.map +1 -0
- package/dist/redact.d.ts +26 -0
- package/dist/redact.js +93 -1
- package/dist/redact.js.map +1 -1
- package/dist/resources/dataModel.d.ts +3 -0
- package/dist/resources/dataModel.js +66 -0
- package/dist/resources/dataModel.js.map +1 -0
- package/dist/resources/knowledge.js +189 -47
- package/dist/resources/knowledge.js.map +1 -1
- package/dist/resources/planData.js +3 -1
- package/dist/resources/planData.js.map +1 -1
- package/dist/resources/summary.js +4 -1
- package/dist/resources/summary.js.map +1 -1
- package/dist/snapshot.d.ts +21 -0
- package/dist/snapshot.js +116 -12
- package/dist/snapshot.js.map +1 -1
- package/dist/summary.js +19 -6
- package/dist/summary.js.map +1 -1
- package/dist/tools/clonePlan.js +17 -8
- package/dist/tools/clonePlan.js.map +1 -1
- package/dist/tools/connection.js +29 -4
- package/dist/tools/connection.js.map +1 -1
- package/dist/tools/deletePlan.d.ts +4 -0
- package/dist/tools/deletePlan.js +103 -0
- package/dist/tools/deletePlan.js.map +1 -0
- package/dist/tools/diff.d.ts +4 -0
- package/dist/tools/diff.js +417 -0
- package/dist/tools/diff.js.map +1 -0
- package/dist/tools/getCurrentFinances.js +27 -4
- package/dist/tools/getCurrentFinances.js.map +1 -1
- package/dist/tools/getPlan.js +4 -4
- package/dist/tools/getPlan.js.map +1 -1
- package/dist/tools/getPlanSection.js +7 -6
- package/dist/tools/getPlanSection.js.map +1 -1
- package/dist/tools/getPlanSettings.js +10 -6
- package/dist/tools/getPlanSettings.js.map +1 -1
- package/dist/tools/restore.js +18 -1
- package/dist/tools/restore.js.map +1 -1
- package/dist/tools/schema.js +28 -4
- package/dist/tools/schema.js.map +1 -1
- package/dist/tools/snapshots.d.ts +1 -1
- package/dist/tools/snapshots.js +53 -6
- package/dist/tools/snapshots.js.map +1 -1
- package/dist/tools/summary.js +3 -2
- package/dist/tools/summary.js.map +1 -1
- package/dist/tools/updateAccount.js +9 -7
- package/dist/tools/updateAccount.js.map +1 -1
- package/dist/tools/updatePlanEvent.js +71 -33
- package/dist/tools/updatePlanEvent.js.map +1 -1
- package/dist/tools/updatePlanSection.js +17 -32
- package/dist/tools/updatePlanSection.js.map +1 -1
- package/dist/tools/updatePlanSettings.js +11 -7
- package/dist/tools/updatePlanSettings.js.map +1 -1
- package/dist/types/projectionlab.d.ts +3 -1
- package/dist/types/wire.d.ts +27 -2
- package/dist/types/wire.js +6 -2
- package/dist/types/wire.js.map +1 -1
- package/dist/updateCheck.d.ts +22 -0
- package/dist/updateCheck.js +58 -0
- package/dist/updateCheck.js.map +1 -0
- package/dist/versionCompare.d.ts +5 -0
- package/dist/versionCompare.js +33 -0
- package/dist/versionCompare.js.map +1 -0
- package/dist/versionNotices.d.ts +9 -0
- package/dist/versionNotices.js +42 -0
- package/dist/versionNotices.js.map +1 -0
- package/package.json +6 -3
- package/skills/projectionlab/SKILL.md +329 -29
package/dist/largeOutput.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readdir, stat, unlink } from "node:fs/promises";
|
|
1
2
|
import { join } from "node:path";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import { toJson } from "./json.js";
|
|
@@ -5,6 +6,28 @@ import { secureMkdir, secureWriteFile } from "./secureFs.js";
|
|
|
5
6
|
import { log } from "./logger.js";
|
|
6
7
|
export const FILE_THRESHOLD = 100_000;
|
|
7
8
|
export const EXPORT_DIR = join(homedir(), ".projectionlab-mcp", "exports");
|
|
9
|
+
const RETENTION_DAYS = 30;
|
|
10
|
+
/** Delete export files whose last modification is older than the retention
|
|
11
|
+
* window. Only .json files this module writes are touched. */
|
|
12
|
+
async function prune() {
|
|
13
|
+
const cutoff = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1000;
|
|
14
|
+
const files = await readdir(EXPORT_DIR);
|
|
15
|
+
for (const file of files) {
|
|
16
|
+
if (!file.endsWith(".json"))
|
|
17
|
+
continue;
|
|
18
|
+
const filePath = join(EXPORT_DIR, file);
|
|
19
|
+
try {
|
|
20
|
+
const info = await stat(filePath);
|
|
21
|
+
if (info.mtimeMs < cutoff) {
|
|
22
|
+
await unlink(filePath);
|
|
23
|
+
log.info("largeOutput", `Pruned expired export ${file}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Skip unreadable files.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
8
31
|
/**
|
|
9
32
|
* Returns the text as-is when small enough; otherwise writes it to a file
|
|
10
33
|
* and returns a pointer message so MCP clients don't truncate mid-JSON.
|
|
@@ -17,6 +40,8 @@ export async function textOrFile(text, label, summary = {}) {
|
|
|
17
40
|
const filePath = join(EXPORT_DIR, `${label}-${timestamp}.json`);
|
|
18
41
|
await secureWriteFile(filePath, text);
|
|
19
42
|
log.info("largeOutput", `Wrote ${text.length} chars to ${filePath}`);
|
|
43
|
+
// Prune expired exports in the background, the way snapshots do.
|
|
44
|
+
prune().catch((err) => log.warn("largeOutput", `Prune failed: ${err}`));
|
|
20
45
|
return toJson({
|
|
21
46
|
truncated: true,
|
|
22
47
|
reason: `Result is ${text.length} chars, larger than the ${FILE_THRESHOLD} char inline limit`,
|
package/dist/largeOutput.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"largeOutput.js","sourceRoot":"","sources":["../src/largeOutput.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAElC,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC;AACtC,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,oBAAoB,EAAE,SAAS,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"largeOutput.js","sourceRoot":"","sources":["../src/largeOutput.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAElC,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC;AACtC,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,oBAAoB,EAAE,SAAS,CAAC,CAAC;AAC3E,MAAM,cAAc,GAAG,EAAE,CAAC;AAE1B;8DAC8D;AAC9D,KAAK,UAAU,KAAK;IAClB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IACjE,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC;IAExC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,SAAS;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;YAClC,IAAI,IAAI,CAAC,OAAO,GAAG,MAAM,EAAE,CAAC;gBAC1B,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACvB,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,yBAAyB,IAAI,EAAE,CAAC,CAAC;YAC3D,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,yBAAyB;QAC3B,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAY,EACZ,KAAa,EACb,UAAmC,EAAE;IAErC,IAAI,IAAI,CAAC,MAAM,IAAI,cAAc;QAAE,OAAO,IAAI,CAAC;IAE/C,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,GAAG,KAAK,IAAI,SAAS,OAAO,CAAC,CAAC;IAChE,MAAM,eAAe,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,IAAI,CAAC,MAAM,aAAa,QAAQ,EAAE,CAAC,CAAC;IAErE,iEAAiE;IACjE,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,iBAAiB,GAAG,EAAE,CAAC,CAAC,CAAC;IAExE,OAAO,MAAM,CACX;QACE,SAAS,EAAE,IAAI;QACf,MAAM,EAAE,aAAa,IAAI,CAAC,MAAM,2BAA2B,cAAc,oBAAoB;QAC7F,QAAQ;QACR,IAAI,EAAE,8DAA8D;QACpE,GAAG,OAAO;KACX,CACF,CAAC;AACJ,CAAC"}
|
package/dist/logger.js
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
import { config } from "./config.js";
|
|
2
|
+
import { redactSecrets } from "./redact.js";
|
|
2
3
|
const LEVELS = { error: 0, warn: 1, info: 2, debug: 3 };
|
|
3
4
|
const threshold = LEVELS[config.logLevel] ?? LEVELS.info;
|
|
4
5
|
function timestamp() {
|
|
5
6
|
return new Date().toISOString();
|
|
6
7
|
}
|
|
8
|
+
// Messages often carry an error from the browser executor, which can echo
|
|
9
|
+
// request data. Every line is scrubbed here so no call site can log the
|
|
10
|
+
// Plugin API key.
|
|
11
|
+
function write(label, op, msg) {
|
|
12
|
+
console.error(`[${timestamp()}] ${label} [${op}] ${redactSecrets(msg)}`);
|
|
13
|
+
}
|
|
7
14
|
export const log = {
|
|
8
15
|
error(op, msg) {
|
|
9
16
|
if (threshold >= LEVELS.error)
|
|
10
|
-
|
|
17
|
+
write("ERROR", op, msg);
|
|
11
18
|
},
|
|
12
19
|
warn(op, msg) {
|
|
13
20
|
if (threshold >= LEVELS.warn)
|
|
14
|
-
|
|
21
|
+
write("WARN ", op, msg);
|
|
15
22
|
},
|
|
16
23
|
info(op, msg) {
|
|
17
24
|
if (threshold >= LEVELS.info)
|
|
18
|
-
|
|
25
|
+
write("INFO ", op, msg);
|
|
19
26
|
},
|
|
20
27
|
debug(op, msg) {
|
|
21
28
|
if (threshold >= LEVELS.debug)
|
|
22
|
-
|
|
29
|
+
write("DEBUG", op, msg);
|
|
23
30
|
},
|
|
24
31
|
};
|
|
25
32
|
//# sourceMappingURL=logger.js.map
|
package/dist/logger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAW,CAAC;AAEjE,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC;AAEzD,SAAS,SAAS;IAChB,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED,0EAA0E;AAC1E,wEAAwE;AACxE,kBAAkB;AAClB,SAAS,KAAK,CAAC,KAAa,EAAE,EAAU,EAAE,GAAW;IACnD,OAAO,CAAC,KAAK,CAAC,IAAI,SAAS,EAAE,KAAK,KAAK,KAAK,EAAE,KAAK,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,KAAK,CAAC,EAAU,EAAE,GAAW;QAC3B,IAAI,SAAS,IAAI,MAAM,CAAC,KAAK;YAAE,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,CAAC,EAAU,EAAE,GAAW;QAC1B,IAAI,SAAS,IAAI,MAAM,CAAC,IAAI;YAAE,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,CAAC,EAAU,EAAE,GAAW;QAC1B,IAAI,SAAS,IAAI,MAAM,CAAC,IAAI;YAAE,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;IACxD,CAAC;IACD,KAAK,CAAC,EAAU,EAAE,GAAW;QAC3B,IAAI,SAAS,IAAI,MAAM,CAAC,KAAK;YAAE,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;IACzD,CAAC;CACF,CAAC"}
|
package/dist/pathPatch.d.ts
CHANGED
|
@@ -24,9 +24,12 @@ export interface AddressableValueOptions {
|
|
|
24
24
|
export declare function listAddressableValues(data: unknown, prefix?: string, options?: AddressableValueOptions): AddressableValue[];
|
|
25
25
|
/**
|
|
26
26
|
* Apply path-addressed changes immutably. Paths must already exist and values
|
|
27
|
-
* must retain their JSON type.
|
|
27
|
+
* must retain their JSON type. When `scope` names a group in the data model
|
|
28
|
+
* registry (a settings section, event section, or Current Finances
|
|
29
|
+
* collection), a string value outside an observed enum's choices is refused
|
|
30
|
+
* with INVALID_INPUT.
|
|
28
31
|
*/
|
|
29
|
-
export declare function applyExistingPathChanges(data: unknown, incoming: Record<string, unknown>, context: PathPatchContext): {
|
|
32
|
+
export declare function applyExistingPathChanges(data: unknown, incoming: Record<string, unknown>, context: PathPatchContext, scope?: string): {
|
|
30
33
|
updated: PlanObj;
|
|
31
34
|
changes: Record<string, PathValueChange>;
|
|
32
35
|
};
|
package/dist/pathPatch.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { enumRefusalError } from "./dataModel.js";
|
|
1
2
|
import { ProjectionLabError } from "./errors.js";
|
|
2
3
|
import { isSecretFieldName } from "./redact.js";
|
|
3
4
|
const UNSAFE_PATH_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
|
|
@@ -97,6 +98,11 @@ function assertCompatibleType(path, current, incoming, context) {
|
|
|
97
98
|
}
|
|
98
99
|
function replaceAtPath(root, parts, incoming, fullPath, context) {
|
|
99
100
|
const [head, ...tail] = parts;
|
|
101
|
+
// "length" is an own property of arrays, so hasOwnProperty alone would let
|
|
102
|
+
// a leaf patch resize the array through copy[Number("length")].
|
|
103
|
+
if (Array.isArray(root) && !/^(0|[1-9]\d*)$/.test(head)) {
|
|
104
|
+
throw new ProjectionLabError("INVALID_INPUT", `Array segment "${head}" in ${context.label} path "${fullPath}" is not an index`, `Use a numeric array index returned by ${context.discoveryTool}`);
|
|
105
|
+
}
|
|
100
106
|
if (!Object.prototype.hasOwnProperty.call(root, head)) {
|
|
101
107
|
throw new ProjectionLabError("INVALID_INPUT", `Unknown ${context.label} path "${fullPath}"`, `Only existing paths returned by ${context.discoveryTool} can be changed`);
|
|
102
108
|
}
|
|
@@ -127,9 +133,12 @@ function replaceAtPath(root, parts, incoming, fullPath, context) {
|
|
|
127
133
|
}
|
|
128
134
|
/**
|
|
129
135
|
* Apply path-addressed changes immutably. Paths must already exist and values
|
|
130
|
-
* must retain their JSON type.
|
|
136
|
+
* must retain their JSON type. When `scope` names a group in the data model
|
|
137
|
+
* registry (a settings section, event section, or Current Finances
|
|
138
|
+
* collection), a string value outside an observed enum's choices is refused
|
|
139
|
+
* with INVALID_INPUT.
|
|
131
140
|
*/
|
|
132
|
-
export function applyExistingPathChanges(data, incoming, context) {
|
|
141
|
+
export function applyExistingPathChanges(data, incoming, context, scope) {
|
|
133
142
|
if (!isPlainObject(data)) {
|
|
134
143
|
throw new ProjectionLabError("INVALID_INPUT", `The selected ${context.label} container is not an object`, `Inspect it with ${context.discoveryTool}`);
|
|
135
144
|
}
|
|
@@ -148,6 +157,11 @@ export function applyExistingPathChanges(data, incoming, context) {
|
|
|
148
157
|
const changes = {};
|
|
149
158
|
for (const [path, after] of Object.entries(incoming)) {
|
|
150
159
|
const parts = parsePath(path, context);
|
|
160
|
+
if (scope !== undefined) {
|
|
161
|
+
const refusal = enumRefusalError(scope, path, after);
|
|
162
|
+
if (refusal)
|
|
163
|
+
throw refusal;
|
|
164
|
+
}
|
|
151
165
|
let before = data;
|
|
152
166
|
for (const part of parts) {
|
|
153
167
|
if (!isContainer(before) ||
|
package/dist/pathPatch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pathPatch.js","sourceRoot":"","sources":["../src/pathPatch.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"pathPatch.js","sourceRoot":"","sources":["../src/pathPatch.ts"],"names":[],"mappings":"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"}
|
package/dist/planSettings.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type PlanSettingSection = (typeof PLAN_SETTING_SECTIONS)[number];
|
|
|
4
4
|
export type PlanSetting = AddressableValue;
|
|
5
5
|
export type PlanSettingChange = PathValueChange;
|
|
6
6
|
export declare function listPlanSettings(sectionData: unknown, section?: PlanSettingSection): PlanSetting[];
|
|
7
|
-
export declare function applyPlanSettingChanges(sectionData: unknown, incoming: Record<string, unknown
|
|
7
|
+
export declare function applyPlanSettingChanges(sectionData: unknown, incoming: Record<string, unknown>, section?: PlanSettingSection): {
|
|
8
8
|
updated: Record<string, unknown>;
|
|
9
9
|
changes: Record<string, PlanSettingChange>;
|
|
10
10
|
};
|
package/dist/planSettings.js
CHANGED
|
@@ -14,15 +14,15 @@ const ADVANCED_RATE_SCHEDULES = new Set([
|
|
|
14
14
|
const PLAN_SETTING_CONTEXT = {
|
|
15
15
|
label: "plan setting",
|
|
16
16
|
discoveryTool: "projectionlab_get_plan_settings",
|
|
17
|
-
example: '{ "inflation":
|
|
17
|
+
example: '{ "inflation": 3 }',
|
|
18
18
|
};
|
|
19
19
|
export function listPlanSettings(sectionData, section) {
|
|
20
20
|
return listAddressableValues(sectionData, "", section === "variables"
|
|
21
21
|
? { expandArrayPaths: ADVANCED_RATE_SCHEDULES }
|
|
22
22
|
: {});
|
|
23
23
|
}
|
|
24
|
-
export function applyPlanSettingChanges(sectionData, incoming) {
|
|
25
|
-
return applyExistingPathChanges(sectionData, incoming, PLAN_SETTING_CONTEXT);
|
|
24
|
+
export function applyPlanSettingChanges(sectionData, incoming, section) {
|
|
25
|
+
return applyExistingPathChanges(sectionData, incoming, PLAN_SETTING_CONTEXT, section);
|
|
26
26
|
}
|
|
27
27
|
export function planSettingChangesApplied(sectionData, expected) {
|
|
28
28
|
return pathChangesApplied(sectionData, expected, PLAN_SETTING_CONTEXT);
|
package/dist/planSettings.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planSettings.js","sourceRoot":"","sources":["../src/planSettings.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,kBAAkB,GAGnB,MAAM,gBAAgB,CAAC;AAExB,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,WAAW;IACX,YAAY;IACZ,oBAAoB;CACZ,CAAC;AAMX,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAC;IACtC,iBAAiB;IACjB,wBAAwB;IACxB,oBAAoB;IACpB,4BAA4B;IAC5B,wBAAwB;CACzB,CAAC,CAAC;AAEH,MAAM,oBAAoB,GAAG;IAC3B,KAAK,EAAE,cAAc;IACrB,aAAa,EAAE,iCAAiC;IAChD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"planSettings.js","sourceRoot":"","sources":["../src/planSettings.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,kBAAkB,GAGnB,MAAM,gBAAgB,CAAC;AAExB,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,WAAW;IACX,YAAY;IACZ,oBAAoB;CACZ,CAAC;AAMX,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAC;IACtC,iBAAiB;IACjB,wBAAwB;IACxB,oBAAoB;IACpB,4BAA4B;IAC5B,wBAAwB;CACzB,CAAC,CAAC;AAEH,MAAM,oBAAoB,GAAG;IAC3B,KAAK,EAAE,cAAc;IACrB,aAAa,EAAE,iCAAiC;IAChD,OAAO,EAAE,oBAAoB;CACrB,CAAC;AAEX,MAAM,UAAU,gBAAgB,CAC9B,WAAoB,EACpB,OAA4B;IAE5B,OAAO,qBAAqB,CAC1B,WAAW,EACX,EAAE,EACF,OAAO,KAAK,WAAW;QACrB,CAAC,CAAC,EAAE,gBAAgB,EAAE,uBAAuB,EAAE;QAC/C,CAAC,CAAC,EAAE,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,WAAoB,EACpB,QAAiC,EACjC,OAA4B;IAK5B,OAAO,wBAAwB,CAC7B,WAAW,EACX,QAAQ,EACR,oBAAoB,EACpB,OAAO,CACR,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,WAAoB,EACpB,QAA2C;IAE3C,OAAO,kBAAkB,CAAC,WAAW,EAAE,QAAQ,EAAE,oBAAoB,CAAC,CAAC;AACzE,CAAC"}
|
package/dist/plans.d.ts
CHANGED
|
@@ -2,13 +2,34 @@ import { ProjectionLabError } from "./errors.js";
|
|
|
2
2
|
export type PlanObj = Record<string, unknown>;
|
|
3
3
|
export declare const PLAN_SECTIONS: readonly ["income", "expenses", "accounts", "assets", "milestones", "variables", "withdrawalStrategy", "montecarlo", "priorities"];
|
|
4
4
|
export type PlanSection = (typeof PLAN_SECTIONS)[number];
|
|
5
|
+
/** Sections whose members are events addressed by id or name; the
|
|
6
|
+
* settings-like sections use projectionlab_update_plan_settings instead. */
|
|
7
|
+
export declare const EVENT_SECTIONS: readonly ["income", "expenses", "accounts", "assets", "milestones", "priorities"];
|
|
8
|
+
/** The container each section holds, known from its name alone and not from
|
|
9
|
+
* what a plan happens to store: milestones is a direct array, the event
|
|
10
|
+
* sections are objects with an events array, and the settings-like sections
|
|
11
|
+
* are plain objects. Per DATA-MODEL.md sections 5.1 to 5.6 and the
|
|
12
|
+
* `variables`, `montecarlo` and `withdrawalStrategy` field trees. */
|
|
13
|
+
export declare const SECTION_CONTAINERS: {
|
|
14
|
+
readonly income: "events";
|
|
15
|
+
readonly expenses: "events";
|
|
16
|
+
readonly accounts: "events";
|
|
17
|
+
readonly assets: "events";
|
|
18
|
+
readonly milestones: "array";
|
|
19
|
+
readonly variables: "object";
|
|
20
|
+
readonly withdrawalStrategy: "object";
|
|
21
|
+
readonly montecarlo: "object";
|
|
22
|
+
readonly priorities: "events";
|
|
23
|
+
};
|
|
5
24
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
25
|
+
* Index of the plan the identifier names, -1 when nothing matches. Throws
|
|
26
|
+
* INVALID_INPUT when several plans match the same way, so a read and the
|
|
27
|
+
* following write can never target different plans.
|
|
9
28
|
*/
|
|
10
|
-
export declare function
|
|
11
|
-
|
|
29
|
+
export declare function resolvePlanIndex(plans: unknown[], identifier: string): number;
|
|
30
|
+
/** The plan the identifier names, or null when nothing matches. Refuses
|
|
31
|
+
* ambiguous identifiers the way resolvePlanIndex does. */
|
|
32
|
+
export declare function resolvePlan(plans: unknown[], identifier: string): PlanObj | null;
|
|
12
33
|
export declare function planNotFoundError(plans: unknown[], identifier: string): ProjectionLabError;
|
|
13
34
|
export declare function requirePlans(data: {
|
|
14
35
|
plans?: unknown[];
|
|
@@ -24,14 +45,38 @@ export declare function planEventCounts(plan: unknown): {
|
|
|
24
45
|
expenseCount: number;
|
|
25
46
|
milestoneCount: number;
|
|
26
47
|
};
|
|
27
|
-
/**
|
|
28
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Index of the event the identifier names: exact id, then exact name
|
|
50
|
+
* (case-insensitive), then name substring. -1 when nothing matches. Throws
|
|
51
|
+
* INVALID_INPUT when several events match the same way.
|
|
52
|
+
*/
|
|
53
|
+
export declare function resolveEventIndex(events: unknown[], identifier: string): number;
|
|
29
54
|
export declare function eventLabel(event: unknown): string;
|
|
30
55
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
56
|
+
* Key events for diffing by label plus occurrence number, so events sharing a
|
|
57
|
+
* name each keep their own entry. Ids are not used because a replacement
|
|
58
|
+
* section may omit them. The display name shows the occurrence only when the
|
|
59
|
+
* label repeats.
|
|
60
|
+
*/
|
|
61
|
+
export declare function keyEvents(events: unknown[]): Map<string, {
|
|
62
|
+
name: string;
|
|
63
|
+
event: unknown;
|
|
64
|
+
}>;
|
|
65
|
+
/** The container kind of a section value, or null when it is not a usable
|
|
66
|
+
* container (null, undefined, a primitive, or a non-plain object). */
|
|
67
|
+
export declare function sectionContainer(value: unknown): "array" | "events" | "object" | null;
|
|
68
|
+
/**
|
|
69
|
+
* Refuse sectionData whose container shape does not match the section named.
|
|
70
|
+
* The expected container comes from the section's name, not from what the
|
|
71
|
+
* plan currently stores, so an event section held as {} still accepts the
|
|
72
|
+
* correct { events: [...] } replacement. One exception: when the stored
|
|
73
|
+
* section is itself a usable container that contradicts the table, a
|
|
74
|
+
* replacement in the stored shape is accepted too, so the tool never refuses
|
|
75
|
+
* to write back what it just read. An omitted value would delete the
|
|
76
|
+
* section, and a container of the wrong kind would store data ProjectionLab
|
|
77
|
+
* cannot read.
|
|
34
78
|
*/
|
|
79
|
+
export declare function assertSectionShape(section: string, planName: string, current: unknown, incoming: unknown): void;
|
|
35
80
|
/**
|
|
36
81
|
* True when every value in `expected` is present and equal in `actual`.
|
|
37
82
|
* Objects may carry extra keys; arrays must match in length and order.
|
|
@@ -49,5 +94,23 @@ export declare function verifySectionApplied(submitted: unknown, actual: unknown
|
|
|
49
94
|
ok: boolean;
|
|
50
95
|
reason?: string;
|
|
51
96
|
};
|
|
97
|
+
/**
|
|
98
|
+
* True when `after` still holds every input value of `before`.
|
|
99
|
+
* `lastUpdated`, `simKey`, and `meta` are left out: ProjectionLab saves every
|
|
100
|
+
* plan on array replacement and re-runs simulations, which may re-stamp
|
|
101
|
+
* lastUpdated and update computed fields (simKey, meta.runKey,
|
|
102
|
+
* meta.yearlySummary) on untouched plans.
|
|
103
|
+
*/
|
|
104
|
+
export declare function samePlanContent(before: PlanObj, after: PlanObj): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Checks on a plan a write did not target, beyond its input content: its
|
|
107
|
+
* `lastUpdated` stamp may move forward (restorePlans saves every plan) but may
|
|
108
|
+
* not go backwards or disappear, and a `simKey` it carried may change value
|
|
109
|
+
* (ProjectionLab re-runs the plan open in the tab) but may not disappear. A
|
|
110
|
+
* plan that carried no simKey, or a stamp that is not a number on either side,
|
|
111
|
+
* is not reported, so absent fields never read as a change. `label` names the
|
|
112
|
+
* plan in each problem, for example `original plan p1`.
|
|
113
|
+
*/
|
|
114
|
+
export declare function keptPlanIdentityProblems(label: string, before: PlanObj, after: PlanObj): string[];
|
|
52
115
|
/** Field-by-field diff of two objects; values rendered as "from → to". */
|
|
53
116
|
export declare function diffFields(current: unknown, incoming: unknown): Record<string, string>;
|
package/dist/plans.js
CHANGED
|
@@ -10,34 +10,93 @@ export const PLAN_SECTIONS = [
|
|
|
10
10
|
"montecarlo",
|
|
11
11
|
"priorities",
|
|
12
12
|
];
|
|
13
|
+
/** Sections whose members are events addressed by id or name; the
|
|
14
|
+
* settings-like sections use projectionlab_update_plan_settings instead. */
|
|
15
|
+
export const EVENT_SECTIONS = [
|
|
16
|
+
"income",
|
|
17
|
+
"expenses",
|
|
18
|
+
"accounts",
|
|
19
|
+
"assets",
|
|
20
|
+
"milestones",
|
|
21
|
+
"priorities",
|
|
22
|
+
];
|
|
23
|
+
/** The container each section holds, known from its name alone and not from
|
|
24
|
+
* what a plan happens to store: milestones is a direct array, the event
|
|
25
|
+
* sections are objects with an events array, and the settings-like sections
|
|
26
|
+
* are plain objects. Per DATA-MODEL.md sections 5.1 to 5.6 and the
|
|
27
|
+
* `variables`, `montecarlo` and `withdrawalStrategy` field trees. */
|
|
28
|
+
export const SECTION_CONTAINERS = {
|
|
29
|
+
income: "events",
|
|
30
|
+
expenses: "events",
|
|
31
|
+
accounts: "events",
|
|
32
|
+
assets: "events",
|
|
33
|
+
milestones: "array",
|
|
34
|
+
variables: "object",
|
|
35
|
+
withdrawalStrategy: "object",
|
|
36
|
+
montecarlo: "object",
|
|
37
|
+
priorities: "events",
|
|
38
|
+
};
|
|
13
39
|
/**
|
|
14
|
-
*
|
|
15
|
-
* (case-insensitive), then
|
|
16
|
-
*
|
|
40
|
+
* Resolve one identifier against a list of { id, name } items. Match
|
|
41
|
+
* priority: exact ID, then exact name (case-insensitive), then name
|
|
42
|
+
* substring. Anything beyond a single match at the level reached is
|
|
43
|
+
* ambiguous: picking the first would silently target whichever item happens
|
|
44
|
+
* to come first, so the caller must refuse and ask for an exact name or ID.
|
|
17
45
|
*/
|
|
18
|
-
|
|
46
|
+
function matchOne(items, identifier) {
|
|
19
47
|
if (!identifier.trim())
|
|
20
|
-
return
|
|
48
|
+
return { kind: "none" };
|
|
21
49
|
const lower = identifier.toLowerCase();
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
for (let i = 0; i <
|
|
25
|
-
const
|
|
26
|
-
if (!
|
|
50
|
+
const exact = [];
|
|
51
|
+
const partial = [];
|
|
52
|
+
for (let i = 0; i < items.length; i++) {
|
|
53
|
+
const item = items[i];
|
|
54
|
+
if (!item || typeof item !== "object")
|
|
27
55
|
continue;
|
|
28
|
-
const obj =
|
|
56
|
+
const obj = item;
|
|
29
57
|
if (String(obj.id ?? "") === identifier)
|
|
30
|
-
return i;
|
|
58
|
+
return { kind: "found", index: i };
|
|
31
59
|
const name = String(obj.name ?? "").toLowerCase();
|
|
32
|
-
if (name === lower
|
|
33
|
-
|
|
34
|
-
else if (name.includes(lower)
|
|
35
|
-
partial
|
|
60
|
+
if (name === lower)
|
|
61
|
+
exact.push(i);
|
|
62
|
+
else if (name.includes(lower))
|
|
63
|
+
partial.push(i);
|
|
64
|
+
}
|
|
65
|
+
if (exact.length === 1)
|
|
66
|
+
return { kind: "found", index: exact[0] };
|
|
67
|
+
if (exact.length > 1)
|
|
68
|
+
return { kind: "ambiguous", indexes: exact };
|
|
69
|
+
if (partial.length === 1)
|
|
70
|
+
return { kind: "found", index: partial[0] };
|
|
71
|
+
if (partial.length > 1)
|
|
72
|
+
return { kind: "ambiguous", indexes: partial };
|
|
73
|
+
return { kind: "none" };
|
|
74
|
+
}
|
|
75
|
+
function ambiguousMatchError(kind, identifier, items, indexes) {
|
|
76
|
+
const candidates = indexes
|
|
77
|
+
.map((i) => {
|
|
78
|
+
const obj = items[i];
|
|
79
|
+
return ` - "${obj.name ?? "(unnamed)"}" (ID: ${obj.id ?? "?"})`;
|
|
80
|
+
})
|
|
81
|
+
.join("\n");
|
|
82
|
+
return new ProjectionLabError("INVALID_INPUT", `More than one ${kind} matches "${identifier}"`, `Candidates:\n${candidates}\nSupply an exact name or one of the IDs`);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Index of the plan the identifier names, -1 when nothing matches. Throws
|
|
86
|
+
* INVALID_INPUT when several plans match the same way, so a read and the
|
|
87
|
+
* following write can never target different plans.
|
|
88
|
+
*/
|
|
89
|
+
export function resolvePlanIndex(plans, identifier) {
|
|
90
|
+
const outcome = matchOne(plans, identifier);
|
|
91
|
+
if (outcome.kind === "ambiguous") {
|
|
92
|
+
throw ambiguousMatchError("plan", identifier, plans, outcome.indexes);
|
|
36
93
|
}
|
|
37
|
-
return
|
|
94
|
+
return outcome.kind === "found" ? outcome.index : -1;
|
|
38
95
|
}
|
|
39
|
-
|
|
40
|
-
|
|
96
|
+
/** The plan the identifier names, or null when nothing matches. Refuses
|
|
97
|
+
* ambiguous identifiers the way resolvePlanIndex does. */
|
|
98
|
+
export function resolvePlan(plans, identifier) {
|
|
99
|
+
const i = resolvePlanIndex(plans, identifier);
|
|
41
100
|
return i === -1 ? null : plans[i];
|
|
42
101
|
}
|
|
43
102
|
export function planNotFoundError(plans, identifier) {
|
|
@@ -87,27 +146,17 @@ function eventId(event) {
|
|
|
87
146
|
}
|
|
88
147
|
return undefined;
|
|
89
148
|
}
|
|
90
|
-
/**
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (!e || typeof e !== "object")
|
|
100
|
-
continue;
|
|
101
|
-
const obj = e;
|
|
102
|
-
if (String(obj.id ?? "") === identifier)
|
|
103
|
-
return i;
|
|
104
|
-
const name = String(obj.name ?? "").toLowerCase();
|
|
105
|
-
if (name === lower && exactName === -1)
|
|
106
|
-
exactName = i;
|
|
107
|
-
else if (name.includes(lower) && partial === -1)
|
|
108
|
-
partial = i;
|
|
149
|
+
/**
|
|
150
|
+
* Index of the event the identifier names: exact id, then exact name
|
|
151
|
+
* (case-insensitive), then name substring. -1 when nothing matches. Throws
|
|
152
|
+
* INVALID_INPUT when several events match the same way.
|
|
153
|
+
*/
|
|
154
|
+
export function resolveEventIndex(events, identifier) {
|
|
155
|
+
const outcome = matchOne(events, identifier);
|
|
156
|
+
if (outcome.kind === "ambiguous") {
|
|
157
|
+
throw ambiguousMatchError("event", identifier, events, outcome.indexes);
|
|
109
158
|
}
|
|
110
|
-
return
|
|
159
|
+
return outcome.kind === "found" ? outcome.index : -1;
|
|
111
160
|
}
|
|
112
161
|
export function eventLabel(event) {
|
|
113
162
|
if (event && typeof event === "object") {
|
|
@@ -116,6 +165,30 @@ export function eventLabel(event) {
|
|
|
116
165
|
}
|
|
117
166
|
return "unnamed";
|
|
118
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Key events for diffing by label plus occurrence number, so events sharing a
|
|
170
|
+
* name each keep their own entry. Ids are not used because a replacement
|
|
171
|
+
* section may omit them. The display name shows the occurrence only when the
|
|
172
|
+
* label repeats.
|
|
173
|
+
*/
|
|
174
|
+
export function keyEvents(events) {
|
|
175
|
+
const labelCounts = new Map();
|
|
176
|
+
for (const e of events) {
|
|
177
|
+
const label = eventLabel(e);
|
|
178
|
+
labelCounts.set(label, (labelCounts.get(label) ?? 0) + 1);
|
|
179
|
+
}
|
|
180
|
+
const seen = new Map();
|
|
181
|
+
const keyed = new Map();
|
|
182
|
+
for (const e of events) {
|
|
183
|
+
const label = eventLabel(e);
|
|
184
|
+
const n = (seen.get(label) ?? 0) + 1;
|
|
185
|
+
seen.set(label, n);
|
|
186
|
+
const key = `${label}#${n}`;
|
|
187
|
+
const name = (labelCounts.get(label) ?? 0) > 1 ? `${label} #${n}` : label;
|
|
188
|
+
keyed.set(key, { name, event: e });
|
|
189
|
+
}
|
|
190
|
+
return keyed;
|
|
191
|
+
}
|
|
119
192
|
function sectionIsEventBased(section) {
|
|
120
193
|
if (Array.isArray(section))
|
|
121
194
|
return true;
|
|
@@ -123,11 +196,59 @@ function sectionIsEventBased(section) {
|
|
|
123
196
|
typeof section === "object" &&
|
|
124
197
|
Array.isArray(section.events));
|
|
125
198
|
}
|
|
199
|
+
function isPlainSectionObject(value) {
|
|
200
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
201
|
+
return false;
|
|
202
|
+
const prototype = Object.getPrototypeOf(value);
|
|
203
|
+
return prototype === Object.prototype || prototype === null;
|
|
204
|
+
}
|
|
205
|
+
/** The container kind of a section value, or null when it is not a usable
|
|
206
|
+
* container (null, undefined, a primitive, or a non-plain object). */
|
|
207
|
+
export function sectionContainer(value) {
|
|
208
|
+
if (Array.isArray(value))
|
|
209
|
+
return "array";
|
|
210
|
+
if (isPlainSectionObject(value)) {
|
|
211
|
+
return Array.isArray(value.events) ? "events" : "object";
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
const CONTAINER_WORDS = {
|
|
216
|
+
array: "an array of events",
|
|
217
|
+
events: "an object with an events array",
|
|
218
|
+
object: "an object",
|
|
219
|
+
};
|
|
126
220
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
221
|
+
* Refuse sectionData whose container shape does not match the section named.
|
|
222
|
+
* The expected container comes from the section's name, not from what the
|
|
223
|
+
* plan currently stores, so an event section held as {} still accepts the
|
|
224
|
+
* correct { events: [...] } replacement. One exception: when the stored
|
|
225
|
+
* section is itself a usable container that contradicts the table, a
|
|
226
|
+
* replacement in the stored shape is accepted too, so the tool never refuses
|
|
227
|
+
* to write back what it just read. An omitted value would delete the
|
|
228
|
+
* section, and a container of the wrong kind would store data ProjectionLab
|
|
229
|
+
* cannot read.
|
|
130
230
|
*/
|
|
231
|
+
export function assertSectionShape(section, planName, current, incoming) {
|
|
232
|
+
const incomingShape = sectionContainer(incoming);
|
|
233
|
+
if (incomingShape === null) {
|
|
234
|
+
const got = incoming === undefined
|
|
235
|
+
? "missing"
|
|
236
|
+
: incoming === null
|
|
237
|
+
? "null"
|
|
238
|
+
: Array.isArray(incoming)
|
|
239
|
+
? "an array"
|
|
240
|
+
: typeof incoming;
|
|
241
|
+
throw new ProjectionLabError("INVALID_INPUT", `sectionData for "${section}" of plan "${planName}" is ${got}; a section is ${CONTAINER_WORDS.array} or ${CONTAINER_WORDS.object}`, "Send the complete section in the same shape projectionlab_get_plan_section returns: an array for milestones, an object with an events array for event sections, an object for settings-like sections");
|
|
242
|
+
}
|
|
243
|
+
const expected = SECTION_CONTAINERS[section];
|
|
244
|
+
const accepted = new Set([expected]);
|
|
245
|
+
const currentShape = sectionContainer(current);
|
|
246
|
+
if (currentShape !== null && currentShape !== expected)
|
|
247
|
+
accepted.add(currentShape);
|
|
248
|
+
if (!accepted.has(incomingShape)) {
|
|
249
|
+
throw new ProjectionLabError("INVALID_INPUT", `sectionData for "${section}" of plan "${planName}" must be ${CONTAINER_WORDS[expected]}, which is how this section is stored; received ${CONTAINER_WORDS[incomingShape]}`, `Read the section with projectionlab_get_plan_section and send the replacement in the container shape the section name calls for`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
131
252
|
/**
|
|
132
253
|
* True when every value in `expected` is present and equal in `actual`.
|
|
133
254
|
* Objects may carry extra keys; arrays must match in length and order.
|
|
@@ -191,6 +312,49 @@ export function verifySectionApplied(submitted, actual) {
|
|
|
191
312
|
}
|
|
192
313
|
return { ok: true };
|
|
193
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* True when `after` still holds every input value of `before`.
|
|
317
|
+
* `lastUpdated`, `simKey`, and `meta` are left out: ProjectionLab saves every
|
|
318
|
+
* plan on array replacement and re-runs simulations, which may re-stamp
|
|
319
|
+
* lastUpdated and update computed fields (simKey, meta.runKey,
|
|
320
|
+
* meta.yearlySummary) on untouched plans.
|
|
321
|
+
*/
|
|
322
|
+
export function samePlanContent(before, after) {
|
|
323
|
+
const { lastUpdated: _stamp, simKey: _key, meta: _meta, ...content } = before;
|
|
324
|
+
return containsSubset(content, after);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Checks on a plan a write did not target, beyond its input content: its
|
|
328
|
+
* `lastUpdated` stamp may move forward (restorePlans saves every plan) but may
|
|
329
|
+
* not go backwards or disappear, and a `simKey` it carried may change value
|
|
330
|
+
* (ProjectionLab re-runs the plan open in the tab) but may not disappear. A
|
|
331
|
+
* plan that carried no simKey, or a stamp that is not a number on either side,
|
|
332
|
+
* is not reported, so absent fields never read as a change. `label` names the
|
|
333
|
+
* plan in each problem, for example `original plan p1`.
|
|
334
|
+
*/
|
|
335
|
+
export function keptPlanIdentityProblems(label, before, after) {
|
|
336
|
+
const problems = [];
|
|
337
|
+
const simKey = before.simKey;
|
|
338
|
+
if (simKey !== undefined && simKey !== null) {
|
|
339
|
+
const current = after.simKey;
|
|
340
|
+
if (current === undefined || current === null) {
|
|
341
|
+
problems.push(`${label} lost its simKey`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const was = before.lastUpdated;
|
|
345
|
+
const now = after.lastUpdated;
|
|
346
|
+
if (was !== undefined && was !== null) {
|
|
347
|
+
if (now === undefined || now === null) {
|
|
348
|
+
problems.push(`${label} lost its lastUpdated`);
|
|
349
|
+
}
|
|
350
|
+
else if (typeof was === "number" &&
|
|
351
|
+
typeof now === "number" &&
|
|
352
|
+
now < was) {
|
|
353
|
+
problems.push(`${label} lastUpdated moved backwards from ${was} to ${now}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return problems;
|
|
357
|
+
}
|
|
194
358
|
/** Field-by-field diff of two objects; values rendered as "from → to". */
|
|
195
359
|
export function diffFields(current, incoming) {
|
|
196
360
|
const cur = (current ?? {});
|