@memberjunction/global
Advanced tools
| export {}; | ||
| //# sourceMappingURL=FieldRulesEvaluator.test.d.ts.map |
| {"version":3,"file":"FieldRulesEvaluator.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/FieldRulesEvaluator.test.ts"],"names":[],"mappings":""} |
| import { describe, it, expect } from 'vitest'; | ||
| import { FieldRulesEvaluator } from '../fieldRules/FieldRulesEvaluator.js'; | ||
| import { FieldTransformEngine } from '../fieldRules/FieldTransformEngine.js'; | ||
| import { RegisterFieldTransform, GetFieldTransform } from '../fieldRules/transformRegistry.js'; | ||
| describe('FieldTransformEngine — plugin registry (keeps libs out of global)', () => { | ||
| it('dispatches a non-built-in transform to a registered plugin', () => { | ||
| const seen = []; | ||
| RegisterFieldTransform('jsonpath', (value, _fields, config) => { | ||
| seen.push({ value, config }); | ||
| return 'plugin-result'; | ||
| }); | ||
| expect(GetFieldTransform('JSONPath')).toBeTypeOf('function'); // case-insensitive | ||
| const out = new FieldTransformEngine().ExecutePipeline('{"a":1}', {}, [{ Type: 'jsonpath', Config: { Path: '$.a' } }]); | ||
| expect(out.Value).toBe('plugin-result'); | ||
| expect(seen[0]).toEqual({ value: '{"a":1}', config: { Path: '$.a' } }); | ||
| }); | ||
| it('errors clearly for an unregistered plugin transform (OnError=Fail)', () => { | ||
| expect(() => new FieldTransformEngine().ExecutePipeline('x', {}, [{ Type: 'xpath', Config: { Path: '/a' }, OnError: 'Fail' }])) | ||
| .toThrow(/Unknown transform type 'xpath'/); | ||
| }); | ||
| }); | ||
| describe('FieldRulesEvaluator — entityDocument source (injected render infra)', () => { | ||
| it('renders the record via the injected EntityDocumentResolver', async () => { | ||
| const seen = []; | ||
| const evaluator = new FieldRulesEvaluator({ EntityDocumentResolver: async (d) => { seen.push(d); return 'RENDERED DOC'; } }); | ||
| const [c] = await evaluator.ComputeChanges({ Name: 'Acme', Doc: '' }, { Rules: [{ TargetField: 'Doc', Source: { Kind: 'entityDocument', EntityDocument: { EntityDocumentID: 'ed-1' } } }] }); | ||
| expect(c.NewValue).toBe('RENDERED DOC'); | ||
| expect(seen[0]).toEqual({ EntityDocumentID: 'ed-1', Record: { Name: 'Acme', Doc: '' } }); | ||
| }); | ||
| it('errors clearly when no EntityDocumentResolver is provided', async () => { | ||
| const [c] = await new FieldRulesEvaluator().ComputeChanges({ X: '' }, { Rules: [{ TargetField: 'X', Source: { Kind: 'entityDocument' } }] }); | ||
| expect(c.Applied).toBe(false); | ||
| expect(c.Error).toMatch(/no EntityDocumentResolver/); | ||
| }); | ||
| }); | ||
| describe('FieldRulesEvaluator — prompt sources', () => { | ||
| it('passes the WHOLE record to the prompt by default and uses its output', async () => { | ||
| const seen = []; | ||
| const evaluator = new FieldRulesEvaluator({ PromptResolver: async (p) => { seen.push(p); return 'A concise summary.'; } }); | ||
| const [c] = await evaluator.ComputeChanges({ Name: 'Acme', Notes: 'long notes…', Summary: '' }, { Rules: [{ TargetField: 'Summary', Source: { Kind: 'prompt', Prompt: { PromptID: 'p-1' } } }] }); | ||
| expect(c.NewValue).toBe('A concise summary.'); | ||
| expect(seen[0].PromptID).toBe('p-1'); | ||
| expect(seen[0].Data).toEqual({ Name: 'Acme', Notes: 'long notes…', Summary: '' }); // whole record | ||
| }); | ||
| it('shapes the prompt data when Data is provided (field/formula/static resolved per entry)', async () => { | ||
| let captured = {}; | ||
| const evaluator = new FieldRulesEvaluator({ PromptResolver: async (p) => { captured = p.Data; return 'ok'; } }); | ||
| await evaluator.ComputeChanges({ First: 'Ada', Last: 'Lovelace', Bio: '' }, { Rules: [{ TargetField: 'Bio', Source: { Kind: 'prompt', Prompt: { PromptID: 'p-2', Data: { | ||
| fullName: { Kind: 'formula', Expression: "fields.First + ' ' + fields.Last" }, | ||
| role: { Kind: 'static', Value: 'mathematician' }, | ||
| } } } }] }); | ||
| expect(captured).toEqual({ fullName: 'Ada Lovelace', role: 'mathematician' }); | ||
| }); | ||
| it('errors clearly when a prompt rule is used with no PromptResolver', async () => { | ||
| const [c] = await new FieldRulesEvaluator().ComputeChanges({ X: '' }, { Rules: [{ TargetField: 'X', Source: { Kind: 'prompt', Prompt: { PromptID: 'p' } } }] }); | ||
| expect(c.Applied).toBe(false); | ||
| expect(c.Error).toMatch(/no PromptResolver/); | ||
| }); | ||
| }); | ||
| describe('FieldRulesEvaluator.ComputeChanges', () => { | ||
| const run = async (record, ruleSet, resolver) => new FieldRulesEvaluator({ LookupResolver: resolver }).ComputeChanges(record, ruleSet); | ||
| describe('value sources', () => { | ||
| it('static: sets a literal value and flags the change', async () => { | ||
| const [c] = await run({ Status: 'Active' }, { Rules: [{ TargetField: 'Status', Source: { Kind: 'static', Value: 'Inactive' } }] }); | ||
| expect(c).toMatchObject({ Field: 'Status', OldValue: 'Active', NewValue: 'Inactive', Changed: true, Applied: true }); | ||
| }); | ||
| it('static: equal value is Applied but not Changed', async () => { | ||
| const [c] = await run({ Status: 'Active' }, { Rules: [{ TargetField: 'Status', Source: { Kind: 'static', Value: 'Active' } }] }); | ||
| expect(c).toMatchObject({ Changed: false, Applied: true }); | ||
| }); | ||
| it('field: copies another field', async () => { | ||
| const [c] = await run({ A: 'x', B: 'y' }, { Rules: [{ TargetField: 'A', Source: { Kind: 'field', Field: 'B' } }] }); | ||
| expect(c.NewValue).toBe('y'); | ||
| }); | ||
| it('formula: computes from record fields', async () => { | ||
| const [c] = await run({ FirstName: 'Ada', LastName: 'Lovelace', FullName: '' }, { Rules: [{ TargetField: 'FullName', Source: { Kind: 'formula', Expression: "fields.FirstName + ' ' + fields.LastName" } }] }); | ||
| expect(c.NewValue).toBe('Ada Lovelace'); | ||
| }); | ||
| it('lookup: resolves via the injected resolver and passes through the matched value', async () => { | ||
| const seen = []; | ||
| const [c] = await run({ CompanyName: 'Acme', CompanyID: null }, { Rules: [{ TargetField: 'CompanyID', Source: { Kind: 'lookup', Lookup: { Entity: 'Accounts', MatchField: 'Name', MatchValue: { Kind: 'field', Field: 'CompanyName' }, ReturnField: 'ID' } } }] }, async (l) => { seen.push(l); return 'acct-123'; }); | ||
| expect(c.NewValue).toBe('acct-123'); | ||
| expect(seen[0]).toEqual({ Entity: 'Accounts', MatchField: 'Name', MatchValue: 'Acme', ReturnField: 'ID' }); | ||
| }); | ||
| it('lookup: falls back to Default when the resolver returns undefined', async () => { | ||
| const [c] = await run({ Name: 'x', RegionID: null }, { Rules: [{ TargetField: 'RegionID', Source: { Kind: 'lookup', Lookup: { Entity: 'Regions', MatchField: 'Name', MatchValue: { Kind: 'static', Value: 'Nowhere' }, ReturnField: 'ID', Default: 'unknown' } } }] }, async () => undefined); | ||
| expect(c.NewValue).toBe('unknown'); | ||
| }); | ||
| it('lookup: errors clearly when no resolver is provided', async () => { | ||
| const [c] = await run({ X: null }, { Rules: [{ TargetField: 'X', Source: { Kind: 'lookup', Lookup: { Entity: 'E', MatchField: 'F', MatchValue: { Kind: 'static', Value: 1 }, ReturnField: 'ID' } } }] }); | ||
| expect(c.Applied).toBe(false); | ||
| expect(c.Error).toMatch(/no LookupResolver/); | ||
| }); | ||
| }); | ||
| describe('conditions', () => { | ||
| it('applies the rule only when the condition is true', async () => { | ||
| const ruleSet = { Rules: [{ TargetField: 'Tier', Source: { Kind: 'static', Value: 'Gold' }, Condition: 'Revenue > 1000' }] }; | ||
| const [hot] = await run({ Revenue: 5000, Tier: 'Bronze' }, ruleSet); | ||
| const [cold] = await run({ Revenue: 10, Tier: 'Bronze' }, ruleSet); | ||
| expect(hot).toMatchObject({ Applied: true, NewValue: 'Gold' }); | ||
| expect(cold).toMatchObject({ Applied: false, SkipReason: expect.stringMatching(/condition/) }); | ||
| }); | ||
| it('reports a condition expression error without throwing', async () => { | ||
| const [c] = await run({ X: 1 }, { Rules: [{ TargetField: 'X', Source: { Kind: 'static', Value: 2 }, Condition: 'this is not valid ===' }] }); | ||
| expect(c.Applied).toBe(false); | ||
| expect(c.Error).toMatch(/condition/); | ||
| }); | ||
| }); | ||
| describe('transform pipeline on the computed value', () => { | ||
| it('chains transforms (formula → coerce → custom)', async () => { | ||
| const [c] = await run({ Price: '19.99', Total: 0 }, { Rules: [{ | ||
| TargetField: 'Total', | ||
| Source: { Kind: 'field', Field: 'Price' }, | ||
| Transforms: [ | ||
| { Type: 'coerce', Config: { TargetType: 'number' } }, | ||
| { Type: 'custom', Config: { Expression: 'value * 2' } }, | ||
| ], | ||
| }] }); | ||
| expect(c.NewValue).toBeCloseTo(39.98); | ||
| }); | ||
| it('OnError=Null nulls the field when a transform throws', async () => { | ||
| const [c] = await run({ Amount: 'not-a-number', Out: 'x' }, { Rules: [{ TargetField: 'Out', Source: { Kind: 'field', Field: 'Amount' }, Transforms: [{ Type: 'coerce', Config: { TargetType: 'number' }, OnError: 'Null' }] }] }); | ||
| expect(c.NewValue).toBeNull(); | ||
| }); | ||
| }); | ||
| it('computes ALL rules independently (multi-rule preview)', async () => { | ||
| const changes = await run({ A: 1, B: 2, C: 3 }, { Rules: [ | ||
| { TargetField: 'A', Source: { Kind: 'static', Value: 10 } }, | ||
| { TargetField: 'B', Source: { Kind: 'static', Value: 2 } }, // unchanged | ||
| { TargetField: 'C', Source: { Kind: 'static', Value: 30 }, Condition: 'A == 1' }, | ||
| ] }); | ||
| expect(changes.map((c) => [c.Field, c.Changed, c.Applied])).toEqual([ | ||
| ['A', true, true], | ||
| ['B', false, true], | ||
| ['C', true, true], | ||
| ]); | ||
| }); | ||
| }); | ||
| describe('FieldTransformEngine (each transform type)', () => { | ||
| const e = new FieldTransformEngine(); | ||
| const step = (value, ...steps) => e.ExecutePipeline(value, {}, steps).Value; | ||
| it('regex / split / substring', () => { | ||
| expect(step('a-b-c', { Type: 'regex', Config: { Pattern: '-', Replacement: '_', Flags: 'g' } })).toBe('a_b_c'); | ||
| expect(step('a,b,c', { Type: 'split', Config: { Delimiter: ',', Index: 1 } })).toBe('b'); | ||
| expect(step('hello', { Type: 'substring', Config: { Start: 1, Length: 3 } })).toBe('ell'); | ||
| }); | ||
| it('combine pulls from fields', () => { | ||
| expect(e.ExecutePipeline(null, { A: 'x', B: 'y' }, [{ Type: 'combine', Config: { SourceFields: ['A', 'B'], Separator: '-' } }]).Value).toBe('x-y'); | ||
| }); | ||
| it('lookup (in-memory map, case-insensitive) + default', () => { | ||
| expect(step('ACTIVE', { Type: 'lookup', Config: { Map: { active: 1 }, Default: 0 } })).toBe(1); | ||
| expect(step('other', { Type: 'lookup', Config: { Map: { active: 1 }, Default: 0 } })).toBe(0); | ||
| }); | ||
| it('coerce number/boolean/string', () => { | ||
| expect(step('42', { Type: 'coerce', Config: { TargetType: 'number' } })).toBe(42); | ||
| expect(step('yes', { Type: 'coerce', Config: { TargetType: 'boolean' } })).toBe(true); | ||
| expect(step(7, { Type: 'coerce', Config: { TargetType: 'string' } })).toBe('7'); | ||
| }); | ||
| it('OnError=Skip drops the field; OnError=Fail propagates', () => { | ||
| expect(e.ExecutePipeline('x', {}, [{ Type: 'coerce', Config: { TargetType: 'number' }, OnError: 'Skip' }]).Skipped).toBe(true); | ||
| expect(() => e.ExecutePipeline('x', {}, [{ Type: 'coerce', Config: { TargetType: 'number' }, OnError: 'Fail' }])).toThrow(); | ||
| }); | ||
| it('a malformed custom expression compiles once and is reported', () => { | ||
| expect(() => e.ExecutePipeline('x', {}, [{ Type: 'custom', Config: { Expression: ')(' }, OnError: 'Fail' }])).toThrow(); | ||
| }); | ||
| }); | ||
| //# sourceMappingURL=FieldRulesEvaluator.test.js.map |
| {"version":3,"file":"FieldRulesEvaluator.test.js","sourceRoot":"","sources":["../../src/__tests__/FieldRulesEvaluator.test.ts"],"names":[],"mappings":"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"} |
| export {}; | ||
| //# sourceMappingURL=KeyedSerialTaskQueue.test.d.ts.map |
| {"version":3,"file":"KeyedSerialTaskQueue.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/KeyedSerialTaskQueue.test.ts"],"names":[],"mappings":""} |
| import { describe, it, expect } from 'vitest'; | ||
| import { KeyedSerialTaskQueue } from '../KeyedSerialTaskQueue.js'; | ||
| const delay = (ms) => new Promise((r) => setTimeout(r, ms)); | ||
| describe('KeyedSerialTaskQueue', () => { | ||
| it('serializes tasks for the same key in enqueue order', async () => { | ||
| const q = new KeyedSerialTaskQueue(); | ||
| const key = {}; | ||
| const order = []; | ||
| q.enqueue(key, async () => { await delay(20); order.push(1); }); | ||
| q.enqueue(key, async () => { await delay(5); order.push(2); }); | ||
| q.enqueue(key, async () => { order.push(3); }); | ||
| await q.flush(); | ||
| expect(order).toEqual([1, 2, 3]); | ||
| }); | ||
| it('runs tasks for different keys concurrently', async () => { | ||
| const q = new KeyedSerialTaskQueue(); | ||
| const order = []; | ||
| q.enqueue({}, async () => { await delay(30); order.push('slow'); }); | ||
| q.enqueue({}, async () => { order.push('fast'); }); | ||
| await q.flush(); | ||
| // Different keys → concurrent → the fast one finishes first despite enqueuing second. | ||
| expect(order).toEqual(['fast', 'slow']); | ||
| }); | ||
| it('is fire-and-forget — a thrown task resolves undefined, never rejects', async () => { | ||
| const q = new KeyedSerialTaskQueue(); | ||
| await expect(q.enqueue({}, async () => { throw new Error('boom'); })).resolves.toBeUndefined(); | ||
| }); | ||
| it('resolves the task value on success and undefined on isOk-falsy', async () => { | ||
| const q = new KeyedSerialTaskQueue(); | ||
| await expect(q.enqueue({}, async () => 'val', { isOk: () => true })).resolves.toBe('val'); | ||
| await expect(q.enqueue({}, async () => false, { isOk: (v) => v === true })).resolves.toBeUndefined(); | ||
| }); | ||
| it('flush reports failures (reject OR isOk-falsy) and rejections (threw) + routes to onError', async () => { | ||
| const errors = []; | ||
| const q = new KeyedSerialTaskQueue({ onError: (e) => errors.push(e) }); | ||
| q.enqueue({}, async () => 'ok'); | ||
| q.enqueue({}, async () => { throw new Error('boom'); }); | ||
| q.enqueue({}, async () => false, { isOk: (v) => v === true }); | ||
| const res = await q.flush(); | ||
| expect(res).toEqual({ failures: 2, rejections: 1 }); | ||
| expect(errors.length).toBe(2); | ||
| }); | ||
| it('drains on re-flush — a second flush only counts tasks enqueued after the first', async () => { | ||
| const q = new KeyedSerialTaskQueue(); | ||
| q.enqueue({}, async () => { throw new Error('a'); }); | ||
| expect(await q.flush()).toEqual({ failures: 1, rejections: 1 }); | ||
| q.enqueue({}, async () => 'ok'); | ||
| expect(await q.flush()).toEqual({ failures: 0, rejections: 0 }); | ||
| }); | ||
| it('flush with nothing pending resolves to zero counts', async () => { | ||
| const q = new KeyedSerialTaskQueue(); | ||
| expect(await q.flush()).toEqual({ failures: 0, rejections: 0 }); | ||
| }); | ||
| it('a failed task does not break the chain for its key', async () => { | ||
| const q = new KeyedSerialTaskQueue(); | ||
| const key = {}; | ||
| const order = []; | ||
| q.enqueue(key, async () => { throw new Error('boom'); }); | ||
| q.enqueue(key, async () => { order.push(2); }); | ||
| await q.flush(); | ||
| expect(order).toEqual([2]); | ||
| }); | ||
| it('passes the label to onError', async () => { | ||
| const seen = []; | ||
| const q = new KeyedSerialTaskQueue({ onError: (_e, label) => seen.push(label) }); | ||
| q.enqueue({}, async () => { throw new Error('boom'); }, { label: 'my-task' }); | ||
| await q.flush(); | ||
| expect(seen).toEqual(['my-task']); | ||
| }); | ||
| it('flush awaits tasks enqueued before it began, not ones enqueued after', async () => { | ||
| const q = new KeyedSerialTaskQueue(); | ||
| const done = []; | ||
| q.enqueue({}, async () => { done.push('first'); }); // fast — in the flush snapshot | ||
| const flushed = q.flush(); // snapshots only 'first' | ||
| q.enqueue({}, async () => { await delay(30); done.push('second'); }); // slow, after flush began | ||
| await flushed; | ||
| expect(done).toEqual(['first']); // flush awaited only 'first'; 'second' is still running | ||
| await q.flush(); // next window awaits 'second' | ||
| expect(done).toEqual(['first', 'second']); | ||
| }); | ||
| it('is self-bounding — many settled tasks do not accumulate (flush still awaits in-flight)', async () => { | ||
| const q = new KeyedSerialTaskQueue(); | ||
| for (let i = 0; i < 50; i++) { | ||
| q.enqueue({}, async () => { }); | ||
| } | ||
| // All settle; a flush after they've drained reports zero and resolves promptly. | ||
| await delay(5); | ||
| expect(await q.flush()).toEqual({ failures: 0, rejections: 0 }); | ||
| }); | ||
| }); | ||
| //# sourceMappingURL=KeyedSerialTaskQueue.test.js.map |
| {"version":3,"file":"KeyedSerialTaskQueue.test.js","sourceRoot":"","sources":["../../src/__tests__/KeyedSerialTaskQueue.test.ts"],"names":[],"mappings":"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"} |
| import type { EntityDocumentResolver, FieldChange, FieldRuleSet, LookupResolver, PromptResolver } from './rules.js'; | ||
| /** Options for an evaluator instance. */ | ||
| export interface FieldRulesEvaluatorOptions { | ||
| /** Resolver for `lookup` sources. Omit if no rule uses a lookup (a lookup rule then errors). */ | ||
| LookupResolver?: LookupResolver; | ||
| /** Resolver for `prompt` sources. Omit if no rule uses a prompt (a prompt rule then errors). */ | ||
| PromptResolver?: PromptResolver; | ||
| /** Resolver for `entityDocument` sources. Omit if no rule renders one (such a rule then errors). */ | ||
| EntityDocumentResolver?: EntityDocumentResolver; | ||
| } | ||
| /** | ||
| * Computes the field changes a {@link FieldRuleSet} would make to a record — WITHOUT mutating anything. | ||
| * | ||
| * Returning a list of {@link FieldChange} (old → new per rule) is what makes a dry-run preview possible: | ||
| * the caller decides whether to apply. Conditions are evaluated with the safe expression evaluator; | ||
| * value formulas + the transform pipeline run through the shared {@link FieldTransformEngine}; entity | ||
| * lookups go through the injected resolver. The engine never touches a database itself. | ||
| */ | ||
| export declare class FieldRulesEvaluator { | ||
| private readonly transforms; | ||
| private readonly conditions; | ||
| private readonly lookupResolver?; | ||
| private readonly promptResolver?; | ||
| private readonly entityDocumentResolver?; | ||
| constructor(options?: FieldRulesEvaluatorOptions); | ||
| /** Computes one {@link FieldChange} per rule for a record (a row of field name → current value). */ | ||
| ComputeChanges(record: Record<string, unknown>, ruleSet: FieldRuleSet): Promise<FieldChange[]>; | ||
| private evaluateRule; | ||
| private checkCondition; | ||
| private resolveSource; | ||
| private equal; | ||
| private message; | ||
| } | ||
| //# sourceMappingURL=FieldRulesEvaluator.d.ts.map |
| {"version":3,"file":"FieldRulesEvaluator.d.ts","sourceRoot":"","sources":["../../src/fieldRules/FieldRulesEvaluator.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACR,sBAAsB,EACtB,WAAW,EAEX,YAAY,EAEZ,cAAc,EACd,cAAc,EACjB,MAAM,SAAS,CAAC;AAEjB,yCAAyC;AACzC,MAAM,WAAW,0BAA0B;IACvC,gGAAgG;IAChG,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,gGAAgG;IAChG,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,oGAAoG;IACpG,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;CACnD;AAED;;;;;;;GAOG;AACH,qBAAa,mBAAmB;IAC5B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA8B;IACzD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiC;IAC5D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAiB;IACjD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAiB;IACjD,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAyB;gBAErD,OAAO,CAAC,EAAE,0BAA0B;IAMhD,oGAAoG;IACvF,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;YAQ7F,YAAY;IAoC1B,OAAO,CAAC,cAAc;YAWR,aAAa;IA6C3B,OAAO,CAAC,KAAK;IAOb,OAAO,CAAC,OAAO;CAGlB"} |
| import { SafeExpressionEvaluator } from '../SafeExpressionEvaluator.js'; | ||
| import { FieldTransformEngine } from './FieldTransformEngine.js'; | ||
| /** | ||
| * Computes the field changes a {@link FieldRuleSet} would make to a record — WITHOUT mutating anything. | ||
| * | ||
| * Returning a list of {@link FieldChange} (old → new per rule) is what makes a dry-run preview possible: | ||
| * the caller decides whether to apply. Conditions are evaluated with the safe expression evaluator; | ||
| * value formulas + the transform pipeline run through the shared {@link FieldTransformEngine}; entity | ||
| * lookups go through the injected resolver. The engine never touches a database itself. | ||
| */ | ||
| export class FieldRulesEvaluator { | ||
| constructor(options) { | ||
| this.transforms = new FieldTransformEngine(); | ||
| this.conditions = new SafeExpressionEvaluator(); | ||
| this.lookupResolver = options?.LookupResolver; | ||
| this.promptResolver = options?.PromptResolver; | ||
| this.entityDocumentResolver = options?.EntityDocumentResolver; | ||
| } | ||
| /** Computes one {@link FieldChange} per rule for a record (a row of field name → current value). */ | ||
| async ComputeChanges(record, ruleSet) { | ||
| const changes = []; | ||
| for (const rule of ruleSet.Rules) { | ||
| changes.push(await this.evaluateRule(record, rule)); | ||
| } | ||
| return changes; | ||
| } | ||
| async evaluateRule(record, rule) { | ||
| const oldValue = record[rule.TargetField]; | ||
| const unchanged = (extra) => ({ | ||
| Field: rule.TargetField, OldValue: oldValue, NewValue: oldValue, Changed: false, Applied: false, ...extra, | ||
| }); | ||
| const condition = this.checkCondition(record, rule); | ||
| if (condition.error) | ||
| return unchanged({ Error: condition.error }); | ||
| if (!condition.passed) | ||
| return unchanged({ SkipReason: 'condition evaluated false' }); | ||
| let value; | ||
| try { | ||
| value = await this.resolveSource(record, rule.Source); | ||
| } | ||
| catch (err) { | ||
| return unchanged({ Error: `source: ${this.message(err)}` }); | ||
| } | ||
| if (rule.Transforms?.length) { | ||
| try { | ||
| const result = this.transforms.ExecutePipeline(value, record, rule.Transforms); | ||
| if (result.Skipped) | ||
| return unchanged({ SkipReason: 'transform skipped the field' }); | ||
| value = result.Value; | ||
| } | ||
| catch (err) { | ||
| return unchanged({ Error: `transform: ${this.message(err)}` }); | ||
| } | ||
| } | ||
| return { | ||
| Field: rule.TargetField, | ||
| OldValue: oldValue, | ||
| NewValue: value, | ||
| Changed: !this.equal(oldValue, value), | ||
| Applied: true, | ||
| }; | ||
| } | ||
| checkCondition(record, rule) { | ||
| if (!rule.Condition) { | ||
| return { passed: true }; | ||
| } | ||
| const result = this.conditions.evaluate(rule.Condition, record); | ||
| if (!result.success) { | ||
| return { passed: false, error: `condition: ${result.error ?? 'invalid expression'}` }; | ||
| } | ||
| return { passed: result.value === true }; | ||
| } | ||
| async resolveSource(record, source) { | ||
| switch (source.Kind) { | ||
| case 'static': | ||
| return source.Value; | ||
| case 'field': | ||
| return record[source.Field]; | ||
| case 'formula': | ||
| return this.transforms.Evaluate(source.Expression, undefined, record); | ||
| case 'lookup': { | ||
| if (!this.lookupResolver) { | ||
| throw new Error('a lookup rule was used but no LookupResolver was provided'); | ||
| } | ||
| const matchValue = await this.resolveSource(record, source.Lookup.MatchValue); | ||
| const resolved = await this.lookupResolver({ | ||
| Entity: source.Lookup.Entity, | ||
| MatchField: source.Lookup.MatchField, | ||
| MatchValue: matchValue, | ||
| ReturnField: source.Lookup.ReturnField, | ||
| }); | ||
| return resolved === undefined ? (source.Lookup.Default ?? null) : resolved; | ||
| } | ||
| case 'prompt': { | ||
| if (!this.promptResolver) { | ||
| throw new Error('a prompt rule was used but no PromptResolver was provided'); | ||
| } | ||
| // By default hand the WHOLE record to the prompt; otherwise resolve each shaped Data entry | ||
| // through the same source machinery (so Data values can be fields, formulas, statics, …). | ||
| let data = record; | ||
| if (source.Prompt.Data) { | ||
| data = {}; | ||
| for (const [key, valueSource] of Object.entries(source.Prompt.Data)) { | ||
| data[key] = await this.resolveSource(record, valueSource); | ||
| } | ||
| } | ||
| return this.promptResolver({ PromptID: source.Prompt.PromptID, Data: data }); | ||
| } | ||
| case 'entityDocument': { | ||
| if (!this.entityDocumentResolver) { | ||
| throw new Error('an entityDocument rule was used but no EntityDocumentResolver was provided'); | ||
| } | ||
| return this.entityDocumentResolver({ EntityDocumentID: source.EntityDocument?.EntityDocumentID, Record: record }); | ||
| } | ||
| } | ||
| } | ||
| equal(a, b) { | ||
| if (a === b) | ||
| return true; | ||
| if (a instanceof Date && b instanceof Date) | ||
| return a.getTime() === b.getTime(); | ||
| if (a == null && b == null) | ||
| return true; | ||
| return false; | ||
| } | ||
| message(err) { | ||
| return err instanceof Error ? err.message : String(err); | ||
| } | ||
| } | ||
| //# sourceMappingURL=FieldRulesEvaluator.js.map |
| {"version":3,"file":"FieldRulesEvaluator.js","sourceRoot":"","sources":["../../src/fieldRules/FieldRulesEvaluator.ts"],"names":[],"mappings":"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"} |
| import type { TransformStep, TransformStepResult } from './transforms.js'; | ||
| /** Signature of a compiled custom/formula expression: `(value, fields) => result`. */ | ||
| export type CompiledExpression = (value: unknown, fields: Record<string, unknown>) => unknown; | ||
| /** | ||
| * Framework-agnostic engine that runs a field-value transform pipeline over a single `(value, fields)` | ||
| * pair. Lifted from the MemberJunction integration field-mapping engine so the integration sync AND the | ||
| * rules-based bulk-update processor share one implementation of every transform type. | ||
| * | ||
| * Holds an LRU cache of compiled custom/formula expressions (an identical expression compiles once, not | ||
| * once per record). A failed compile is cached as the `Error` it threw and re-thrown from cache, so a | ||
| * malformed expression is compiled exactly once across a whole batch. | ||
| */ | ||
| export declare class FieldTransformEngine { | ||
| private readonly compiledExpressionCache; | ||
| /** | ||
| * Runs `value` through every step of `steps`, returning the final value (or `Skipped` when an | ||
| * OnError='Skip' step drops the field). `fields` is the full record, available to combine/custom steps. | ||
| */ | ||
| ExecutePipeline(value: unknown, fields: Record<string, unknown>, steps: TransformStep[]): TransformStepResult; | ||
| /** | ||
| * Executes a single transform step, applying the configured transformation and handling errors | ||
| * according to the step's OnError strategy (default 'Null' — grace: null the field, keep going). | ||
| */ | ||
| ExecuteStep(step: TransformStep, value: unknown, fields: Record<string, unknown>): TransformStepResult; | ||
| /** | ||
| * Evaluates a value-producing expression against the record. Reuses the compiled-expression cache. | ||
| * The expression sees the current field value as `value` and all record fields as `fields` | ||
| * (e.g. `fields.FirstName + ' ' + fields.LastName`). | ||
| */ | ||
| Evaluate(expression: string, value: unknown, fields: Record<string, unknown>): unknown; | ||
| private dispatch; | ||
| private handleError; | ||
| private applyDirect; | ||
| private applyRegex; | ||
| private applySplit; | ||
| private applyCombine; | ||
| private applyLookup; | ||
| private applyFormat; | ||
| private applyCoerce; | ||
| private applySubstring; | ||
| private getCompiled; | ||
| private compile; | ||
| } | ||
| //# sourceMappingURL=FieldTransformEngine.d.ts.map |
| {"version":3,"file":"FieldTransformEngine.d.ts","sourceRoot":"","sources":["../../src/fieldRules/FieldTransformEngine.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACR,aAAa,EAEb,mBAAmB,EAUtB,MAAM,cAAc,CAAC;AAEtB,sFAAsF;AACtF,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;AAE9F;;;;;;;;GAQG;AACH,qBAAa,oBAAoB;IAC7B,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAwD;IAEhG;;;OAGG;IACI,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,mBAAmB;IAYpH;;;OAGG;IACI,WAAW,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,mBAAmB;IAS7G;;;;OAIG;IACI,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAI7F,OAAO,CAAC,QAAQ;IAoChB,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,WAAW;IA6BnB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,OAAO;CAQlB"} |
| import { MJLruCache } from '../MJLruCache.js'; | ||
| import { GetFieldTransform } from './transformRegistry.js'; | ||
| /** | ||
| * Framework-agnostic engine that runs a field-value transform pipeline over a single `(value, fields)` | ||
| * pair. Lifted from the MemberJunction integration field-mapping engine so the integration sync AND the | ||
| * rules-based bulk-update processor share one implementation of every transform type. | ||
| * | ||
| * Holds an LRU cache of compiled custom/formula expressions (an identical expression compiles once, not | ||
| * once per record). A failed compile is cached as the `Error` it threw and re-thrown from cache, so a | ||
| * malformed expression is compiled exactly once across a whole batch. | ||
| */ | ||
| export class FieldTransformEngine { | ||
| constructor() { | ||
| this.compiledExpressionCache = new MJLruCache(); | ||
| } | ||
| /** | ||
| * Runs `value` through every step of `steps`, returning the final value (or `Skipped` when an | ||
| * OnError='Skip' step drops the field). `fields` is the full record, available to combine/custom steps. | ||
| */ | ||
| ExecutePipeline(value, fields, steps) { | ||
| let current = value; | ||
| for (const step of steps) { | ||
| const result = this.ExecuteStep(step, current, fields); | ||
| if (result.Skipped) { | ||
| return result; | ||
| } | ||
| current = result.Value; | ||
| } | ||
| return { Value: current, Skipped: false }; | ||
| } | ||
| /** | ||
| * Executes a single transform step, applying the configured transformation and handling errors | ||
| * according to the step's OnError strategy (default 'Null' — grace: null the field, keep going). | ||
| */ | ||
| ExecuteStep(step, value, fields) { | ||
| const onError = step.OnError ?? 'Null'; | ||
| try { | ||
| return { Value: this.dispatch(step, value, fields), Skipped: false }; | ||
| } | ||
| catch (err) { | ||
| return this.handleError(onError, err); | ||
| } | ||
| } | ||
| /** | ||
| * Evaluates a value-producing expression against the record. Reuses the compiled-expression cache. | ||
| * The expression sees the current field value as `value` and all record fields as `fields` | ||
| * (e.g. `fields.FirstName + ' ' + fields.LastName`). | ||
| */ | ||
| Evaluate(expression, value, fields) { | ||
| return this.getCompiled(expression)(value, fields); | ||
| } | ||
| dispatch(step, value, fields) { | ||
| switch (step.Type) { | ||
| case 'direct': | ||
| return this.applyDirect(value, step.Config); | ||
| case 'regex': | ||
| return this.applyRegex(value, step.Config); | ||
| case 'split': | ||
| return this.applySplit(value, step.Config); | ||
| case 'combine': | ||
| return this.applyCombine(fields, step.Config); | ||
| case 'lookup': | ||
| return this.applyLookup(value, step.Config); | ||
| case 'format': | ||
| return this.applyFormat(value, step.Config); | ||
| case 'coerce': | ||
| return this.applyCoerce(value, step.Config); | ||
| case 'substring': | ||
| return this.applySubstring(value, step.Config); | ||
| case 'custom': | ||
| try { | ||
| return this.Evaluate(step.Config.Expression, value, fields); | ||
| } | ||
| catch (err) { | ||
| throw new Error(`Custom transform expression failed: ${err instanceof Error ? err.message : String(err)}`); | ||
| } | ||
| default: { | ||
| // Not a built-in — dispatch to a registered plugin (e.g. 'jsonpath' / 'xpath', whose | ||
| // libraries must not be deps of this package). See {@link RegisterFieldTransform}. | ||
| const plugin = GetFieldTransform(step.Type); | ||
| if (plugin) { | ||
| return plugin(value, fields, step.Config); | ||
| } | ||
| throw new Error(`Unknown transform type '${step.Type}'. If it is a plugin transform (e.g. 'jsonpath'/'xpath'), import the package that registers it before running.`); | ||
| } | ||
| } | ||
| } | ||
| handleError(onError, err) { | ||
| switch (onError) { | ||
| case 'Skip': | ||
| return { Value: undefined, Skipped: true }; | ||
| case 'Null': | ||
| return { Value: null, Skipped: false }; | ||
| case 'Fail': | ||
| throw err; | ||
| } | ||
| } | ||
| applyDirect(value, config) { | ||
| return value == null && config.DefaultValue !== undefined ? config.DefaultValue : value; | ||
| } | ||
| applyRegex(value, config) { | ||
| return String(value ?? '').replace(new RegExp(config.Pattern, config.Flags ?? ''), config.Replacement); | ||
| } | ||
| applySplit(value, config) { | ||
| return String(value ?? '').split(config.Delimiter)[config.Index] ?? null; | ||
| } | ||
| applyCombine(fields, config) { | ||
| return config.SourceFields.map((f) => String(fields[f] ?? '')).join(config.Separator); | ||
| } | ||
| applyLookup(value, config) { | ||
| const lowerKey = String(value ?? '').toLowerCase(); | ||
| const entry = Object.entries(config.Map).find(([key]) => key.toLowerCase() === lowerKey); | ||
| return entry ? entry[1] : (config.Default ?? null); | ||
| } | ||
| applyFormat(value, config) { | ||
| if (config.FormatType === 'date') { | ||
| const dateVal = value instanceof Date ? value : new Date(String(value)); | ||
| if (isNaN(dateVal.getTime())) { | ||
| throw new Error(`Cannot format "${String(value)}" as a date`); | ||
| } | ||
| return config.FormatString.toLowerCase() === 'iso' || config.FormatString === 'ISO8601' | ||
| ? dateVal.toISOString() | ||
| : dateVal.toLocaleDateString('en-US'); | ||
| } | ||
| if (config.FormatType === 'number') { | ||
| const numVal = Number(value); | ||
| if (!Number.isFinite(numVal)) { | ||
| throw new Error(`Cannot format "${String(value)}" as a number`); | ||
| } | ||
| return numVal.toFixed(Number(config.FormatString) || 0); | ||
| } | ||
| return String(value ?? ''); | ||
| } | ||
| applyCoerce(value, config) { | ||
| switch (config.TargetType) { | ||
| case 'string': | ||
| return String(value ?? ''); | ||
| case 'number': { | ||
| const num = Number(value); | ||
| if (isNaN(num)) { | ||
| throw new Error(`Cannot coerce "${String(value)}" to number`); | ||
| } | ||
| return num; | ||
| } | ||
| case 'boolean': { | ||
| if (typeof value === 'boolean') | ||
| return value; | ||
| if (typeof value === 'number') | ||
| return value !== 0; | ||
| const s = String(value).toLowerCase().trim(); | ||
| return s === 'true' || s === '1' || s === 'yes'; | ||
| } | ||
| case 'date': { | ||
| const d = new Date(String(value)); | ||
| if (isNaN(d.getTime())) { | ||
| throw new Error(`Cannot coerce "${String(value)}" to date`); | ||
| } | ||
| return d; | ||
| } | ||
| default: | ||
| throw new Error(`Unknown coerce target type: ${config.TargetType}`); | ||
| } | ||
| } | ||
| applySubstring(value, config) { | ||
| const s = String(value ?? ''); | ||
| return config.Length !== undefined ? s.substring(config.Start, config.Start + config.Length) : s.substring(config.Start); | ||
| } | ||
| getCompiled(expression) { | ||
| let cached = this.compiledExpressionCache.Get(expression); | ||
| if (cached === undefined) { | ||
| cached = this.compile(expression); | ||
| this.compiledExpressionCache.Set(expression, cached); | ||
| } | ||
| if (cached instanceof Error) { | ||
| throw cached; | ||
| } | ||
| return cached; | ||
| } | ||
| compile(expression) { | ||
| try { | ||
| // eslint-disable-next-line @typescript-eslint/no-implied-eval | ||
| return new Function('value', 'fields', `return (${expression});`); | ||
| } | ||
| catch (err) { | ||
| return err instanceof Error ? err : new Error(String(err)); | ||
| } | ||
| } | ||
| } | ||
| //# sourceMappingURL=FieldTransformEngine.js.map |
| {"version":3,"file":"FieldTransformEngine.js","sourceRoot":"","sources":["../../src/fieldRules/FieldTransformEngine.ts"],"names":[],"mappings":"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"} |
| export * from './transforms.js'; | ||
| export * from './transformRegistry.js'; | ||
| export * from './FieldTransformEngine.js'; | ||
| export * from './rules.js'; | ||
| export * from './FieldRulesEvaluator.js'; | ||
| //# sourceMappingURL=index.d.ts.map |
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fieldRules/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC"} |
| export * from './transforms.js'; | ||
| export * from './transformRegistry.js'; | ||
| export * from './FieldTransformEngine.js'; | ||
| export * from './rules.js'; | ||
| export * from './FieldRulesEvaluator.js'; | ||
| //# sourceMappingURL=index.js.map |
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/fieldRules/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC"} |
| import type { TransformStep } from './transforms.js'; | ||
| /** | ||
| * How a field rule produces its base value (before any transform pipeline runs): | ||
| * - `static` — a literal constant | ||
| * - `field` — copy another field's current value | ||
| * - `formula` — a JS value expression over the record (`fields.X`), e.g. `fields.FirstName + ' ' + fields.LastName` | ||
| * - `lookup` — resolve a value from another entity by matching a key (requires a LookupResolver) | ||
| * - `prompt` — run an AI prompt over the record and use its output (requires a PromptResolver). By | ||
| * default the WHOLE record is passed as the prompt's data; supply `Data` to send a narrower / shaped | ||
| * context (e.g. a rendered entity-document of the record). Powerful but one model call PER record — | ||
| * use the dry-run preview + bounded concurrency, and prefer it for enrichment/summarization fields. | ||
| */ | ||
| export type FieldRuleValueSource = { | ||
| Kind: 'static'; | ||
| Value: unknown; | ||
| } | { | ||
| Kind: 'field'; | ||
| Field: string; | ||
| } | { | ||
| Kind: 'formula'; | ||
| Expression: string; | ||
| } | { | ||
| Kind: 'lookup'; | ||
| Lookup: FieldRuleLookup; | ||
| } | { | ||
| Kind: 'prompt'; | ||
| Prompt: FieldRulePrompt; | ||
| } | { | ||
| Kind: 'entityDocument'; | ||
| EntityDocument?: FieldRuleEntityDocument; | ||
| }; | ||
| /** | ||
| * Renders the record as text via an MJ Entity Document template (the same templates used for | ||
| * embeddings/RAG), using the existing rendering infrastructure through an injected | ||
| * {@link EntityDocumentResolver}. Most useful inside a `prompt` source's `Data` — hand the LLM a rich, | ||
| * curated rendering of the record instead of raw fields. | ||
| */ | ||
| export interface FieldRuleEntityDocument { | ||
| /** Which Entity Document template to render. Omit to use the entity's default/primary document. */ | ||
| EntityDocumentID?: string; | ||
| } | ||
| /** An AI prompt source: run a prompt over the record and use its output as the field value. */ | ||
| export interface FieldRulePrompt { | ||
| /** The AI prompt to run (its ID). */ | ||
| PromptID: string; | ||
| /** | ||
| * Optional shaping of the data handed to the prompt. Omit to pass the whole record. Each entry's | ||
| * value is computed like any other source (`field` / `formula` / `static`), so you can pass selected | ||
| * fields, derived values, or a pre-rendered entity-document string under named keys. | ||
| */ | ||
| Data?: Record<string, FieldRuleValueSource>; | ||
| } | ||
| /** An entity lookup: find a record where MatchField === (computed) MatchValue, return ReturnField. */ | ||
| export interface FieldRuleLookup { | ||
| /** Entity to look in (e.g. 'Accounts'). */ | ||
| Entity: string; | ||
| /** Field on the lookup entity to match against. */ | ||
| MatchField: string; | ||
| /** How to compute the value to match (static / field / formula). */ | ||
| MatchValue: FieldRuleValueSource; | ||
| /** Field to return from the matched record. */ | ||
| ReturnField: string; | ||
| /** Value to use when no record matches. */ | ||
| Default?: unknown; | ||
| } | ||
| /** A single rule: set TargetField from Source (optionally transformed), gated by an optional Condition. */ | ||
| export interface FieldRule { | ||
| /** The field on the record to set. */ | ||
| TargetField: string; | ||
| /** How to produce the base value. */ | ||
| Source: FieldRuleValueSource; | ||
| /** Optional transform pipeline applied to the computed value. */ | ||
| Transforms?: TransformStep[]; | ||
| /** Optional boolean expression over the record — the rule only applies when it evaluates true. */ | ||
| Condition?: string; | ||
| } | ||
| /** An ordered set of field rules applied to each record. */ | ||
| export interface FieldRuleSet { | ||
| Rules: FieldRule[]; | ||
| } | ||
| /** The computed outcome of one rule for one record — the unit of a dry-run preview. */ | ||
| export interface FieldChange { | ||
| /** The target field. */ | ||
| Field: string; | ||
| /** The record's current value. */ | ||
| OldValue: unknown; | ||
| /** The computed new value (equals OldValue when not Applied). */ | ||
| NewValue: unknown; | ||
| /** True when NewValue differs from OldValue. */ | ||
| Changed: boolean; | ||
| /** True when the rule produced a value to write (condition passed, no skip/error). */ | ||
| Applied: boolean; | ||
| /** Why the rule did not apply (condition false / transform skipped). */ | ||
| SkipReason?: string; | ||
| /** Set when computing the value errored (condition or source/transform). */ | ||
| Error?: string; | ||
| } | ||
| /** A lookup with its MatchValue already resolved to a concrete value, handed to the LookupResolver. */ | ||
| export interface ResolvedLookup { | ||
| Entity: string; | ||
| MatchField: string; | ||
| MatchValue: unknown; | ||
| ReturnField: string; | ||
| } | ||
| /** | ||
| * Resolves an entity lookup to a value. Injected into the evaluator so the engine stays framework-pure | ||
| * (the bulk-update processor supplies a RunView-backed resolver; unit tests supply a stub). | ||
| * Should return the matched record's ReturnField value, or `undefined` when nothing matches. | ||
| */ | ||
| export type LookupResolver = (lookup: ResolvedLookup) => Promise<unknown>; | ||
| /** A prompt source with its data already resolved to concrete values, handed to the PromptResolver. */ | ||
| export interface ResolvedPrompt { | ||
| PromptID: string; | ||
| /** The data context for the prompt run (the whole record by default, or the shaped `Data` map). */ | ||
| Data: Record<string, unknown>; | ||
| } | ||
| /** | ||
| * Runs an AI prompt and returns its output value. Injected into the evaluator so the engine itself never | ||
| * depends on the AI stack — the bulk-update processor supplies a real `AIPromptRunner`-backed resolver; | ||
| * unit tests supply a stub. This is the same dependency-inversion seam as {@link LookupResolver}. | ||
| */ | ||
| export type PromptResolver = (prompt: ResolvedPrompt) => Promise<unknown>; | ||
| /** An entity-document source with its record handed to the {@link EntityDocumentResolver} to render. */ | ||
| export interface ResolvedEntityDocument { | ||
| EntityDocumentID?: string; | ||
| /** The record's field values, to render through the Entity Document template. */ | ||
| Record: Record<string, unknown>; | ||
| } | ||
| /** | ||
| * Renders a record to text via the existing Entity Document / Template infrastructure. Injected so the | ||
| * engine never depends on `@memberjunction/templates` or the AI/vector stack — the bulk-update processor | ||
| * supplies a resolver backed by the real render infra. Same dependency-inversion seam as the others. | ||
| */ | ||
| export type EntityDocumentResolver = (doc: ResolvedEntityDocument) => Promise<string>; | ||
| //# sourceMappingURL=rules.d.ts.map |
| {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/fieldRules/rules.ts"],"names":[],"mappings":"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"} |
| export {}; | ||
| //# sourceMappingURL=rules.js.map |
| {"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/fieldRules/rules.ts"],"names":[],"mappings":""} |
| /** | ||
| * A pluggable transform handler: `(value, fields, config) => result`. Same call shape as the built-in | ||
| * transforms, so a plugin is indistinguishable from a built-in at the call site. | ||
| */ | ||
| export type FieldTransformPlugin = (value: unknown, fields: Record<string, unknown>, config: unknown) => unknown; | ||
| /** | ||
| * Registers a transform plugin by type name so the {@link FieldTransformEngine} can dispatch a | ||
| * non-built-in transform to it. | ||
| * | ||
| * This is how heavier transforms (e.g. `jsonpath`, `xpath`) are added WITHOUT making their libraries | ||
| * (jsonpath-plus, xpath, @xmldom/xmldom, …) dependencies of this foundational package: a separate | ||
| * package owns the libs, implements the handler, and calls this at module-load to register it. Any | ||
| * `FieldTransformEngine` in the process (integration sync, rules-based bulk update, …) then supports the | ||
| * transform — the engine stays lib-free, the plugin owns the deps. Idempotent per type (last wins). | ||
| * | ||
| * @param type - The transform `Type` string (case-insensitive), e.g. `'jsonpath'`. | ||
| * @param plugin - The handler. | ||
| */ | ||
| export declare function RegisterFieldTransform(type: string, plugin: FieldTransformPlugin): void; | ||
| /** Returns the registered plugin for a transform type, or `undefined` if none is registered. */ | ||
| export declare function GetFieldTransform(type: string): FieldTransformPlugin | undefined; | ||
| //# sourceMappingURL=transformRegistry.d.ts.map |
| {"version":3,"file":"transformRegistry.d.ts","sourceRoot":"","sources":["../../src/fieldRules/transformRegistry.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;AAejH;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAEvF;AAED,gGAAgG;AAChG,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAEhF"} |
| import { GetGlobalObjectStore } from '../util.js'; | ||
| /** Global-object-store key so registrations survive bundlers that duplicate this module. */ | ||
| const STORE_KEY = '__mj_FieldTransformPlugins__'; | ||
| function registry() { | ||
| const store = (GetGlobalObjectStore() ?? globalThis); | ||
| let map = store[STORE_KEY]; | ||
| if (!map) { | ||
| map = new Map(); | ||
| store[STORE_KEY] = map; | ||
| } | ||
| return map; | ||
| } | ||
| /** | ||
| * Registers a transform plugin by type name so the {@link FieldTransformEngine} can dispatch a | ||
| * non-built-in transform to it. | ||
| * | ||
| * This is how heavier transforms (e.g. `jsonpath`, `xpath`) are added WITHOUT making their libraries | ||
| * (jsonpath-plus, xpath, @xmldom/xmldom, …) dependencies of this foundational package: a separate | ||
| * package owns the libs, implements the handler, and calls this at module-load to register it. Any | ||
| * `FieldTransformEngine` in the process (integration sync, rules-based bulk update, …) then supports the | ||
| * transform — the engine stays lib-free, the plugin owns the deps. Idempotent per type (last wins). | ||
| * | ||
| * @param type - The transform `Type` string (case-insensitive), e.g. `'jsonpath'`. | ||
| * @param plugin - The handler. | ||
| */ | ||
| export function RegisterFieldTransform(type, plugin) { | ||
| registry().set(type.trim().toLowerCase(), plugin); | ||
| } | ||
| /** Returns the registered plugin for a transform type, or `undefined` if none is registered. */ | ||
| export function GetFieldTransform(type) { | ||
| return registry().get(type.trim().toLowerCase()); | ||
| } | ||
| //# sourceMappingURL=transformRegistry.js.map |
| {"version":3,"file":"transformRegistry.js","sourceRoot":"","sources":["../../src/fieldRules/transformRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAQ/C,4FAA4F;AAC5F,MAAM,SAAS,GAAG,8BAA8B,CAAC;AAEjD,SAAS,QAAQ;IACb,MAAM,KAAK,GAAG,CAAC,oBAAoB,EAAE,IAAK,UAAiD,CAA4B,CAAC;IACxH,IAAI,GAAG,GAAG,KAAK,CAAC,SAAS,CAAkD,CAAC;IAC5E,IAAI,CAAC,GAAG,EAAE,CAAC;QACP,GAAG,GAAG,IAAI,GAAG,EAAgC,CAAC;QAC9C,KAAK,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC;IAC3B,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY,EAAE,MAA4B;IAC7E,QAAQ,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,MAAM,CAAC,CAAC;AACtD,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC1C,OAAO,QAAQ,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AACrD,CAAC"} |
| /** | ||
| * Field-value transform pipeline types. | ||
| * | ||
| * This package is the canonical home for these types. The MemberJunction integration field-mapping | ||
| * engine and the rules-based bulk-update processor both consume them — author once, reuse everywhere. | ||
| */ | ||
| /** Supported transform types for field value transformation */ | ||
| export type TransformType = 'direct' | 'regex' | 'split' | 'combine' | 'lookup' | 'format' | 'coerce' | 'substring' | 'custom' | 'jsonpath' | 'xpath'; | ||
| /** What to do when a transform step encounters an error */ | ||
| export type TransformOnError = 'Skip' | 'Null' | 'Fail'; | ||
| /** A single step in a field value transformation pipeline */ | ||
| export interface TransformStep { | ||
| /** The type of transform to apply */ | ||
| Type: TransformType; | ||
| /** Configuration for the transform step */ | ||
| Config: TransformConfig; | ||
| /** Error handling strategy for this step. Defaults to 'Null' (grace — null the field, keep going). */ | ||
| OnError?: TransformOnError; | ||
| } | ||
| /** Configuration for extracting value(s) from JSON via a JSONPath expression. */ | ||
| export interface JsonPathConfig { | ||
| /** JSONPath expression, e.g. `$.store.book[0].title` or `$..author`. */ | ||
| Path: string; | ||
| /** Return only the first match (default). When false, returns the full array of matches. */ | ||
| First?: boolean; | ||
| } | ||
| /** Configuration for extracting value(s) from XML via an XPath expression. */ | ||
| export interface XPathConfig { | ||
| /** XPath expression, e.g. `/catalog/book[1]/title/text()` or `//author`. */ | ||
| Path: string; | ||
| /** Return only the first match (default). When false, returns the full array of matches. */ | ||
| First?: boolean; | ||
| } | ||
| /** Union of all transform configuration types */ | ||
| export type TransformConfig = DirectConfig | RegexConfig | SplitConfig | CombineConfig | LookupConfig | FormatConfig | JsonPathConfig | XPathConfig | CoerceConfig | SubstringConfig | CustomConfig; | ||
| /** Configuration for direct pass-through (no transformation) */ | ||
| export interface DirectConfig { | ||
| /** Optional default value if source is null/undefined */ | ||
| DefaultValue?: unknown; | ||
| } | ||
| /** Configuration for regex-based string transformation */ | ||
| export interface RegexConfig { | ||
| /** Regular expression pattern to match */ | ||
| Pattern: string; | ||
| /** Replacement string (supports $1, $2, etc. for capture groups) */ | ||
| Replacement: string; | ||
| /** Regex flags (e.g., 'gi' for global, case-insensitive) */ | ||
| Flags?: string; | ||
| } | ||
| /** Configuration for splitting a string value and extracting a portion */ | ||
| export interface SplitConfig { | ||
| /** Delimiter to split on */ | ||
| Delimiter: string; | ||
| /** Zero-based index of the part to extract */ | ||
| Index: number; | ||
| } | ||
| /** Configuration for combining multiple source field values */ | ||
| export interface CombineConfig { | ||
| /** Names of source fields to combine */ | ||
| SourceFields: string[]; | ||
| /** Separator to place between combined values */ | ||
| Separator: string; | ||
| } | ||
| /** Configuration for value lookup/mapping (in-memory translation table — not an entity lookup) */ | ||
| export interface LookupConfig { | ||
| /** Map of source values to destination values. Keys are matched case-insensitively */ | ||
| Map: Record<string, unknown>; | ||
| /** Value to use when the source value is not found in the map */ | ||
| Default?: unknown; | ||
| } | ||
| /** Configuration for date/number formatting */ | ||
| export interface FormatConfig { | ||
| /** The target format string. For dates: ISO8601 locale format specifiers */ | ||
| FormatString: string; | ||
| /** The type of formatting to apply */ | ||
| FormatType: 'date' | 'number' | 'string'; | ||
| } | ||
| /** Configuration for type coercion */ | ||
| export interface CoerceConfig { | ||
| /** Target type to coerce the value into */ | ||
| TargetType: 'string' | 'number' | 'boolean' | 'date'; | ||
| } | ||
| /** Configuration for extracting a substring */ | ||
| export interface SubstringConfig { | ||
| /** Start index (zero-based) */ | ||
| Start: number; | ||
| /** Number of characters to extract. If omitted, extracts to end */ | ||
| Length?: number; | ||
| } | ||
| /** Configuration for custom JavaScript expression evaluation */ | ||
| export interface CustomConfig { | ||
| /** JavaScript expression to evaluate. The source value is available as 'value' and all fields as 'fields' */ | ||
| Expression: string; | ||
| } | ||
| /** Result of running a transform step or pipeline. */ | ||
| export interface TransformStepResult { | ||
| /** The resulting value (undefined when Skipped). */ | ||
| Value: unknown; | ||
| /** True when an OnError='Skip' step asked to drop the field entirely. */ | ||
| Skipped: boolean; | ||
| } | ||
| //# sourceMappingURL=transforms.d.ts.map |
| {"version":3,"file":"transforms.d.ts","sourceRoot":"","sources":["../../src/fieldRules/transforms.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,+DAA+D;AAC/D,MAAM,MAAM,aAAa,GACnB,QAAQ,GACR,OAAO,GACP,OAAO,GACP,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,WAAW,GACX,QAAQ,GACR,UAAU,GACV,OAAO,CAAC;AAEd,2DAA2D;AAC3D,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAExD,6DAA6D;AAC7D,MAAM,WAAW,aAAa;IAC1B,qCAAqC;IACrC,IAAI,EAAE,aAAa,CAAC;IACpB,2CAA2C;IAC3C,MAAM,EAAE,eAAe,CAAC;IACxB,sGAAsG;IACtG,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AAED,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC3B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,4FAA4F;IAC5F,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IACxB,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,4FAA4F;IAC5F,KAAK,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,iDAAiD;AACjD,MAAM,MAAM,eAAe,GACrB,YAAY,GACZ,WAAW,GACX,WAAW,GACX,aAAa,GACb,YAAY,GACZ,YAAY,GACZ,cAAc,GACd,WAAW,GACX,YAAY,GACZ,eAAe,GACf,YAAY,CAAC;AAEnB,gEAAgE;AAChE,MAAM,WAAW,YAAY;IACzB,yDAAyD;IACzD,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,0DAA0D;AAC1D,MAAM,WAAW,WAAW;IACxB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,0EAA0E;AAC1E,MAAM,WAAW,WAAW;IACxB,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,+DAA+D;AAC/D,MAAM,WAAW,aAAa;IAC1B,wCAAwC;IACxC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,kGAAkG;AAClG,MAAM,WAAW,YAAY;IACzB,sFAAsF;IACtF,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,+CAA+C;AAC/C,MAAM,WAAW,YAAY;IACzB,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;CAC5C;AAED,sCAAsC;AACtC,MAAM,WAAW,YAAY;IACzB,2CAA2C;IAC3C,UAAU,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;CACxD;AAED,+CAA+C;AAC/C,MAAM,WAAW,eAAe;IAC5B,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,gEAAgE;AAChE,MAAM,WAAW,YAAY;IACzB,6GAA6G;IAC7G,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,sDAAsD;AACtD,MAAM,WAAW,mBAAmB;IAChC,oDAAoD;IACpD,KAAK,EAAE,OAAO,CAAC;IACf,yEAAyE;IACzE,OAAO,EAAE,OAAO,CAAC;CACpB"} |
| /** | ||
| * Field-value transform pipeline types. | ||
| * | ||
| * This package is the canonical home for these types. The MemberJunction integration field-mapping | ||
| * engine and the rules-based bulk-update processor both consume them — author once, reuse everywhere. | ||
| */ | ||
| export {}; | ||
| //# sourceMappingURL=transforms.js.map |
| {"version":3,"file":"transforms.js","sourceRoot":"","sources":["../../src/fieldRules/transforms.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"} |
| /** | ||
| * @fileoverview Entity-agnostic per-key serial task queue. The lowest-layer primitive behind the | ||
| * fire-and-forget save pattern: tasks enqueued under the same key run strictly in order (the next | ||
| * cannot start until the prior settles), tasks under different keys run concurrently, and failures | ||
| * are captured for a later `flush()` rather than propagated outward. MJGlobal cannot reference | ||
| * `BaseEntity`, so this knows nothing about entities — see `BaseEntitySaveQueue` for the entity façade. | ||
| * | ||
| * Self-bounding: only *in-flight* tasks are retained (they drop out as they settle), and failures are | ||
| * tallied into counters — so a long-lived queue that never flushes does not grow without bound. | ||
| * @module @memberjunction/global | ||
| */ | ||
| /** Aggregate outcome of a {@link KeyedSerialTaskQueue.flush}. */ | ||
| export interface SerialTaskFlushResult { | ||
| /** Tasks that rejected (threw) OR resolved a falsy "not ok" value, since the last flush. */ | ||
| failures: number; | ||
| /** Tasks that rejected (threw), since the last flush. */ | ||
| rejections: number; | ||
| } | ||
| /** | ||
| * A per-key serial task chain. Tasks for the **same key** (compared by object identity) run one at a | ||
| * time in enqueue order; tasks for **different keys** run concurrently. Enqueuing is fire-and-forget — | ||
| * the returned promise never rejects, and failures are tallied for {@link flush}. | ||
| * | ||
| * This is what makes "mutate after the prior task lands" structural: a task enqueued after another on | ||
| * the same key physically cannot begin until the prior one resolves. | ||
| */ | ||
| export declare class KeyedSerialTaskQueue { | ||
| /** Latest tail promise per key (object identity) — never rejects, so the chain can't break. */ | ||
| private tails; | ||
| /** Currently-running tasks (each removes itself on settle), for `flush()` to await. */ | ||
| private inFlight; | ||
| /** Failures/rejections since the last flush (counters, not a growing list — keeps memory bounded). */ | ||
| private failures; | ||
| private rejections; | ||
| private readonly onError?; | ||
| constructor(opts?: { | ||
| onError?: (err: unknown, label?: string) => void; | ||
| }); | ||
| /** | ||
| * Enqueues `task` to run after all prior tasks for `key` have settled. Fire-and-forget: returns | ||
| * the task's result promise, but the caller need not await it — it resolves to the task's value on | ||
| * success or `undefined` on failure, and **never rejects**. Failures (a thrown error, or a resolved | ||
| * value `opts.isOk` deems falsy) are counted for {@link flush} and routed to `onError`. | ||
| * | ||
| * @param key Serialization key (object identity). Same key → serialized; different keys → concurrent. | ||
| * @param task The work to run. | ||
| * @param opts.label Diagnostic label passed to `onError`. | ||
| * @param opts.isOk Treats a resolved value as a failure when it returns false (e.g. `Save()` → `false`). | ||
| */ | ||
| enqueue<T>(key: object, task: () => Promise<T>, opts?: { | ||
| label?: string; | ||
| isOk?: (v: T) => boolean; | ||
| }): Promise<T | undefined>; | ||
| /** | ||
| * Awaits the currently in-flight tasks, then reports and resets the failure tally accumulated since | ||
| * the last flush. Tasks enqueued after this call begins are not awaited here. | ||
| */ | ||
| flush(): Promise<SerialTaskFlushResult>; | ||
| } | ||
| //# sourceMappingURL=KeyedSerialTaskQueue.d.ts.map |
| {"version":3,"file":"KeyedSerialTaskQueue.d.ts","sourceRoot":"","sources":["../src/KeyedSerialTaskQueue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,iEAAiE;AACjE,MAAM,WAAW,qBAAqB;IAClC,4FAA4F;IAC5F,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,qBAAa,oBAAoB;IAC7B,+FAA+F;IAC/F,OAAO,CAAC,KAAK,CAAwC;IACrD,uFAAuF;IACvF,OAAO,CAAC,QAAQ,CAA4B;IAC5C,sGAAsG;IACtG,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAyC;gBAEtD,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE;IAIvE;;;;;;;;;;OAUG;IACI,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IA8BnI;;;OAGG;IACU,KAAK,IAAI,OAAO,CAAC,qBAAqB,CAAC;CAOvD"} |
| /** | ||
| * @fileoverview Entity-agnostic per-key serial task queue. The lowest-layer primitive behind the | ||
| * fire-and-forget save pattern: tasks enqueued under the same key run strictly in order (the next | ||
| * cannot start until the prior settles), tasks under different keys run concurrently, and failures | ||
| * are captured for a later `flush()` rather than propagated outward. MJGlobal cannot reference | ||
| * `BaseEntity`, so this knows nothing about entities — see `BaseEntitySaveQueue` for the entity façade. | ||
| * | ||
| * Self-bounding: only *in-flight* tasks are retained (they drop out as they settle), and failures are | ||
| * tallied into counters — so a long-lived queue that never flushes does not grow without bound. | ||
| * @module @memberjunction/global | ||
| */ | ||
| /** | ||
| * A per-key serial task chain. Tasks for the **same key** (compared by object identity) run one at a | ||
| * time in enqueue order; tasks for **different keys** run concurrently. Enqueuing is fire-and-forget — | ||
| * the returned promise never rejects, and failures are tallied for {@link flush}. | ||
| * | ||
| * This is what makes "mutate after the prior task lands" structural: a task enqueued after another on | ||
| * the same key physically cannot begin until the prior one resolves. | ||
| */ | ||
| export class KeyedSerialTaskQueue { | ||
| constructor(opts) { | ||
| /** Latest tail promise per key (object identity) — never rejects, so the chain can't break. */ | ||
| this.tails = new WeakMap(); | ||
| /** Currently-running tasks (each removes itself on settle), for `flush()` to await. */ | ||
| this.inFlight = new Set(); | ||
| /** Failures/rejections since the last flush (counters, not a growing list — keeps memory bounded). */ | ||
| this.failures = 0; | ||
| this.rejections = 0; | ||
| this.onError = opts?.onError; | ||
| } | ||
| /** | ||
| * Enqueues `task` to run after all prior tasks for `key` have settled. Fire-and-forget: returns | ||
| * the task's result promise, but the caller need not await it — it resolves to the task's value on | ||
| * success or `undefined` on failure, and **never rejects**. Failures (a thrown error, or a resolved | ||
| * value `opts.isOk` deems falsy) are counted for {@link flush} and routed to `onError`. | ||
| * | ||
| * @param key Serialization key (object identity). Same key → serialized; different keys → concurrent. | ||
| * @param task The work to run. | ||
| * @param opts.label Diagnostic label passed to `onError`. | ||
| * @param opts.isOk Treats a resolved value as a failure when it returns false (e.g. `Save()` → `false`). | ||
| */ | ||
| enqueue(key, task, opts) { | ||
| const prior = this.tails.get(key) ?? Promise.resolve(); | ||
| // Run the task exactly once, after `prior` settles, collapsing to a never-rejecting outcome. | ||
| const settled = prior.then(() => task(), () => task()).then((value) => ({ value: value, ok: opts?.isOk ? !!opts.isOk(value) : true, rejected: false, err: undefined }), (err) => ({ value: undefined, ok: false, rejected: true, err })); | ||
| // Never-rejecting tail: tally failures + route to onError. Serves as the per-key chain link. | ||
| const tail = settled.then((o) => { | ||
| if (!o.ok) { | ||
| this.failures++; | ||
| if (o.rejected) { | ||
| this.rejections++; | ||
| } | ||
| const err = o.rejected ? o.err : new Error(`task resolved not-ok${opts?.label ? `: ${opts.label}` : ''}`); | ||
| this.onError?.(err, opts?.label); | ||
| } | ||
| }); | ||
| this.tails.set(key, tail); | ||
| // Track only while in flight — self-remove on settle so the set stays bounded by concurrency. | ||
| let tracked; | ||
| tracked = tail.finally(() => this.inFlight.delete(tracked)); | ||
| this.inFlight.add(tracked); | ||
| return settled.then((o) => (o.ok ? o.value : undefined)); | ||
| } | ||
| /** | ||
| * Awaits the currently in-flight tasks, then reports and resets the failure tally accumulated since | ||
| * the last flush. Tasks enqueued after this call begins are not awaited here. | ||
| */ | ||
| async flush() { | ||
| await Promise.all([...this.inFlight]); | ||
| const result = { failures: this.failures, rejections: this.rejections }; | ||
| this.failures = 0; | ||
| this.rejections = 0; | ||
| return result; | ||
| } | ||
| } | ||
| //# sourceMappingURL=KeyedSerialTaskQueue.js.map |
| {"version":3,"file":"KeyedSerialTaskQueue.js","sourceRoot":"","sources":["../src/KeyedSerialTaskQueue.ts"],"names":[],"mappings":"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"} |
+2
-0
@@ -17,7 +17,9 @@ export { ClassFactory, ClassRegistration } from './ClassFactory.js'; | ||
| export * from './SafeExpressionEvaluator.js'; | ||
| export * from './fieldRules/index.js'; | ||
| export * from './SQLExpressionValidator.js'; | ||
| export * from './warningManager.js'; | ||
| export * from './EncryptionUtils.js'; | ||
| export * from './KeyedSerialTaskQueue.js'; | ||
| export * from './Global.js'; | ||
| export * from './RegisterClass.js'; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAChE,cAAc,aAAa,CAAA;AAC3B,cAAc,QAAQ,CAAA;AACtB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,2BAA2B,CAAA;AACzC,cAAc,0BAA0B,CAAA;AACxC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AAMjC,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAChE,cAAc,aAAa,CAAA;AAC3B,cAAc,QAAQ,CAAA;AACtB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,2BAA2B,CAAA;AACzC,cAAc,cAAc,CAAA;AAC5B,cAAc,0BAA0B,CAAA;AACxC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,wBAAwB,CAAA;AAMtC,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA"} |
+2
-0
@@ -18,5 +18,7 @@ // Export all types and utilities | ||
| export * from './SafeExpressionEvaluator.js'; | ||
| export * from './fieldRules/index.js'; | ||
| export * from './SQLExpressionValidator.js'; | ||
| export * from './warningManager.js'; | ||
| export * from './EncryptionUtils.js'; | ||
| export * from './KeyedSerialTaskQueue.js'; | ||
| // NOTE: TelemetryManager has moved to @memberjunction/core | ||
@@ -23,0 +25,0 @@ // Import from there instead of here |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAChE,cAAc,aAAa,CAAA;AAC3B,cAAc,QAAQ,CAAA;AACtB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,2BAA2B,CAAA;AACzC,cAAc,0BAA0B,CAAA;AACxC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AAEjC,2DAA2D;AAC3D,oCAAoC;AAEpC,0BAA0B;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAE/B,6DAA6D;AAC7D,oCAAoC"} | ||
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAChE,cAAc,aAAa,CAAA;AAC3B,cAAc,QAAQ,CAAA;AACtB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,2BAA2B,CAAA;AACzC,cAAc,cAAc,CAAA;AAC5B,cAAc,0BAA0B,CAAA;AACxC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,wBAAwB,CAAA;AAEtC,2DAA2D;AAC3D,oCAAoC;AAEpC,0BAA0B;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAE/B,6DAA6D;AAC7D,oCAAoC"} |
+1
-1
| { | ||
| "name": "@memberjunction/global", | ||
| "type": "module", | ||
| "version": "5.42.0", | ||
| "version": "5.43.0", | ||
| "description": "MemberJunction: Global Object - Needed for ALL other MJ components", | ||
@@ -6,0 +6,0 @@ "main": "dist/index.js", |
+89
-0
@@ -459,2 +459,22 @@ # @memberjunction/global | ||
| ### KeyedSerialTaskQueue | ||
| An entity-agnostic primitive for **fire-and-forget work that must serialize per key**. Tasks enqueued under the same key (compared by object identity) run strictly in order — the next can't start until the prior settles — while tasks under different keys run concurrently. Failures are tallied for a later `flush()` and never propagate outward, so the enqueue site is never blocked, nor broken, by a background failure. | ||
| It is **self-bounding**: only in-flight tasks are retained (they drop out as they settle) and failures accumulate into counters, so a long-lived queue that never flushes does not grow without bound. | ||
| ```typescript | ||
| import { KeyedSerialTaskQueue } from '@memberjunction/global'; | ||
| const queue = new KeyedSerialTaskQueue({ onError: (err, label) => console.error(label, err) }); | ||
| // Same key (the `record` object) → these run in order; different keys → concurrent. | ||
| queue.enqueue(record, () => insert(record)); | ||
| queue.enqueue(record, () => update(record), { label: 'update', isOk: (ok) => ok === true }); | ||
| const { failures, rejections } = await queue.flush(); // await in-flight tasks + read/reset the failure tally | ||
| ``` | ||
| The canonical consumer is `BaseEntitySaveQueue` in `@memberjunction/core`, which builds the fire-and-forget entity-save pattern (INSERT then chained UPDATE, with the "mutate-after-insert" race made structurally impossible) on top of this primitive. | ||
| ### WarningManager | ||
@@ -679,2 +699,71 @@ | ||
| ## Field Rules Engine | ||
| A **framework-agnostic** engine for computing field values from declarative rules and a composable | ||
| transform pipeline. It is the shared substrate behind two MemberJunction features — and is designed so | ||
| *any* code can use it, because it lives here in `global` with zero dependencies beyond this package: | ||
| | Layer | Package | Use it when | | ||
| |---|---|---| | ||
| | **Pure engine** (this package) | `@memberjunction/global` | You have a **plain record** (`Record<string, unknown>`) — from anywhere — and want to compute/transform field values. No entity, no DB required. | | ||
| | **`EntityFieldRules`** | `@memberjunction/core` | The **target is an MJ entity** — you want metadata validation, automatic type coercion, RunView-backed lookups, and write-back with Record Changes versioning. Builds on this engine. | | ||
| | **`FieldMappingEngine`** | `@memberjunction/integration` | The **other side is a live external system** (its own protocol, auth, match resolution, sync direction). Uses this engine for the per-field transforms. | | ||
| > One engine, three purpose-built layers. Pick the lowest one that fits: if you only have a dict, use this; if you're updating an entity, use core; if you're syncing an external system, that's integration's job. | ||
| ### Two pieces | ||
| **1. `FieldTransformEngine` — a transform pipeline over `(value, fields)`** | ||
| Runs an ordered list of `TransformStep`s. Step types: `direct`, `regex`, `split`, `combine`, `lookup` | ||
| (in-memory map), `format`, `coerce`, `substring`, `custom` (a `(value, fields) => result` expression, | ||
| compiled once and LRU-cached). Per-step `OnError` is `Skip` | `Null` | `Fail` (default `Null` — grace). | ||
| ```ts | ||
| import { FieldTransformEngine } from '@memberjunction/global'; | ||
| const e = new FieldTransformEngine(); | ||
| e.ExecutePipeline('19.99', {}, [{ Type: 'coerce', Config: { TargetType: 'number' } }]).Value; // 19.99 | ||
| e.Evaluate("fields.First + ' ' + fields.Last", undefined, { First: 'Ada', Last: 'Lovelace' }); // 'Ada Lovelace' | ||
| ``` | ||
| **2. `FieldRulesEvaluator` — rules → a per-field diff (no mutation)** | ||
| A `FieldRule` sets a `TargetField` from a `Source` — `static` | `field` | `formula` | `lookup` — | ||
| optionally through a `Transforms` pipeline, gated by an optional `Condition` (a **safe** boolean | ||
| expression via this package's `SafeExpressionEvaluator`). `ComputeChanges` returns a `FieldChange[]` | ||
| (old → new per rule) **without applying anything** — which is exactly what makes a **dry-run preview** | ||
| possible: the caller decides whether to write. | ||
| ```ts | ||
| import { FieldRulesEvaluator, type FieldRuleSet } from '@memberjunction/global'; | ||
| const ruleSet: FieldRuleSet = { | ||
| Rules: [ | ||
| { TargetField: 'FullName', Source: { Kind: 'formula', Expression: "fields.FirstName + ' ' + fields.LastName" } }, | ||
| { TargetField: 'Tier', Source: { Kind: 'static', Value: 'Gold' }, Condition: 'Revenue > 1000' }, | ||
| ], | ||
| }; | ||
| // LookupResolver is optional — only needed for `lookup` sources. core's EntityFieldRules supplies a | ||
| // RunView-backed one; here you inject your own (or omit it). | ||
| const changes = await new FieldRulesEvaluator().ComputeChanges( | ||
| { FirstName: 'Ada', LastName: 'Lovelace', FullName: '', Revenue: 5000, Tier: 'Bronze' }, | ||
| ruleSet, | ||
| ); | ||
| // → [{ Field: 'FullName', OldValue: '', NewValue: 'Ada Lovelace', Changed: true, Applied: true }, …] | ||
| ``` | ||
| ### Safety | ||
| - `Condition` expressions use the **safe** evaluator (blocklisted patterns, no statements). | ||
| - `formula` / `custom` value expressions compile with `new Function` (the same model as the established | ||
| integration `custom` transform) — author them from privileged users/agents, not untrusted input. | ||
| - The engine performs **no I/O**; `lookup` reaches a database only through the resolver you inject. | ||
| ### See also | ||
| - **`EntityFieldRules`** in [`@memberjunction/core`](../MJCore/README.md#entity-field-rules) — the metadata-aware layer for updating entities (validation, coercion, lookups, apply). | ||
| - **`FieldMappingEngine`** in [`@memberjunction/integration`](../Integration/engine/README.md#field-mapping--the-shared-transform-engine) — external-system field mapping built on this engine. | ||
| ## Related Packages | ||
@@ -681,0 +770,0 @@ |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
888112
13.48%190
23.38%11532
10.5%786
12.77%2
100%