@memberjunction/metadata-sync 6.1.2 → 6.2.0-edge.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.
Files changed (68) hide show
  1. package/README.md +88 -25
  2. package/dist/config.d.ts +42 -2
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js.map +1 -1
  5. package/dist/index.d.ts +6 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/lib/EntityPropertyExtractor.d.ts +16 -0
  10. package/dist/lib/EntityPropertyExtractor.d.ts.map +1 -1
  11. package/dist/lib/EntityPropertyExtractor.js +34 -0
  12. package/dist/lib/EntityPropertyExtractor.js.map +1 -1
  13. package/dist/lib/FieldExternalizer.d.ts +1 -2
  14. package/dist/lib/FieldExternalizer.d.ts.map +1 -1
  15. package/dist/lib/FieldExternalizer.js.map +1 -1
  16. package/dist/lib/RecordProcessor.d.ts +7 -0
  17. package/dist/lib/RecordProcessor.d.ts.map +1 -1
  18. package/dist/lib/RecordProcessor.js +27 -1
  19. package/dist/lib/RecordProcessor.js.map +1 -1
  20. package/dist/lib/existing-record-files.d.ts +12 -0
  21. package/dist/lib/existing-record-files.d.ts.map +1 -0
  22. package/dist/lib/existing-record-files.js +85 -0
  23. package/dist/lib/existing-record-files.js.map +1 -0
  24. package/dist/lib/file-backup-manager.d.ts +10 -0
  25. package/dist/lib/file-backup-manager.d.ts.map +1 -1
  26. package/dist/lib/file-backup-manager.js +17 -0
  27. package/dist/lib/file-backup-manager.js.map +1 -1
  28. package/dist/lib/file-write-batch.d.ts +0 -4
  29. package/dist/lib/file-write-batch.d.ts.map +1 -1
  30. package/dist/lib/file-write-batch.js +10 -8
  31. package/dist/lib/file-write-batch.js.map +1 -1
  32. package/dist/lib/graph-provider-pool.d.ts +62 -33
  33. package/dist/lib/graph-provider-pool.d.ts.map +1 -1
  34. package/dist/lib/graph-provider-pool.js +131 -79
  35. package/dist/lib/graph-provider-pool.js.map +1 -1
  36. package/dist/lib/json-subproperty-externalization.d.ts +77 -0
  37. package/dist/lib/json-subproperty-externalization.d.ts.map +1 -0
  38. package/dist/lib/json-subproperty-externalization.js +139 -0
  39. package/dist/lib/json-subproperty-externalization.js.map +1 -0
  40. package/dist/lib/push-outcome.d.ts +66 -0
  41. package/dist/lib/push-outcome.d.ts.map +1 -0
  42. package/dist/lib/push-outcome.js +81 -0
  43. package/dist/lib/push-outcome.js.map +1 -0
  44. package/dist/lib/push-write-mode.d.ts +47 -0
  45. package/dist/lib/push-write-mode.d.ts.map +1 -0
  46. package/dist/lib/push-write-mode.js +53 -0
  47. package/dist/lib/push-write-mode.js.map +1 -0
  48. package/dist/lib/record-primary-key.d.ts +35 -0
  49. package/dist/lib/record-primary-key.d.ts.map +1 -0
  50. package/dist/lib/record-primary-key.js +44 -0
  51. package/dist/lib/record-primary-key.js.map +1 -0
  52. package/dist/lib/transaction-manager.d.ts +11 -1
  53. package/dist/lib/transaction-manager.d.ts.map +1 -1
  54. package/dist/lib/transaction-manager.js +20 -4
  55. package/dist/lib/transaction-manager.js.map +1 -1
  56. package/dist/plugins/index.d.ts +1 -0
  57. package/dist/plugins/index.d.ts.map +1 -1
  58. package/dist/plugins/index.js +43 -17
  59. package/dist/plugins/index.js.map +1 -1
  60. package/dist/services/PullService.d.ts +1 -3
  61. package/dist/services/PullService.d.ts.map +1 -1
  62. package/dist/services/PullService.js +28 -68
  63. package/dist/services/PullService.js.map +1 -1
  64. package/dist/services/PushService.d.ts +109 -0
  65. package/dist/services/PushService.d.ts.map +1 -1
  66. package/dist/services/PushService.js +631 -319
  67. package/dist/services/PushService.js.map +1 -1
  68. package/package.json +14 -14
@@ -1,26 +1,61 @@
1
1
  /**
2
- * One independent provider per JSON-root graph for `mj sync push`.
2
+ * Provider assignment per JSON-root graph for `mj sync push`.
3
3
  *
4
- * Sibling graphs may run in parallel; a root and its nested relatedEntities share
5
- * one provider so every DB op in that graph uses the same connection. Mixing the
6
- * host connection with a graph instance in the same tree is a deadlock (child FK
7
- * waits on an uncommitted parent).
4
+ * Two modes, fixed when the pool is built. A pool never mixes them.
8
5
  *
9
- * Drain: a graph is released at the end of a batch when (a) it will not appear
10
- * at a later level, or (b) its TransactionDepth is 0 (Save already committed,
11
- * so a fresh instance at the next level is safe). Graphs with leftover depth
12
- * stay live until their last level. Peak live independent instances is therefore
13
- * bounded by `--parallel-batch-size`, plus any still-open leftover-depth graphs.
6
+ * - **`host`** (atomic push, the default): every graph uses the host provider, inside the
7
+ * push transaction. Nothing commits until the push commits. The host has ONE transaction
8
+ * stack, so graphs must run one at a time: `obtain` throws if a second graph asks for the
9
+ * host while another graph still holds it. The caller releases a graph with `drainBatch`.
10
+ *
11
+ * - **`independent`** (an entity directory using isolated transactions): each graph gets its own instance from
12
+ * `CreateIndependentInstance()` (shared pool, own transaction stack), so sibling graphs can
13
+ * run in parallel. A root and its nested relatedEntities share one instance, so every DB op
14
+ * in that graph uses the same connection. Each `Save()` commits on its own, so this mode is
15
+ * NOT atomic: `onGraphSettled` reports which graphs left committed rows behind.
16
+ * If `CreateIndependentInstance` fails, `obtain` throws. It never falls back to the host,
17
+ * because mixing the host connection with graph instances is a deadlock (child FK waits on
18
+ * an uncommitted parent). The caller decides the mode up front (see `probeIndependentInstances`).
19
+ *
20
+ * Drain (independent mode): a graph is released at the end of a batch when (a) it will not
21
+ * appear at a later level, or (b) its TransactionDepth is 0 (Save already committed, so a fresh
22
+ * instance at the next level is safe). Graphs with leftover depth stay live until their last
23
+ * level. Peak live independent instances is therefore bounded by the batch size, plus any
24
+ * still-open leftover-depth graphs.
14
25
  */
26
+ /**
27
+ * Checks once whether the host can create independent instances. Used to pick the pool mode
28
+ * before any graph runs, so a push never discovers mid-file that it has to switch topology.
29
+ * @returns `undefined` when independent instances work, otherwise the reason they do not.
30
+ */
31
+ export async function probeIndependentInstances(host) {
32
+ let instance;
33
+ try {
34
+ instance = await host.CreateIndependentInstance();
35
+ }
36
+ catch (e) {
37
+ return e instanceof Error ? e.message : String(e);
38
+ }
39
+ try {
40
+ await instance.ReleaseIndependentInstance();
41
+ }
42
+ catch {
43
+ /* the probe instance did no work; a failed release changes nothing */
44
+ }
45
+ return undefined;
46
+ }
15
47
  export class GraphProviderPool {
16
- constructor(host, log = () => undefined) {
48
+ constructor(host, options) {
17
49
  this.host = host;
18
- this.log = log;
19
50
  this.providers = new Map();
20
51
  this.lastLevelByGraph = new Map();
21
- this.independentUnavailable = false;
22
- this.unavailableLogged = false;
23
52
  this.anyFailed = false;
53
+ this.mode = options.mode;
54
+ this.log = options.log ?? (() => undefined);
55
+ this.onGraphSettled = options.onGraphSettled;
56
+ }
57
+ get Mode() {
58
+ return this.mode;
24
59
  }
25
60
  /**
26
61
  * Record, per graphId, the highest dependency-level index it occupies so we
@@ -41,54 +76,44 @@ export class GraphProviderPool {
41
76
  get hasFailed() {
42
77
  return this.anyFailed;
43
78
  }
44
- /**
45
- * Provider for this graph. Never returns a mix: either every graph in the
46
- * file gets its own independent instance, or (if the *first*
47
- * CreateIndependentInstance fails) every graph uses the host.
48
- *
49
- * If independent instances already exist and a later CreateIndependentInstance
50
- * throws, this throws rather than handing the rest of the file to the host.
51
- * Mid-file topology change is the deadlock this pool exists to prevent.
52
- *
53
- * The host itself is never stored in the map, so release cannot
54
- * RollbackTransaction the global push TX.
55
- */
79
+ /** Provider for this graph. See the class comment for the per-mode rules. */
56
80
  async obtain(graphId) {
81
+ if (this.mode === 'host') {
82
+ return this.obtainHost(graphId);
83
+ }
57
84
  const existing = this.providers.get(graphId);
58
85
  if (existing)
59
86
  return existing;
60
- if (this.independentUnavailable)
61
- return this.host;
62
87
  try {
63
88
  const created = await this.host.CreateIndependentInstance();
64
89
  this.providers.set(graphId, created);
65
90
  return created;
66
91
  }
67
92
  catch (e) {
68
- const reason = e.message;
69
- if (this.providers.size > 0) {
70
- throw new Error(`CreateIndependentInstance failed after ${this.providers.size} graph(s) already had independent instances (${reason}). ` +
71
- `Refusing mixed host + independent topology in one file.`);
72
- }
73
- this.independentUnavailable = true;
74
- if (!this.unavailableLogged) {
75
- this.unavailableLogged = true;
76
- this.log(`⚠️ CreateIndependentInstance unavailable (${reason}); ` +
77
- `ALL graphs in this file use the host provider (inside the push transaction). ` +
78
- `Mixing host + independent instances in one file is a deadlock — we refuse that topology.`);
79
- }
80
- return this.host;
93
+ const reason = e instanceof Error ? e.message : String(e);
94
+ throw new Error(`CreateIndependentInstance failed for graph ${graphId} (${reason}). ` +
95
+ `Refusing to run this graph on the host provider: mixing host and independent instances is a deadlock.`);
81
96
  }
82
97
  }
98
+ obtainHost(graphId) {
99
+ if (this.hostHolder !== undefined && this.hostHolder !== graphId) {
100
+ throw new Error(`Graph ${graphId} asked for the host provider while graph ${this.hostHolder} still holds it. ` +
101
+ `In atomic mode graphs share one transaction stack and must run one at a time.`);
102
+ }
103
+ this.hostHolder = graphId;
104
+ return this.host;
105
+ }
83
106
  /**
84
- * Commit-or-rollback + release graphs in this batch that are done:
85
- * their last level is `levelIndex`, or TransactionDepth is already 0
86
- * (Save settled; a fresh instance at a later level is safe).
87
- * Leftover-depth graphs that still appear later stay live.
88
- * Returns the first settle error so the caller cannot report success
89
- * with uncommitted rows.
107
+ * Release graphs in this batch that are done. Host mode: frees the host for the next graph
108
+ * (the push transaction settles it later). Independent mode: commit-or-rollback + release the
109
+ * graphs whose last level is `levelIndex`, or whose TransactionDepth is already 0.
110
+ * Returns the first settle error so the caller cannot report success with uncommitted rows.
90
111
  */
91
112
  async drainBatch(graphIds, levelIndex) {
113
+ if (this.mode === 'host') {
114
+ this.releaseHost(graphIds);
115
+ return undefined;
116
+ }
92
117
  const ending = graphIds.filter((id) => {
93
118
  const last = this.lastLevelByGraph.get(id) ?? levelIndex;
94
119
  if (last === levelIndex)
@@ -98,52 +123,79 @@ export class GraphProviderPool {
98
123
  });
99
124
  return this.releaseGraphs(ending);
100
125
  }
101
- /** Release every remaining independent instance. Safe to call from `finally`. */
126
+ /** Release every remaining graph. Safe to call from `finally`. */
102
127
  async releaseAll() {
128
+ if (this.mode === 'host') {
129
+ this.hostHolder = undefined;
130
+ return undefined;
131
+ }
103
132
  return this.releaseGraphs([...this.providers.keys()]);
104
133
  }
134
+ releaseHost(graphIds) {
135
+ if (this.hostHolder !== undefined && graphIds.includes(this.hostHolder)) {
136
+ this.hostHolder = undefined;
137
+ }
138
+ }
105
139
  async releaseGraphs(ids) {
106
140
  let settleError;
107
141
  for (const id of ids) {
108
142
  const provider = this.providers.get(id);
109
143
  this.providers.delete(id);
110
- if (!provider || provider === this.host)
144
+ if (!provider)
111
145
  continue;
146
+ const error = await this.settleAndRelease(id, provider);
147
+ settleError ??= error;
148
+ }
149
+ return settleError;
150
+ }
151
+ /** Settle one independent instance, report the outcome, and always release it. */
152
+ async settleAndRelease(id, provider) {
153
+ try {
154
+ const outcome = await this.settle(provider);
155
+ this.onGraphSettled?.(id, outcome);
156
+ return undefined;
157
+ }
158
+ catch (e) {
159
+ const error = e instanceof Error ? e : new Error(String(e));
160
+ this.anyFailed = true;
161
+ this.log(`Failed to settle graph ${id} transaction: ${error.message}`);
162
+ await this.rollbackQuietly(provider);
163
+ this.onGraphSettled?.(id, 'rolledBack');
164
+ return error;
165
+ }
166
+ finally {
112
167
  try {
113
- if (provider.TransactionDepth > 0) {
114
- if (this.anyFailed) {
115
- // Explicit rollback at the call site — do not rely on
116
- // ReleaseIndependentInstance's implicit leftover-depth rollback.
117
- await provider.RollbackTransaction();
118
- }
119
- else {
120
- await provider.CommitTransaction();
121
- }
122
- }
168
+ await provider.ReleaseIndependentInstance();
123
169
  }
124
- catch (e) {
125
- this.anyFailed = true;
126
- settleError ??= e;
127
- this.log(`Failed to settle graph ${id} transaction: ${e.message}`);
128
- try {
129
- if (provider.TransactionDepth > 0) {
130
- await provider.RollbackTransaction();
131
- }
132
- }
133
- catch {
134
- /* still release */
135
- }
170
+ catch {
171
+ /* pool-safe: handle already dropped */
136
172
  }
137
- finally {
138
- try {
139
- await provider.ReleaseIndependentInstance();
140
- }
141
- catch {
142
- /* pool-safe: handle already dropped */
143
- }
173
+ }
174
+ }
175
+ /**
176
+ * Depth 0: every Save on this instance already committed. Leftover depth: commit it, or
177
+ * roll it back explicitly when the file failed (do not rely on ReleaseIndependentInstance).
178
+ */
179
+ async settle(provider) {
180
+ if (provider.TransactionDepth === 0) {
181
+ return 'committed';
182
+ }
183
+ if (this.anyFailed) {
184
+ await provider.RollbackTransaction();
185
+ return 'rolledBack';
186
+ }
187
+ await provider.CommitTransaction();
188
+ return 'committed';
189
+ }
190
+ async rollbackQuietly(provider) {
191
+ try {
192
+ if (provider.TransactionDepth > 0) {
193
+ await provider.RollbackTransaction();
144
194
  }
145
195
  }
146
- return settleError;
196
+ catch {
197
+ /* still release */
198
+ }
147
199
  }
148
200
  }
149
201
  //# sourceMappingURL=graph-provider-pool.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"graph-provider-pool.js","sourceRoot":"","sources":["../../src/lib/graph-provider-pool.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"graph-provider-pool.js","sourceRoot":"","sources":["../../src/lib/graph-provider-pool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Externalizing a property *inside* a JSON field.
3
+ *
4
+ * `pull.externalizeFields` names entity fields, so it can move a whole column into
5
+ * a side file (`TemplateText` → `templates/x.md`) but cannot reach a single property
6
+ * inside a JSON column. That is the wrong granularity whenever a column mixes
7
+ * hand-authored config with a machine-generated artifact: MJ Tests keep step budgets,
8
+ * oracles and the recorded `ReplayScript` in one `Configuration` blob, and
9
+ * externalizing the whole thing would bury the readable half.
10
+ *
11
+ * A dotted config field (`Configuration.ReplayScript`) externalizes just the leaf and
12
+ * leaves an `@file:` reference in its place. Push already resolves nested references
13
+ * when it walks a metadata object, so the value round-trips with no push-side change.
14
+ *
15
+ * The file IO stays in `FieldExternalizer` — it is injected here as `externalize`, so
16
+ * this module is only the JSON walk: find the leaf, hand it over, splice the reference
17
+ * back. Pattern placeholders, path preservation and the unchanged-content skip all come
18
+ * from that existing code.
19
+ */
20
+ /** A sub-property externalization resolved against one field. */
21
+ export interface SubPropertyExternalization {
22
+ /** Property path within the field's JSON, e.g. `['ReplayScript']`. */
23
+ path: string[];
24
+ /** The `@file:` pattern to externalize to. */
25
+ pattern: string;
26
+ }
27
+ /** Writes a value to a file and returns the `@file:` reference naming it. */
28
+ export type ExternalizeLeaf = (value: unknown, pattern: string, existingRef?: string, leafName?: string) => Promise<string>;
29
+ /**
30
+ * The one `FieldExternalizer` method this module needs. `recordData` is declared as a
31
+ * property bag rather than `BaseEntity` because that is all it is ever used for —
32
+ * `RecordProcessor` hands the pulled properties straight through, and the externalizer
33
+ * only reads `Name` / `ID` / field placeholders off it to build the filename.
34
+ */
35
+ export interface FieldExternalizerLike {
36
+ externalizeField(fieldName: string, fieldValue: unknown, pattern: string, recordData: Record<string, unknown>, targetDir: string, existingFileReference?: string, mergeStrategy?: string, verbose?: boolean): Promise<string>;
37
+ }
38
+ /**
39
+ * Split an `externalizeFields` entry into the entity field it targets and the property
40
+ * path within that field. A name with no dot yields an empty path, which is how callers
41
+ * tell a whole-field config (the existing behavior) from a sub-property one.
42
+ */
43
+ export declare function parseExternalizePath(configField: string): {
44
+ field: string;
45
+ path: string[];
46
+ };
47
+ /**
48
+ * The sub-property externalizations rooted at `fieldName`. Whole-field entries are
49
+ * excluded so they continue down the original code path untouched, and the legacy
50
+ * string-array config form yields nothing because it can only name whole fields.
51
+ */
52
+ export declare function findSubPropertyExternalizations(fieldName: string, externalizeConfig: unknown): SubPropertyExternalization[];
53
+ /** Read the value at `path`, or `undefined` if any step is missing or not an object. */
54
+ export declare function getAtPath(value: unknown, path: string[]): unknown;
55
+ /**
56
+ * Bind a `FieldExternalizer` into the `ExternalizeLeaf` shape this module walks with.
57
+ *
58
+ * The serialization here is load-bearing. `FieldExternalizer` writes `String(value)` and
59
+ * only pretty-prints when the field name hints at JSON, so handing it a parsed object
60
+ * would write the literal text `[object Object]`. Serializing first gives a diffable file
61
+ * and a stable string for the unchanged-content comparison that skips redundant writes.
62
+ */
63
+ export declare function fieldExternalizerAdapter(externalizer: FieldExternalizerLike, recordProperties: Record<string, unknown>, targetDir: string, mergeStrategy?: string, verbose?: boolean): ExternalizeLeaf;
64
+ /**
65
+ * Externalize each configured sub-property of `fieldValue`, returning the field value
66
+ * with `@file:` references in their place.
67
+ *
68
+ * A path the value does not carry is skipped entirely — no file, no key — so a record
69
+ * that never produced the artifact stays byte-identical. `existingFieldValue` is the
70
+ * same field as it currently stands in the metadata file; the reference found at each
71
+ * path is forwarded so `FieldExternalizer` can keep writing to the path already chosen
72
+ * there instead of relocating the file to the pattern's default.
73
+ *
74
+ * Returns the input unchanged when nothing is configured or the value is not JSON.
75
+ */
76
+ export declare function externalizeSubProperties(fieldValue: unknown, configs: SubPropertyExternalization[], externalize: ExternalizeLeaf, existingFieldValue?: unknown): Promise<unknown>;
77
+ //# sourceMappingURL=json-subproperty-externalization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-subproperty-externalization.d.ts","sourceRoot":"","sources":["../../src/lib/json-subproperty-externalization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,iEAAiE;AACjE,MAAM,WAAW,0BAA0B;IACvC,sEAAsE;IACtE,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,6EAA6E;AAC7E,MAAM,MAAM,eAAe,GAAG,CAC1B,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,KAChB,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IAClC,gBAAgB,CACZ,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,OAAO,EACnB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,SAAS,EAAE,MAAM,EACjB,qBAAqB,CAAC,EAAE,MAAM,EAC9B,aAAa,CAAC,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,OAAO,GAClB,OAAO,CAAC,MAAM,CAAC,CAAC;CACtB;AAMD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAG3F;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC3C,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,OAAO,GAC3B,0BAA0B,EAAE,CAe9B;AAED,wFAAwF;AACxF,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CASjE;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACpC,YAAY,EAAE,qBAAqB,EACnC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzC,SAAS,EAAE,MAAM,EACjB,aAAa,GAAE,MAAgB,EAC/B,OAAO,CAAC,EAAE,OAAO,GAClB,eAAe,CAcjB;AA+BD;;;;;;;;;;;GAWG;AACH,wBAAsB,wBAAwB,CAC1C,UAAU,EAAE,OAAO,EACnB,OAAO,EAAE,0BAA0B,EAAE,EACrC,WAAW,EAAE,eAAe,EAC5B,kBAAkB,CAAC,EAAE,OAAO,GAC7B,OAAO,CAAC,OAAO,CAAC,CA4BlB"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Externalizing a property *inside* a JSON field.
3
+ *
4
+ * `pull.externalizeFields` names entity fields, so it can move a whole column into
5
+ * a side file (`TemplateText` → `templates/x.md`) but cannot reach a single property
6
+ * inside a JSON column. That is the wrong granularity whenever a column mixes
7
+ * hand-authored config with a machine-generated artifact: MJ Tests keep step budgets,
8
+ * oracles and the recorded `ReplayScript` in one `Configuration` blob, and
9
+ * externalizing the whole thing would bury the readable half.
10
+ *
11
+ * A dotted config field (`Configuration.ReplayScript`) externalizes just the leaf and
12
+ * leaves an `@file:` reference in its place. Push already resolves nested references
13
+ * when it walks a metadata object, so the value round-trips with no push-side change.
14
+ *
15
+ * The file IO stays in `FieldExternalizer` — it is injected here as `externalize`, so
16
+ * this module is only the JSON walk: find the leaf, hand it over, splice the reference
17
+ * back. Pattern placeholders, path preservation and the unchanged-content skip all come
18
+ * from that existing code.
19
+ */
20
+ function isPlainObject(value) {
21
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
22
+ }
23
+ /**
24
+ * Split an `externalizeFields` entry into the entity field it targets and the property
25
+ * path within that field. A name with no dot yields an empty path, which is how callers
26
+ * tell a whole-field config (the existing behavior) from a sub-property one.
27
+ */
28
+ export function parseExternalizePath(configField) {
29
+ const [field, ...path] = configField.split('.');
30
+ return { field, path };
31
+ }
32
+ /**
33
+ * The sub-property externalizations rooted at `fieldName`. Whole-field entries are
34
+ * excluded so they continue down the original code path untouched, and the legacy
35
+ * string-array config form yields nothing because it can only name whole fields.
36
+ */
37
+ export function findSubPropertyExternalizations(fieldName, externalizeConfig) {
38
+ if (!Array.isArray(externalizeConfig)) {
39
+ return [];
40
+ }
41
+ const found = [];
42
+ for (const entry of externalizeConfig) {
43
+ if (!isPlainObject(entry) || typeof entry.field !== 'string' || typeof entry.pattern !== 'string') {
44
+ continue;
45
+ }
46
+ const { field, path } = parseExternalizePath(entry.field);
47
+ if (path.length > 0 && field === fieldName) {
48
+ found.push({ path, pattern: entry.pattern });
49
+ }
50
+ }
51
+ return found;
52
+ }
53
+ /** Read the value at `path`, or `undefined` if any step is missing or not an object. */
54
+ export function getAtPath(value, path) {
55
+ let cursor = value;
56
+ for (const key of path) {
57
+ if (!isPlainObject(cursor)) {
58
+ return undefined;
59
+ }
60
+ cursor = cursor[key];
61
+ }
62
+ return cursor;
63
+ }
64
+ /**
65
+ * Bind a `FieldExternalizer` into the `ExternalizeLeaf` shape this module walks with.
66
+ *
67
+ * The serialization here is load-bearing. `FieldExternalizer` writes `String(value)` and
68
+ * only pretty-prints when the field name hints at JSON, so handing it a parsed object
69
+ * would write the literal text `[object Object]`. Serializing first gives a diffable file
70
+ * and a stable string for the unchanged-content comparison that skips redundant writes.
71
+ */
72
+ export function fieldExternalizerAdapter(externalizer, recordProperties, targetDir, mergeStrategy = 'merge', verbose) {
73
+ return async (value, pattern, existingRef, leafName) => {
74
+ const serialized = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
75
+ return externalizer.externalizeField(leafName ?? '', serialized, pattern, recordProperties, targetDir, existingRef, mergeStrategy, verbose);
76
+ };
77
+ }
78
+ /** A copy of `root` with `path` set to `leaf`. Untouched branches are shared, not cloned. */
79
+ function withLeaf(root, path, leaf) {
80
+ const [head, ...rest] = path;
81
+ if (rest.length === 0) {
82
+ return { ...root, [head]: leaf };
83
+ }
84
+ const child = root[head];
85
+ if (!isPlainObject(child)) {
86
+ return root;
87
+ }
88
+ return { ...root, [head]: withLeaf(child, rest, leaf) };
89
+ }
90
+ /** JSON columns arrive as a string from the database and as an object from a metadata file. */
91
+ function coerceToObject(value) {
92
+ if (isPlainObject(value)) {
93
+ return value;
94
+ }
95
+ if (typeof value === 'string') {
96
+ try {
97
+ const parsed = JSON.parse(value);
98
+ return isPlainObject(parsed) ? parsed : null;
99
+ }
100
+ catch {
101
+ return null;
102
+ }
103
+ }
104
+ return null;
105
+ }
106
+ /**
107
+ * Externalize each configured sub-property of `fieldValue`, returning the field value
108
+ * with `@file:` references in their place.
109
+ *
110
+ * A path the value does not carry is skipped entirely — no file, no key — so a record
111
+ * that never produced the artifact stays byte-identical. `existingFieldValue` is the
112
+ * same field as it currently stands in the metadata file; the reference found at each
113
+ * path is forwarded so `FieldExternalizer` can keep writing to the path already chosen
114
+ * there instead of relocating the file to the pattern's default.
115
+ *
116
+ * Returns the input unchanged when nothing is configured or the value is not JSON.
117
+ */
118
+ export async function externalizeSubProperties(fieldValue, configs, externalize, existingFieldValue) {
119
+ if (!configs || configs.length === 0) {
120
+ return fieldValue;
121
+ }
122
+ const parsed = coerceToObject(fieldValue);
123
+ if (!parsed) {
124
+ return fieldValue;
125
+ }
126
+ const existing = coerceToObject(existingFieldValue);
127
+ let result = parsed;
128
+ for (const { path, pattern } of configs) {
129
+ const leaf = getAtPath(result, path);
130
+ if (leaf === undefined || leaf === null) {
131
+ continue;
132
+ }
133
+ const existingRef = existing ? getAtPath(existing, path) : undefined;
134
+ const reference = await externalize(leaf, pattern, typeof existingRef === 'string' ? existingRef : undefined, path[path.length - 1]);
135
+ result = withLeaf(result, path, reference);
136
+ }
137
+ return result;
138
+ }
139
+ //# sourceMappingURL=json-subproperty-externalization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-subproperty-externalization.js","sourceRoot":"","sources":["../../src/lib/json-subproperty-externalization.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @fileoverview What a failed `mj sync push` left behind, and how to say so.
3
+ * @module push-outcome
4
+ */
5
+ import type { PushWriteMode } from './push-write-mode.js';
6
+ /** A create or update committed outside the push transaction (an isolated directory only). */
7
+ export interface CommittedWrite {
8
+ /** Absolute path of the metadata file the record came from. */
9
+ filePath: string;
10
+ entityName: string;
11
+ /** Position of the record inside the file, e.g. `MJ: Actions[3]/MJ: Action Params[0]`. */
12
+ recordPath: string;
13
+ status: 'created' | 'updated';
14
+ }
15
+ /** What a push had done when it failed. Same shape as the successful result's counts. */
16
+ export interface PushPartialTotals {
17
+ created: number;
18
+ updated: number;
19
+ unchanged: number;
20
+ deleted: number;
21
+ skipped: number;
22
+ deferred: number;
23
+ errors: number;
24
+ }
25
+ export interface PushAbortedDetails {
26
+ /** The write modes the directories processed so far were using. */
27
+ modes: PushWriteMode[];
28
+ /** Whether the push transaction was rolled back (or was never opened, as in a dry run). */
29
+ rolledBack: boolean;
30
+ /** Rows that are still in the database even though the push failed. */
31
+ committedWrites: CommittedWrite[];
32
+ /** Counts as far as the push got. Reported so a failed run is still machine-readable. */
33
+ totals: PushPartialTotals;
34
+ /** The SQL log for this run, when one was being written. Most wanted on a failure. */
35
+ sqlLogPath?: string;
36
+ cause: unknown;
37
+ }
38
+ /**
39
+ * Thrown by `PushService.push()` whenever a push fails after it started writing.
40
+ * The message is the original failure's message, so existing callers keep working;
41
+ * the extra fields let a caller say exactly what is and is not in the database.
42
+ */
43
+ export declare class PushAbortedError extends Error {
44
+ /** The write modes in play when the push failed, e.g. `['shared']` or `['shared','isolated']`. */
45
+ readonly modes: PushWriteMode[];
46
+ readonly rolledBack: boolean;
47
+ readonly committedWrites: CommittedWrite[];
48
+ readonly totals: PushPartialTotals;
49
+ readonly sqlLogPath?: string;
50
+ constructor(details: PushAbortedDetails);
51
+ /** True only when nothing from this push is left in the database. */
52
+ get NothingCommitted(): boolean;
53
+ }
54
+ /**
55
+ * The lines that tell the user how a failed push ended. The first line is the summary;
56
+ * any further lines list the committed records, grouped by file.
57
+ */
58
+ export declare function describeRollbackOutcome(rolledBack: boolean, committedWrites: CommittedWrite[], cwd: string): string[];
59
+ /** One line per file, then one indented line per committed record. */
60
+ export declare function describeCommittedWrites(committedWrites: CommittedWrite[], cwd: string): string[];
61
+ /**
62
+ * Message for a failed COMMIT. On PostgreSQL, deferred foreign keys are checked at commit, so
63
+ * the failure is about the transaction as a whole and cannot be tied to one record.
64
+ */
65
+ export declare function describeCommitFailure(cause: unknown, platform: string | undefined): string;
66
+ //# sourceMappingURL=push-outcome.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push-outcome.d.ts","sourceRoot":"","sources":["../../src/lib/push-outcome.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,8FAA8F;AAC9F,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;CAC/B;AAED,yFAAyF;AACzF,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,mEAAmE;IACnE,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,2FAA2F;IAC3F,UAAU,EAAE,OAAO,CAAC;IACpB,uEAAuE;IACvE,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,yFAAyF;IACzF,MAAM,EAAE,iBAAiB,CAAC;IAC1B,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,kGAAkG;IAClG,QAAQ,CAAC,KAAK,EAAE,aAAa,EAAE,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,eAAe,EAAE,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;gBAEjB,OAAO,EAAE,kBAAkB;IAUvC,qEAAqE;IACrE,IAAI,gBAAgB,IAAI,OAAO,CAE9B;CACF;AAMD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBrH;AAED,sEAAsE;AACtE,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,cAAc,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAehG;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAS1F"}