@memberjunction/metadata-sync 5.51.1 → 5.51.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/README.md +21 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/config-manager.d.ts +11 -0
- package/dist/lib/config-manager.d.ts.map +1 -1
- package/dist/lib/config-manager.js +23 -0
- package/dist/lib/config-manager.js.map +1 -1
- package/dist/lib/dynamic-packages/discover.d.ts +38 -0
- package/dist/lib/dynamic-packages/discover.d.ts.map +1 -0
- package/dist/lib/dynamic-packages/discover.js +178 -0
- package/dist/lib/dynamic-packages/discover.js.map +1 -0
- package/dist/lib/dynamic-packages/host-import.d.ts +43 -0
- package/dist/lib/dynamic-packages/host-import.d.ts.map +1 -0
- package/dist/lib/dynamic-packages/host-import.js +170 -0
- package/dist/lib/dynamic-packages/host-import.js.map +1 -0
- package/dist/lib/dynamic-packages/index.d.ts +42 -0
- package/dist/lib/dynamic-packages/index.d.ts.map +1 -0
- package/dist/lib/dynamic-packages/index.js +6 -0
- package/dist/lib/dynamic-packages/index.js.map +1 -0
- package/dist/lib/dynamic-packages/loader.d.ts +114 -0
- package/dist/lib/dynamic-packages/loader.d.ts.map +1 -0
- package/dist/lib/dynamic-packages/loader.js +404 -0
- package/dist/lib/dynamic-packages/loader.js.map +1 -0
- package/dist/lib/dynamic-packages/mode.d.ts +27 -0
- package/dist/lib/dynamic-packages/mode.d.ts.map +1 -0
- package/dist/lib/dynamic-packages/mode.js +60 -0
- package/dist/lib/dynamic-packages/mode.js.map +1 -0
- package/dist/lib/dynamic-packages/process-id.d.ts +54 -0
- package/dist/lib/dynamic-packages/process-id.d.ts.map +1 -0
- package/dist/lib/dynamic-packages/process-id.js +104 -0
- package/dist/lib/dynamic-packages/process-id.js.map +1 -0
- package/dist/lib/dynamic-packages/types.d.ts +153 -0
- package/dist/lib/dynamic-packages/types.d.ts.map +1 -0
- package/dist/lib/dynamic-packages/types.js +14 -0
- package/dist/lib/dynamic-packages/types.js.map +1 -0
- package/dist/lib/entity-subclass-guard.d.ts +13 -0
- package/dist/lib/entity-subclass-guard.d.ts.map +1 -0
- package/dist/lib/entity-subclass-guard.js +50 -0
- package/dist/lib/entity-subclass-guard.js.map +1 -0
- package/dist/lib/file-write-batch.d.ts +0 -4
- package/dist/lib/file-write-batch.d.ts.map +1 -1
- package/dist/lib/file-write-batch.js +10 -8
- package/dist/lib/file-write-batch.js.map +1 -1
- package/dist/lib/record-primary-key.d.ts +35 -0
- package/dist/lib/record-primary-key.d.ts.map +1 -0
- package/dist/lib/record-primary-key.js +44 -0
- package/dist/lib/record-primary-key.js.map +1 -0
- package/dist/lib/sync-dynamic-packages.d.ts +23 -0
- package/dist/lib/sync-dynamic-packages.d.ts.map +1 -0
- package/dist/lib/sync-dynamic-packages.js +64 -0
- package/dist/lib/sync-dynamic-packages.js.map +1 -0
- package/dist/lib/sync-engine.d.ts.map +1 -1
- package/dist/lib/sync-engine.js +7 -0
- package/dist/lib/sync-engine.js.map +1 -1
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +8 -2
- package/dist/plugins/index.js.map +1 -1
- package/dist/services/PullService.d.ts +0 -1
- package/dist/services/PullService.d.ts.map +1 -1
- package/dist/services/PullService.js +15 -19
- package/dist/services/PullService.js.map +1 -1
- package/dist/services/PushService.d.ts.map +1 -1
- package/dist/services/PushService.js +8 -0
- package/dist/services/PushService.js.map +1 -1
- package/package.json +13 -13
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mode resolution — the same precedence shape as `ResolveStartupMode` in @memberjunction/core
|
|
3
|
+
* (env var > programmatic option > config > default), so an operator who already knows
|
|
4
|
+
* `MJ_STARTUP_MODE=task npx mj sync push` can reach for `MJ_DYNAMIC_PACKAGES=none` the same way.
|
|
5
|
+
*/
|
|
6
|
+
import { ResolveMostSpecific } from './process-id.js';
|
|
7
|
+
/**
|
|
8
|
+
* Environment variable for a per-invocation override, e.g.
|
|
9
|
+
* `MJ_DYNAMIC_PACKAGES=none npx mj sync push` to push with generic BaseEntity only.
|
|
10
|
+
*/
|
|
11
|
+
export const DYNAMIC_PACKAGES_MODE_ENV_VAR = 'MJ_DYNAMIC_PACKAGES';
|
|
12
|
+
const MODES = ['load', 'none'];
|
|
13
|
+
/** Accepts the canonical values plus the obvious spellings people will try. */
|
|
14
|
+
function parseMode(value) {
|
|
15
|
+
if (!value) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const normalized = value.trim().toLowerCase();
|
|
19
|
+
if (normalized === 'off' || normalized === 'skip' || normalized === 'false' || normalized === '0') {
|
|
20
|
+
return 'none';
|
|
21
|
+
}
|
|
22
|
+
if (normalized === 'on' || normalized === 'true' || normalized === '1' || normalized === 'full') {
|
|
23
|
+
return 'load';
|
|
24
|
+
}
|
|
25
|
+
return MODES.find((m) => m === normalized);
|
|
26
|
+
}
|
|
27
|
+
function readEnv() {
|
|
28
|
+
if (typeof process === 'undefined' || !process.env) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
return process.env[DYNAMIC_PACKAGES_MODE_ENV_VAR];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Highest wins:
|
|
35
|
+
* 1. `MJ_DYNAMIC_PACKAGES` env var
|
|
36
|
+
* 2. `option` — programmatic override from the entry point (e.g. a CLI flag)
|
|
37
|
+
* 3. `policy` — `dynamicPackages.policy` from mj.config.cjs, most specific process key
|
|
38
|
+
* 4. `'load'`
|
|
39
|
+
*
|
|
40
|
+
* An invalid env/policy value never crashes a process; it is reported and falls through.
|
|
41
|
+
*/
|
|
42
|
+
export function ResolveDynamicPackagesMode(args) {
|
|
43
|
+
const envRaw = readEnv();
|
|
44
|
+
const envMode = parseMode(envRaw);
|
|
45
|
+
if (envMode) {
|
|
46
|
+
return { mode: envMode, source: 'env' };
|
|
47
|
+
}
|
|
48
|
+
const ignoredInvalid = envRaw && !envMode ? `${DYNAMIC_PACKAGES_MODE_ENV_VAR}='${envRaw}'` : undefined;
|
|
49
|
+
if (args.option) {
|
|
50
|
+
return { mode: args.option, source: 'option', ignoredInvalid };
|
|
51
|
+
}
|
|
52
|
+
const policyRaw = ResolveMostSpecific(args.processId, args.policy);
|
|
53
|
+
const policyMode = parseMode(policyRaw);
|
|
54
|
+
if (policyMode) {
|
|
55
|
+
return { mode: policyMode, source: 'policy', ignoredInvalid };
|
|
56
|
+
}
|
|
57
|
+
const policyInvalid = policyRaw && !policyMode ? `dynamicPackages.policy value '${policyRaw}'` : undefined;
|
|
58
|
+
return { mode: 'load', source: 'default', ignoredInvalid: ignoredInvalid ?? policyInvalid };
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mode.js","sourceRoot":"","sources":["../../../src/lib/dynamic-packages/mode.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAGtD;;;GAGG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,qBAAqB,CAAC;AAEnE,MAAM,KAAK,GAAmC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE/D,+EAA+E;AAC/E,SAAS,SAAS,CAAC,KAAgC;IAC/C,IAAI,CAAC,KAAK,EAAE,CAAC;QACT,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC9C,IAAI,UAAU,KAAK,KAAK,IAAI,UAAU,KAAK,MAAM,IAAI,UAAU,KAAK,OAAO,IAAI,UAAU,KAAK,GAAG,EAAE,CAAC;QAChG,OAAO,MAAM,CAAC;IAClB,CAAC;IACD,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,KAAK,MAAM,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,MAAM,EAAE,CAAC;QAC9F,OAAO,MAAM,CAAC;IAClB,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC;AAC/C,CAAC;AAED,SAAS,OAAO;IACZ,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QACjD,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,OAAO,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;AACtD,CAAC;AASD;;;;;;;;GAQG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAI1C;IACG,MAAM,MAAM,GAAG,OAAO,EAAE,CAAC;IACzB,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,OAAO,EAAE,CAAC;QACV,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC5C,CAAC;IACD,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,6BAA6B,KAAK,MAAM,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IAEvG,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;IACnE,CAAC;IAED,MAAM,SAAS,GAAG,mBAAmB,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACnE,MAAM,UAAU,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IACxC,IAAI,UAAU,EAAE,CAAC;QACb,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;IAClE,CAAC;IACD,MAAM,aAAa,GAAG,SAAS,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,iCAAiC,SAAS,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IAE3G,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,cAAc,IAAI,aAAa,EAAE,CAAC;AAChG,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process identity and prefix matching.
|
|
3
|
+
*
|
|
4
|
+
* Process IDs are hierarchical, colon-separated, lowercase strings: `mjapi`, `cli`,
|
|
5
|
+
* `cli:sync`, `cli:sync:push`, `mcp`. A pattern matches an ID when it equals the ID or names
|
|
6
|
+
* one of its ancestor segments — `cli:sync` matches `cli:sync:push` but not `cli:syncother`
|
|
7
|
+
* or `cli:migrate`. This is what lets a config author scope a package to "all of the CLI",
|
|
8
|
+
* "just mj sync", or exactly one command with the same field.
|
|
9
|
+
*/
|
|
10
|
+
/** Wildcard pattern that matches every process. */
|
|
11
|
+
export declare const ANY_PROCESS = "*";
|
|
12
|
+
/** Lowercases, trims, and collapses whitespace around the colon separators. */
|
|
13
|
+
export declare function NormalizeProcessId(value: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Builds a CLI process ID from an oclif command ID. oclif reports IDs as either `sync:push`
|
|
16
|
+
* or `sync push` (with `topicSeparator: ' '`); both become `cli:sync:push`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function CliProcessId(commandId: string | undefined | null): string;
|
|
19
|
+
/** True when `pattern` equals `processId` or is an ancestor prefix of it (segment-aware). */
|
|
20
|
+
export declare function ProcessIdMatches(processId: string, pattern: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Evaluates an entry's `Processes` / `ExcludeProcesses` against a process ID.
|
|
23
|
+
* No `Processes` (or an empty list) means "everywhere"; `ExcludeProcesses` is applied after.
|
|
24
|
+
*/
|
|
25
|
+
export declare function MatchesProcess(processId: string, filter: {
|
|
26
|
+
Processes?: string[] | null;
|
|
27
|
+
ExcludeProcesses?: string[] | null;
|
|
28
|
+
} | null | undefined): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Picks the value of the most specific key in `map` that matches `processId`.
|
|
31
|
+
* `'cli:sync'` beats `'cli'` beats `'*'`. Returns `undefined` when nothing matches.
|
|
32
|
+
*/
|
|
33
|
+
export declare function ResolveMostSpecific<T>(processId: string, map: Record<string, T> | null | undefined): T | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Environment variable through which an OUTER host tells nested hosts which process they are
|
|
36
|
+
* running inside. The `mj` CLI's prerun hook sets it to its own ID (`cli:ai:agents:run`) before
|
|
37
|
+
* the command imports `@memberjunction/ai-cli` or `@memberjunction/testing-cli`; those packages'
|
|
38
|
+
* provider bootstraps then evaluate `Processes` / `ExcludeProcesses` / `policy` under the SAME
|
|
39
|
+
* ID instead of their standalone `ai-cli` / `testing-cli` identity. Without it, an entry an
|
|
40
|
+
* operator excluded from `cli` would be loaded by the nested host a moment later.
|
|
41
|
+
*/
|
|
42
|
+
export declare const DYNAMIC_PACKAGES_PROCESS_ENV_VAR = "MJ_DYNAMIC_PACKAGES_PROCESS";
|
|
43
|
+
/**
|
|
44
|
+
* The process ID a host should load under: the one an enclosing host published through
|
|
45
|
+
* {@link DYNAMIC_PACKAGES_PROCESS_ENV_VAR} when there is one, else the host's own default.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* // packages/AI/AICLI — `mj-ai` standalone loads as 'ai-cli'; under `mj ai …` it inherits 'cli:ai:…'
|
|
50
|
+
* await LoadDynamicPackages({ processId: EffectiveProcessId('ai-cli'), ... });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function EffectiveProcessId(hostDefault: string): string;
|
|
54
|
+
//# sourceMappingURL=process-id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-id.d.ts","sourceRoot":"","sources":["../../../src/lib/dynamic-packages/process-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,mDAAmD;AACnD,eAAO,MAAM,WAAW,MAAM,CAAC;AAE/B,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQxD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAGzE;AAED,6FAA6F;AAC7F,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAU5E;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC1B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GAC/F,OAAO,CAUT;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,CAAC,GAAG,SAAS,CAkBlH;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,gCAAgC,gCAAgC,CAAC;AAE9E;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAI9D"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process identity and prefix matching.
|
|
3
|
+
*
|
|
4
|
+
* Process IDs are hierarchical, colon-separated, lowercase strings: `mjapi`, `cli`,
|
|
5
|
+
* `cli:sync`, `cli:sync:push`, `mcp`. A pattern matches an ID when it equals the ID or names
|
|
6
|
+
* one of its ancestor segments — `cli:sync` matches `cli:sync:push` but not `cli:syncother`
|
|
7
|
+
* or `cli:migrate`. This is what lets a config author scope a package to "all of the CLI",
|
|
8
|
+
* "just mj sync", or exactly one command with the same field.
|
|
9
|
+
*/
|
|
10
|
+
/** Wildcard pattern that matches every process. */
|
|
11
|
+
export const ANY_PROCESS = '*';
|
|
12
|
+
/** Lowercases, trims, and collapses whitespace around the colon separators. */
|
|
13
|
+
export function NormalizeProcessId(value) {
|
|
14
|
+
return value
|
|
15
|
+
.trim()
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.split(':')
|
|
18
|
+
.map((segment) => segment.trim())
|
|
19
|
+
.filter((segment) => segment.length > 0)
|
|
20
|
+
.join(':');
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Builds a CLI process ID from an oclif command ID. oclif reports IDs as either `sync:push`
|
|
24
|
+
* or `sync push` (with `topicSeparator: ' '`); both become `cli:sync:push`.
|
|
25
|
+
*/
|
|
26
|
+
export function CliProcessId(commandId) {
|
|
27
|
+
const normalized = NormalizeProcessId((commandId ?? '').replace(/\s+/g, ':'));
|
|
28
|
+
return normalized.length > 0 ? `cli:${normalized}` : 'cli';
|
|
29
|
+
}
|
|
30
|
+
/** True when `pattern` equals `processId` or is an ancestor prefix of it (segment-aware). */
|
|
31
|
+
export function ProcessIdMatches(processId, pattern) {
|
|
32
|
+
const id = NormalizeProcessId(processId);
|
|
33
|
+
const pat = NormalizeProcessId(pattern);
|
|
34
|
+
if (pat === ANY_PROCESS) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (pat.length === 0 || id.length === 0) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return id === pat || id.startsWith(`${pat}:`);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Evaluates an entry's `Processes` / `ExcludeProcesses` against a process ID.
|
|
44
|
+
* No `Processes` (or an empty list) means "everywhere"; `ExcludeProcesses` is applied after.
|
|
45
|
+
*/
|
|
46
|
+
export function MatchesProcess(processId, filter) {
|
|
47
|
+
const includes = (filter?.Processes ?? []).filter((p) => typeof p === 'string' && p.trim().length > 0);
|
|
48
|
+
const excludes = (filter?.ExcludeProcesses ?? []).filter((p) => typeof p === 'string' && p.trim().length > 0);
|
|
49
|
+
if (includes.length > 0 && !includes.some((p) => ProcessIdMatches(processId, p))) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (excludes.some((p) => ProcessIdMatches(processId, p))) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Picks the value of the most specific key in `map` that matches `processId`.
|
|
59
|
+
* `'cli:sync'` beats `'cli'` beats `'*'`. Returns `undefined` when nothing matches.
|
|
60
|
+
*/
|
|
61
|
+
export function ResolveMostSpecific(processId, map) {
|
|
62
|
+
if (!map) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
let bestKey;
|
|
66
|
+
let bestDepth = -1;
|
|
67
|
+
for (const key of Object.keys(map)) {
|
|
68
|
+
if (!ProcessIdMatches(processId, key)) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const normalized = NormalizeProcessId(key);
|
|
72
|
+
const depth = normalized === ANY_PROCESS ? 0 : normalized.split(':').length;
|
|
73
|
+
if (depth > bestDepth) {
|
|
74
|
+
bestDepth = depth;
|
|
75
|
+
bestKey = key;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return bestKey === undefined ? undefined : map[bestKey];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Environment variable through which an OUTER host tells nested hosts which process they are
|
|
82
|
+
* running inside. The `mj` CLI's prerun hook sets it to its own ID (`cli:ai:agents:run`) before
|
|
83
|
+
* the command imports `@memberjunction/ai-cli` or `@memberjunction/testing-cli`; those packages'
|
|
84
|
+
* provider bootstraps then evaluate `Processes` / `ExcludeProcesses` / `policy` under the SAME
|
|
85
|
+
* ID instead of their standalone `ai-cli` / `testing-cli` identity. Without it, an entry an
|
|
86
|
+
* operator excluded from `cli` would be loaded by the nested host a moment later.
|
|
87
|
+
*/
|
|
88
|
+
export const DYNAMIC_PACKAGES_PROCESS_ENV_VAR = 'MJ_DYNAMIC_PACKAGES_PROCESS';
|
|
89
|
+
/**
|
|
90
|
+
* The process ID a host should load under: the one an enclosing host published through
|
|
91
|
+
* {@link DYNAMIC_PACKAGES_PROCESS_ENV_VAR} when there is one, else the host's own default.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* // packages/AI/AICLI — `mj-ai` standalone loads as 'ai-cli'; under `mj ai …` it inherits 'cli:ai:…'
|
|
96
|
+
* await LoadDynamicPackages({ processId: EffectiveProcessId('ai-cli'), ... });
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function EffectiveProcessId(hostDefault) {
|
|
100
|
+
const fromEnv = typeof process !== 'undefined' && process.env ? process.env[DYNAMIC_PACKAGES_PROCESS_ENV_VAR] : undefined;
|
|
101
|
+
const normalized = fromEnv ? NormalizeProcessId(fromEnv) : '';
|
|
102
|
+
return normalized.length > 0 ? normalized : NormalizeProcessId(hostDefault);
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=process-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-id.js","sourceRoot":"","sources":["../../../src/lib/dynamic-packages/process-id.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the process-agnostic dynamic package loader.
|
|
3
|
+
*
|
|
4
|
+
* A "dynamic package" is an npm package whose name is only known at runtime — it is named by
|
|
5
|
+
* configuration (`mj.config.cjs` `dynamicPackages.*[]`, written by `mj app install`; the
|
|
6
|
+
* host's `codeGeneration.packages`), or by an Open App's own `mj-app.json` when a process runs
|
|
7
|
+
* inside that app's repository. Importing one triggers its `@RegisterClass` decorators so the
|
|
8
|
+
* ClassFactory returns the app's entity/action/provider subclasses instead of base-class
|
|
9
|
+
* fallbacks. MJAPI has always done this at boot; every other MJ process (the CLI, MCP/A2A
|
|
10
|
+
* servers, test bootstraps, ad-hoc scripts) needs the same thing, which is why the loader lives
|
|
11
|
+
* in a package with no MJ runtime dependencies.
|
|
12
|
+
*/
|
|
13
|
+
/** Which package tier a process wants: server-side (Node) packages or client (browser) ones. */
|
|
14
|
+
export type DynamicPackageTier = 'server' | 'client';
|
|
15
|
+
/**
|
|
16
|
+
* Whether a process should load dynamic packages at all. `'none'` is the escape hatch for
|
|
17
|
+
* "raw" runs — restoring dumps, bulk ingestion, or diagnosing a package that breaks boot —
|
|
18
|
+
* where custom `Save()` logic and lifecycle hooks must NOT run.
|
|
19
|
+
*/
|
|
20
|
+
export type DynamicPackagesMode = 'load' | 'none';
|
|
21
|
+
/**
|
|
22
|
+
* Where the resolved mode came from, highest precedence first:
|
|
23
|
+
* `MJ_DYNAMIC_PACKAGES` env var > programmatic option > `dynamicPackages.policy` > default.
|
|
24
|
+
*/
|
|
25
|
+
export type DynamicPackagesModeSource = 'env' | 'option' | 'policy' | 'default';
|
|
26
|
+
/**
|
|
27
|
+
* One entry of `dynamicPackages.server[]` / `dynamicPackages.client[]` in mj.config.cjs.
|
|
28
|
+
* `PackageName`, `StartupExport`, `AppName` and `Enabled` are written by `mj app install`;
|
|
29
|
+
* `Processes` / `ExcludeProcesses` are hand-authored scoping (see {@link MatchesProcess}).
|
|
30
|
+
*/
|
|
31
|
+
export interface DynamicPackageEntry {
|
|
32
|
+
/** npm package name to import. */
|
|
33
|
+
PackageName: string;
|
|
34
|
+
/** Optional named export to invoke after import (a registration kicker). */
|
|
35
|
+
StartupExport?: string;
|
|
36
|
+
/** Open App name this package belongs to (for tracking / pruning). */
|
|
37
|
+
AppName?: string;
|
|
38
|
+
/** Whether this package should be loaded anywhere. Treated as enabled unless explicitly `false`. */
|
|
39
|
+
Enabled?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Process IDs (or prefixes) this entry loads in. Omitted or empty = every process that asks
|
|
42
|
+
* for the entry's tier. `['cli:sync']` loads for `cli:sync:push` and `cli:sync:pull` only;
|
|
43
|
+
* `['mjapi', 'cli']` loads in the API and in every CLI command.
|
|
44
|
+
*/
|
|
45
|
+
Processes?: string[];
|
|
46
|
+
/** Process IDs (or prefixes) this entry must NOT load in, evaluated after {@link Processes}. */
|
|
47
|
+
ExcludeProcesses?: string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The `dynamicPackages` section of mj.config.cjs as this loader reads it.
|
|
51
|
+
*/
|
|
52
|
+
export interface DynamicPackagesConfig {
|
|
53
|
+
server?: DynamicPackageEntry[];
|
|
54
|
+
client?: DynamicPackageEntry[];
|
|
55
|
+
/**
|
|
56
|
+
* Per-process on/off policy keyed by process ID or prefix, e.g. `{ 'cli:codegen': 'none' }`.
|
|
57
|
+
* The most specific matching key wins. Overridden by the `MJ_DYNAMIC_PACKAGES` env var and
|
|
58
|
+
* by a programmatic `mode` option.
|
|
59
|
+
*/
|
|
60
|
+
policy?: Record<string, DynamicPackagesMode>;
|
|
61
|
+
}
|
|
62
|
+
/** Where a loadable entry was discovered. */
|
|
63
|
+
export type DynamicPackageSource = 'config' | 'generated' | 'manifest';
|
|
64
|
+
/** Where a manifest-sourced package's workspace member lives on disk (the app's own repository). */
|
|
65
|
+
export interface WorkspaceHome {
|
|
66
|
+
/** Directory holding the app's `mj-app.json`. */
|
|
67
|
+
RepoDir: string;
|
|
68
|
+
/** `code.sourceDirectory` from the manifest (default `packages`), relative to {@link RepoDir}. */
|
|
69
|
+
SourceDirectory: string;
|
|
70
|
+
}
|
|
71
|
+
/** An entry the loader decided about, with its provenance. */
|
|
72
|
+
export interface DiscoveredDynamicPackage {
|
|
73
|
+
Entry: DynamicPackageEntry;
|
|
74
|
+
Source: DynamicPackageSource;
|
|
75
|
+
/**
|
|
76
|
+
* Present only for `'manifest'` entries: lets the loader fall back to importing the
|
|
77
|
+
* workspace member by path when no resolution anchor can see it (pnpm strict layout).
|
|
78
|
+
*/
|
|
79
|
+
WorkspaceHome?: WorkspaceHome;
|
|
80
|
+
}
|
|
81
|
+
/** A package the loader imported (and whose startup export, if any, it ran). */
|
|
82
|
+
export interface LoadedDynamicPackage extends DiscoveredDynamicPackage {
|
|
83
|
+
/** The imported module namespace — hosts read conventions like `RESOLVER_PATHS` off it. */
|
|
84
|
+
Module: Record<string, unknown>;
|
|
85
|
+
/**
|
|
86
|
+
* True when `StartupExport` named a function and it was invoked by THIS call. False when the
|
|
87
|
+
* entry has no startup export, the named export is missing, or the package was already loaded
|
|
88
|
+
* earlier in this process (the module is still returned; the hook is not run twice).
|
|
89
|
+
*/
|
|
90
|
+
RanStartupExport: boolean;
|
|
91
|
+
}
|
|
92
|
+
/** Why an entry was not loaded. */
|
|
93
|
+
export type DynamicPackageSkipReason = 'disabled' | 'process-filter' | 'mode-none' | 'duplicate';
|
|
94
|
+
export interface SkippedDynamicPackage extends DiscoveredDynamicPackage {
|
|
95
|
+
Reason: DynamicPackageSkipReason;
|
|
96
|
+
}
|
|
97
|
+
/** A package that resolved but failed while loading (its own error — never masked). */
|
|
98
|
+
export interface FailedDynamicPackage extends DiscoveredDynamicPackage {
|
|
99
|
+
Error: unknown;
|
|
100
|
+
}
|
|
101
|
+
/** Logger seam so each host routes loader output through its own channel (stdout, stderr, spinner). */
|
|
102
|
+
export interface DynamicPackagesLogger {
|
|
103
|
+
/** Normal progress ("Loaded …"). */
|
|
104
|
+
info(message: string): void;
|
|
105
|
+
/** Recoverable problems the operator must see (a package that threw while loading). */
|
|
106
|
+
warn(message: string, error?: unknown): void;
|
|
107
|
+
/** Detail that only matters when debugging (skips, filters, mode source). */
|
|
108
|
+
verbose?(message: string): void;
|
|
109
|
+
}
|
|
110
|
+
export interface LoadDynamicPackagesOptions {
|
|
111
|
+
/**
|
|
112
|
+
* Identity of the calling process, lowercase and colon-separated: `mjapi`, `cli:sync:push`,
|
|
113
|
+
* `mcp`, `a2a`, `integration-tests`. Entry `Processes` / `ExcludeProcesses` and
|
|
114
|
+
* `dynamicPackages.policy` match against it by exact ID or by prefix segment.
|
|
115
|
+
*/
|
|
116
|
+
processId: string;
|
|
117
|
+
/** Raw (un-validated) mj.config.cjs object. Zod-parsed configs usually strip `dynamicPackages`; pass the raw one. */
|
|
118
|
+
config?: Record<string, unknown> | null;
|
|
119
|
+
/** Absolute path of the mj.config.cjs the config came from — the primary resolution anchor. */
|
|
120
|
+
configFilePath?: string;
|
|
121
|
+
/** Which tier to load. Defaults to `'server'`. */
|
|
122
|
+
tier?: DynamicPackageTier;
|
|
123
|
+
/**
|
|
124
|
+
* Also load the host's generated packages named under `codeGeneration.packages`
|
|
125
|
+
* (entities / actions / graphqlResolvers for the server tier). Defaults to `true`.
|
|
126
|
+
*/
|
|
127
|
+
includeGeneratedPackages?: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Also discover packages from an Open App manifest (`mj-app.json`) found in
|
|
130
|
+
* {@link appManifestDir} — the "running inside the app's own repo" case, where the app's
|
|
131
|
+
* packages are workspace members but nothing has installed them into a host config.
|
|
132
|
+
* Defaults to `true`.
|
|
133
|
+
*/
|
|
134
|
+
discoverAppManifest?: boolean;
|
|
135
|
+
/** Directory holding `mj-app.json`. Defaults to the config file's directory, else `process.cwd()`. */
|
|
136
|
+
appManifestDir?: string;
|
|
137
|
+
/** Programmatic mode override (below the env var, above config policy). */
|
|
138
|
+
mode?: DynamicPackagesMode;
|
|
139
|
+
/** Output channel. Defaults to `console`. */
|
|
140
|
+
log?: DynamicPackagesLogger;
|
|
141
|
+
}
|
|
142
|
+
export interface DynamicPackagesReport {
|
|
143
|
+
ProcessId: string;
|
|
144
|
+
Tier: DynamicPackageTier;
|
|
145
|
+
Mode: DynamicPackagesMode;
|
|
146
|
+
ModeSource: DynamicPackagesModeSource;
|
|
147
|
+
Loaded: LoadedDynamicPackage[];
|
|
148
|
+
Skipped: SkippedDynamicPackage[];
|
|
149
|
+
/** Package names no anchor could resolve — expected before `npm install` or an unbuilt workspace member. */
|
|
150
|
+
NotFound: DiscoveredDynamicPackage[];
|
|
151
|
+
Failed: FailedDynamicPackage[];
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/dynamic-packages/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the process-agnostic dynamic package loader.
|
|
3
|
+
*
|
|
4
|
+
* A "dynamic package" is an npm package whose name is only known at runtime — it is named by
|
|
5
|
+
* configuration (`mj.config.cjs` `dynamicPackages.*[]`, written by `mj app install`; the
|
|
6
|
+
* host's `codeGeneration.packages`), or by an Open App's own `mj-app.json` when a process runs
|
|
7
|
+
* inside that app's repository. Importing one triggers its `@RegisterClass` decorators so the
|
|
8
|
+
* ClassFactory returns the app's entity/action/provider subclasses instead of base-class
|
|
9
|
+
* fallbacks. MJAPI has always done this at boot; every other MJ process (the CLI, MCP/A2A
|
|
10
|
+
* servers, test bootstraps, ad-hoc scripts) needs the same thing, which is why the loader lives
|
|
11
|
+
* in a package with no MJ runtime dependencies.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/lib/dynamic-packages/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface MissingEntitySubclassOptions {
|
|
2
|
+
dryRun?: boolean;
|
|
3
|
+
/** Which sync operation is about to use the entity; decides how the consequence is described. Default `'push'`. */
|
|
4
|
+
operation?: 'push' | 'pull';
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Returns a warning when no `BaseEntity` subclass is registered for `entityName` in this
|
|
8
|
+
* process, or `null` when one is. Each entity is reported once; later calls return `null`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function describeMissingEntitySubclass(entityName: string, options?: MissingEntitySubclassOptions): string | null;
|
|
11
|
+
/** Test seam: forget which entities have been reported. */
|
|
12
|
+
export declare function resetMissingEntitySubclassWarnings(): void;
|
|
13
|
+
//# sourceMappingURL=entity-subclass-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity-subclass-guard.d.ts","sourceRoot":"","sources":["../../src/lib/entity-subclass-guard.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,4BAA4B;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,mHAAmH;IACnH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,4BAAiC,GAAG,MAAM,GAAG,IAAI,CAkB3H;AAkBD,2DAA2D;AAC3D,wBAAgB,kCAAkC,IAAI,IAAI,CAEzD"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 5.x LTS port of MJ `next`'s guard (#4199 / PR #4201, plus the pull wording added alongside
|
|
3
|
+
* the #3415 pull fix).
|
|
4
|
+
*
|
|
5
|
+
* Detects the silent failure behind issue #4199: `Metadata.GetEntityObject` never fails when no
|
|
6
|
+
* subclass is registered for an entity — the ClassFactory hands back a plain `BaseEntity`, which
|
|
7
|
+
* saves fine through the generated stored procedures and simply skips every custom `Save()`
|
|
8
|
+
* override, validation rule and lifecycle hook the entity's real class carries. In a push that
|
|
9
|
+
* looks identical to success. This guard names it, once per entity per process.
|
|
10
|
+
*/
|
|
11
|
+
import { BaseEntity } from '@memberjunction/core';
|
|
12
|
+
import { MJGlobal } from '@memberjunction/global';
|
|
13
|
+
const warned = new Set();
|
|
14
|
+
/**
|
|
15
|
+
* Returns a warning when no `BaseEntity` subclass is registered for `entityName` in this
|
|
16
|
+
* process, or `null` when one is. Each entity is reported once; later calls return `null`.
|
|
17
|
+
*/
|
|
18
|
+
export function describeMissingEntitySubclass(entityName, options = {}) {
|
|
19
|
+
const key = entityName.trim().toLowerCase();
|
|
20
|
+
if (!key || warned.has(key)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const registration = MJGlobal.Instance.ClassFactory.GetRegistration(BaseEntity, entityName);
|
|
24
|
+
if (registration) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
warned.add(key);
|
|
28
|
+
return (`No entity subclass is registered for '${entityName}' in this process — ${describeConsequence(options)} ` +
|
|
29
|
+
`The class is loaded from the host's generated entities package (mj.config.cjs ` +
|
|
30
|
+
`codeGeneration.packages.entities) or, for an Open App entity, from the app's server package ` +
|
|
31
|
+
`(mj app install → dynamicPackages.server). Check that the package is installed and built, that its entry ` +
|
|
32
|
+
`is enabled and scoped to this process (Processes / ExcludeProcesses), and that this run was not started ` +
|
|
33
|
+
`with MJ_DYNAMIC_PACKAGES=none. Run with --verbose to see which packages were loaded.`);
|
|
34
|
+
}
|
|
35
|
+
/** What running without the entity's own class costs, for the operation about to run. */
|
|
36
|
+
function describeConsequence(options) {
|
|
37
|
+
if (options.operation === 'pull') {
|
|
38
|
+
const verb = options.dryRun ? 'would be' : 'are';
|
|
39
|
+
return (`records ${verb} read through the generic BaseEntity, so values the entity's own class computes ` +
|
|
40
|
+
`(its virtual properties) will be missing from the pulled files.`);
|
|
41
|
+
}
|
|
42
|
+
const verb = options.dryRun ? 'would be written' : 'will be written';
|
|
43
|
+
return (`records ${verb} with the generic BaseEntity, so any custom validation, Save() logic or lifecycle ` +
|
|
44
|
+
`hooks the entity's own class carries will NOT run.`);
|
|
45
|
+
}
|
|
46
|
+
/** Test seam: forget which entities have been reported. */
|
|
47
|
+
export function resetMissingEntitySubclassWarnings() {
|
|
48
|
+
warned.clear();
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=entity-subclass-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity-subclass-guard.js","sourceRoot":"","sources":["../../src/lib/entity-subclass-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;AAQjC;;;GAGG;AACH,MAAM,UAAU,6BAA6B,CAAC,UAAkB,EAAE,UAAwC,EAAE;IAC1G,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,CAAC,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,YAAY,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,eAAe,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAC5F,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChB,OAAO,CACL,yCAAyC,UAAU,uBAAuB,mBAAmB,CAAC,OAAO,CAAC,GAAG;QACzG,gFAAgF;QAChF,8FAA8F;QAC9F,2GAA2G;QAC3G,0GAA0G;QAC1G,sFAAsF,CACvF,CAAC;AACJ,CAAC;AAED,yFAAyF;AACzF,SAAS,mBAAmB,CAAC,OAAqC;IAChE,IAAI,OAAO,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC;QACjD,OAAO,CACL,WAAW,IAAI,kFAAkF;YACjG,iEAAiE,CAClE,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,iBAAiB,CAAC;IACrE,OAAO,CACL,WAAW,IAAI,oFAAoF;QACnG,oDAAoD,CACrD,CAAC;AACJ,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,kCAAkC;IAChD,MAAM,CAAC,KAAK,EAAE,CAAC;AACjB,CAAC"}
|
|
@@ -54,9 +54,5 @@ export declare class FileWriteBatch {
|
|
|
54
54
|
* Add a change to the batch
|
|
55
55
|
*/
|
|
56
56
|
private addChange;
|
|
57
|
-
/**
|
|
58
|
-
* Create a primary key lookup string (same logic as PullService)
|
|
59
|
-
*/
|
|
60
|
-
private createPrimaryKeyLookup;
|
|
61
57
|
}
|
|
62
58
|
//# sourceMappingURL=file-write-batch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-write-batch.d.ts","sourceRoot":"","sources":["../../src/lib/file-write-batch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"file-write-batch.d.ts","sourceRoot":"","sources":["../../src/lib/file-write-batch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAoB3C;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,YAAY,CAAgD;IAEpE;;;;OAIG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,UAAU,EAAE,GAAG,IAAI;IASnE;;;;;OAKG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI;IAoB7F;;;;OAIG;IACH,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,GAAG,IAAI;IASpE;;OAEG;YACW,gBAAgB;IAgB9B;;OAEG;YACW,YAAY;IAsD1B;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAgC9B;;OAEG;IACH,KAAK,IAAI,IAAI;IAKb;;OAEG;IACH,mBAAmB,IAAI,MAAM;IAI7B;;OAEG;IACH,eAAe,IAAI,MAAM,EAAE;IAI3B;;OAEG;IACH,OAAO,CAAC,SAAS;CAMlB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { JsonWriteHelper } from './json-write-helper.js';
|
|
4
|
+
import { createPrimaryKeyLookup, hasCompletePrimaryKey } from './record-primary-key.js';
|
|
4
5
|
/**
|
|
5
6
|
* Batches file write operations to improve performance and ensure consistent property ordering.
|
|
6
7
|
* Collects all changes during processing and writes each file only once at the end.
|
|
@@ -30,6 +31,14 @@ export class FileWriteBatch {
|
|
|
30
31
|
* @param primaryKeyLookup - Primary key lookup string to identify the record
|
|
31
32
|
*/
|
|
32
33
|
queueArrayUpdate(filePath, updatedRecord, primaryKeyLookup) {
|
|
34
|
+
// Array updates match entries by primary key. A record whose key has no value ({ ID: undefined },
|
|
35
|
+
// written to the file as `primaryKey: {}`) matches every other such record, so each queued record
|
|
36
|
+
// would overwrite the last and a batch of N new records would write exactly one (#3415).
|
|
37
|
+
// Refuse rather than lose data.
|
|
38
|
+
if (!hasCompletePrimaryKey(updatedRecord.primaryKey)) {
|
|
39
|
+
throw new Error(`Refusing to queue an update to ${path.basename(filePath)} for a record with an incomplete primary key ` +
|
|
40
|
+
`(${JSON.stringify(updatedRecord.primaryKey ?? {})}): it would overwrite other records in the file.`);
|
|
41
|
+
}
|
|
33
42
|
const absolutePath = path.resolve(filePath);
|
|
34
43
|
this.addChange(absolutePath, {
|
|
35
44
|
filePath: absolutePath,
|
|
@@ -88,7 +97,7 @@ export class FileWriteBatch {
|
|
|
88
97
|
const contentArray = Array.isArray(currentContent) ? currentContent : [];
|
|
89
98
|
if (change.primaryKeyLookup) {
|
|
90
99
|
// Find existing record with matching primary key
|
|
91
|
-
const index = contentArray.findIndex(r =>
|
|
100
|
+
const index = contentArray.findIndex(r => createPrimaryKeyLookup(r.primaryKey) === change.primaryKeyLookup);
|
|
92
101
|
if (index >= 0) {
|
|
93
102
|
// Update existing record
|
|
94
103
|
contentArray[index] = change.data;
|
|
@@ -170,12 +179,5 @@ export class FileWriteBatch {
|
|
|
170
179
|
}
|
|
171
180
|
this.changes.get(filePath).push(change);
|
|
172
181
|
}
|
|
173
|
-
/**
|
|
174
|
-
* Create a primary key lookup string (same logic as PullService)
|
|
175
|
-
*/
|
|
176
|
-
createPrimaryKeyLookup(primaryKey) {
|
|
177
|
-
const keys = Object.keys(primaryKey).sort();
|
|
178
|
-
return keys.map(k => `${k}:${primaryKey[k]}`).join('|');
|
|
179
|
-
}
|
|
180
182
|
}
|
|
181
183
|
//# sourceMappingURL=file-write-batch.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-write-batch.js","sourceRoot":"","sources":["../../src/lib/file-write-batch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"file-write-batch.js","sourceRoot":"","sources":["../../src/lib/file-write-batch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Primary-key extraction for pulled records.
|
|
3
|
+
*
|
|
4
|
+
* Pull must key every database record by its real primary-key value: the key decides whether a
|
|
5
|
+
* record updates an existing file entry or is appended as a new one. Reading the key through the
|
|
6
|
+
* entity's typed property (`record.ID`) only works when the entity's generated subclass is
|
|
7
|
+
* registered in this process. When it is not — an Open App whose server package did not load, a
|
|
8
|
+
* host's generated entities package missing from the CLI — the ClassFactory hands back a bare
|
|
9
|
+
* `BaseEntity`, which has no typed properties, so every record read `undefined`. All records then
|
|
10
|
+
* shared one key, each overwrote the last in the write batch, and a pull of N new records wrote
|
|
11
|
+
* exactly one, with an empty `primaryKey` that duplicated on the next pull (#3415).
|
|
12
|
+
*
|
|
13
|
+
* `BaseEntity.Get()` is metadata-driven and works on a bare `BaseEntity` as well as on a subclass.
|
|
14
|
+
*/
|
|
15
|
+
import type { BaseEntity, EntityInfo } from '@memberjunction/core';
|
|
16
|
+
import type { RecordData } from './sync-engine.js';
|
|
17
|
+
/** Primary-key values keyed by field name — the shape `RecordData.primaryKey` carries. */
|
|
18
|
+
export type PrimaryKeyValues = NonNullable<RecordData['primaryKey']>;
|
|
19
|
+
/**
|
|
20
|
+
* Reads every primary-key field of `record` through `BaseEntity.Get()`.
|
|
21
|
+
*
|
|
22
|
+
* @throws when a key field has no value. A record without its key cannot be matched to a file
|
|
23
|
+
* entry and would overwrite other records, so refusing is the only safe outcome.
|
|
24
|
+
*/
|
|
25
|
+
export declare function extractPrimaryKeyValues(record: BaseEntity, entityInfo: EntityInfo): PrimaryKeyValues;
|
|
26
|
+
/** True when `primaryKey` has at least one field and every field holds a value. */
|
|
27
|
+
export declare function hasCompletePrimaryKey(primaryKey: RecordData['primaryKey']): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Builds the string pull uses to match a database record to a file entry: `field:value` segments,
|
|
30
|
+
* sorted by field name, joined by `|`. `\` and `|` inside a value are escaped, so a value that
|
|
31
|
+
* contains the separator can't make two different keys produce the same string. The string is only
|
|
32
|
+
* ever compared in memory — never parsed or persisted.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createPrimaryKeyLookup(primaryKey: RecordData['primaryKey']): string;
|
|
35
|
+
//# sourceMappingURL=record-primary-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record-primary-key.d.ts","sourceRoot":"","sources":["../../src/lib/record-primary-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,0FAA0F;AAC1F,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC;AAErE;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,GAAG,gBAAgB,CAapG;AAUD,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,UAAU,CAAC,YAAY,CAAC,GAAG,OAAO,CAGnF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,UAAU,CAAC,YAAY,CAAC,GAAG,MAAM,CAMnF"}
|