🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@memberjunction/global

Package Overview
Dependencies
Maintainers
9
Versions
437
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@memberjunction/global - npm Package Compare versions

Comparing version
5.42.0
to
5.43.0
+2
dist/__tests__/FieldRulesEvaluator.test.d.ts
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

@@ -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"}

@@ -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"}
{
"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",

@@ -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 @@