@octaviaflow/flow-rules 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +87 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +87 -0
- package/dist/rules/mappingFunctions.d.ts +56 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -53,6 +53,8 @@ __export(exports_src, {
|
|
|
53
53
|
ForEachConfig: () => ForEachConfig,
|
|
54
54
|
JoinType: () => JoinType,
|
|
55
55
|
LogicalOperator: () => LogicalOperator,
|
|
56
|
+
MAPPING_FUNCTIONS: () => MAPPING_FUNCTIONS,
|
|
57
|
+
MAPPING_FUNCTION_SPECS: () => MAPPING_FUNCTION_SPECS,
|
|
56
58
|
MAX_STEP_CONFIG_BYTES: () => MAX_STEP_CONFIG_BYTES,
|
|
57
59
|
ManualTriggerConfig: () => ManualTriggerConfig,
|
|
58
60
|
NodeStatus: () => NodeStatus,
|
|
@@ -62,6 +64,7 @@ __export(exports_src, {
|
|
|
62
64
|
SortDirection: () => SortDirection,
|
|
63
65
|
TRANSFORM_OPERATIONS: () => TRANSFORM_OPERATIONS,
|
|
64
66
|
TransformConfig: () => TransformConfig,
|
|
67
|
+
USER_FUNCTION_PREFIX: () => USER_FUNCTION_PREFIX,
|
|
65
68
|
VariablesConfig: () => VariablesConfig,
|
|
66
69
|
actionsRegistry: () => actionsRegistry,
|
|
67
70
|
buildPathOptions: () => buildPathOptions,
|
|
@@ -83,7 +86,9 @@ __export(exports_src, {
|
|
|
83
86
|
getNodeValidationStatus: () => getNodeValidationStatus,
|
|
84
87
|
getStorageProvider: () => getStorageProvider,
|
|
85
88
|
inferSchemaFromSample: () => inferSchemaFromSample,
|
|
89
|
+
isMappingFunction: () => isMappingFunction,
|
|
86
90
|
isQueryableConnector: () => isQueryableConnector,
|
|
91
|
+
mappingTransformFunctionName: () => mappingTransformFunctionName,
|
|
87
92
|
migrateDelayConfig: () => migrateDelayConfig,
|
|
88
93
|
queryDefinition: () => queryDefinition,
|
|
89
94
|
storageConfigKeys: () => storageConfigKeys,
|
|
@@ -4252,3 +4257,85 @@ function validateStepConfigs(steps) {
|
|
|
4252
4257
|
function configErrors(steps) {
|
|
4253
4258
|
return validateStepConfigs(steps).filter((i) => i.severity === "error");
|
|
4254
4259
|
}
|
|
4260
|
+
// src/rules/mappingFunctions.ts
|
|
4261
|
+
var str = (v) => String(v ?? "");
|
|
4262
|
+
var HAS_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
|
|
4263
|
+
var MAPPING_FUNCTIONS = {
|
|
4264
|
+
concat: (...args) => args.map((a) => a == null ? "" : String(a)).join(""),
|
|
4265
|
+
uppercase: (val) => str(val).toUpperCase(),
|
|
4266
|
+
lowercase: (val) => str(val).toLowerCase(),
|
|
4267
|
+
trim: (val) => str(val).trim(),
|
|
4268
|
+
parseDate: (val) => {
|
|
4269
|
+
const d = new Date(String(val));
|
|
4270
|
+
return isNaN(d.getTime()) ? null : d.toISOString();
|
|
4271
|
+
},
|
|
4272
|
+
parseNumber: (val) => {
|
|
4273
|
+
const n = Number(val);
|
|
4274
|
+
return isNaN(n) ? null : n;
|
|
4275
|
+
},
|
|
4276
|
+
toString: (val) => str(val),
|
|
4277
|
+
substring: (val, start, end) => {
|
|
4278
|
+
const s = str(val);
|
|
4279
|
+
return s.substring(Number(start) || 0, end !== undefined ? Number(end) : undefined);
|
|
4280
|
+
},
|
|
4281
|
+
replace: (val, search, replacement) => str(val).replace(String(search), String(replacement)),
|
|
4282
|
+
default: (val, fallback) => val == null || val === "" ? fallback : val,
|
|
4283
|
+
normalizeUrl: (val, scheme = "https") => {
|
|
4284
|
+
const s = str(val).trim();
|
|
4285
|
+
if (!s)
|
|
4286
|
+
return "";
|
|
4287
|
+
if (HAS_SCHEME.test(s))
|
|
4288
|
+
return s;
|
|
4289
|
+
return `${String(scheme || "https")}://${s}`;
|
|
4290
|
+
},
|
|
4291
|
+
normalizeEmail: (val) => str(val).trim().toLowerCase(),
|
|
4292
|
+
normalizePhone: (val, countryCode) => {
|
|
4293
|
+
const raw = str(val).trim();
|
|
4294
|
+
if (!raw)
|
|
4295
|
+
return "";
|
|
4296
|
+
const plus = raw.startsWith("+");
|
|
4297
|
+
const digits = raw.replace(/\D/g, "");
|
|
4298
|
+
if (!digits)
|
|
4299
|
+
return "";
|
|
4300
|
+
if (plus)
|
|
4301
|
+
return `+${digits}`;
|
|
4302
|
+
const cc = str(countryCode).replace(/\D/g, "");
|
|
4303
|
+
return cc ? `+${cc}${digits}` : digits;
|
|
4304
|
+
},
|
|
4305
|
+
toDateOnly: (val) => {
|
|
4306
|
+
const d = new Date(String(val));
|
|
4307
|
+
return isNaN(d.getTime()) ? null : d.toISOString().slice(0, 10);
|
|
4308
|
+
}
|
|
4309
|
+
};
|
|
4310
|
+
var MAPPING_FUNCTION_SPECS = [
|
|
4311
|
+
{ name: "concat", signature: 'concat(value, "text", …)', description: "Joins the value and any literals into one string.", example: 'concat(value, " Ltd")' },
|
|
4312
|
+
{ name: "uppercase", signature: "uppercase(value)", description: "Upper-cases the value.", example: "uppercase(value)" },
|
|
4313
|
+
{ name: "lowercase", signature: "lowercase(value)", description: "Lower-cases the value.", example: "lowercase(value)" },
|
|
4314
|
+
{ name: "trim", signature: "trim(value)", description: "Removes leading and trailing whitespace.", example: "trim(value)" },
|
|
4315
|
+
{ name: "parseDate", signature: "parseDate(value)", description: "Parses to a full ISO timestamp, or null if unparseable.", example: "parseDate(value)" },
|
|
4316
|
+
{ name: "parseNumber", signature: "parseNumber(value)", description: "Parses to a number, or null if not numeric.", example: "parseNumber(value)" },
|
|
4317
|
+
{ name: "toString", signature: "toString(value)", description: 'Renders the value as a string ("" when empty).', example: "toString(value)" },
|
|
4318
|
+
{ name: "substring", signature: "substring(value, start, end?)", description: "Takes part of the value by index.", example: "substring(value, 0, 10)" },
|
|
4319
|
+
{ name: "replace", signature: 'replace(value, "find", "with")', description: "Replaces the first match.", example: 'replace(value, "Inc", "Inc.")' },
|
|
4320
|
+
{ name: "default", signature: 'default(value, "fallback")', description: "Uses the fallback when the value is empty or missing.", example: 'default(value, "Unknown")' },
|
|
4321
|
+
{ name: "normalizeUrl", signature: "normalizeUrl(value, scheme?)", description: "Adds https:// when the value names no scheme. Leaves http://, mailto: and others alone.", example: "normalizeUrl(value)" },
|
|
4322
|
+
{ name: "normalizeEmail", signature: "normalizeEmail(value)", description: "Trims and lower-cases an address.", example: "normalizeEmail(value)" },
|
|
4323
|
+
{ name: "normalizePhone", signature: "normalizePhone(value, countryCode?)", description: "Strips punctuation to digits, keeping a leading +. With a country code, prefixes numbers that lack one.", example: 'normalizePhone(value, "+1")' },
|
|
4324
|
+
{ name: "toDateOnly", signature: "toDateOnly(value)", description: "Reduces a timestamp to YYYY-MM-DD in UTC.", example: "toDateOnly(value)" }
|
|
4325
|
+
];
|
|
4326
|
+
var USER_FUNCTION_PREFIX = "user.";
|
|
4327
|
+
function isMappingFunction(name) {
|
|
4328
|
+
if (!name)
|
|
4329
|
+
return false;
|
|
4330
|
+
if (name.startsWith(USER_FUNCTION_PREFIX))
|
|
4331
|
+
return name.length > USER_FUNCTION_PREFIX.length;
|
|
4332
|
+
return Object.hasOwn(MAPPING_FUNCTIONS, name);
|
|
4333
|
+
}
|
|
4334
|
+
function mappingTransformFunctionName(transform) {
|
|
4335
|
+
const trimmed = (transform ?? "").trim();
|
|
4336
|
+
if (!trimmed)
|
|
4337
|
+
return null;
|
|
4338
|
+
const normalised = /^[A-Za-z_][\w.]*$/.test(trimmed) ? `${trimmed}(value)` : trimmed;
|
|
4339
|
+
const match = normalised.match(/^([A-Za-z_][\w.]*)\((.*)\)$/s);
|
|
4340
|
+
return match ? match[1] : null;
|
|
4341
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -44,3 +44,4 @@ export * from "./catalog/workflow";
|
|
|
44
44
|
export { type FlowConnection, type FlowData, type FlowStep, generateConnectionsFromSteps, transformConnectionToEdge, transformFlowToWorkflow, transformStepToNode, validateFlowData, } from "./rules/flowShape";
|
|
45
45
|
export { flattenNestedWorkflow } from "./rules/flattenNestedWorkflow";
|
|
46
46
|
export { configErrors, MAX_STEP_CONFIG_BYTES, validateStepConfig, validateStepConfigs, type ConfigIssue, type ConfigIssueCode, } from "./rules/configSafety";
|
|
47
|
+
export { isMappingFunction, MAPPING_FUNCTION_SPECS, MAPPING_FUNCTIONS, mappingTransformFunctionName, USER_FUNCTION_PREFIX, type MappingFunction, type MappingFunctionSpec, } from "./rules/mappingFunctions";
|
package/dist/index.js
CHANGED
|
@@ -4151,6 +4151,88 @@ function validateStepConfigs(steps) {
|
|
|
4151
4151
|
function configErrors(steps) {
|
|
4152
4152
|
return validateStepConfigs(steps).filter((i) => i.severity === "error");
|
|
4153
4153
|
}
|
|
4154
|
+
// src/rules/mappingFunctions.ts
|
|
4155
|
+
var str = (v) => String(v ?? "");
|
|
4156
|
+
var HAS_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
|
|
4157
|
+
var MAPPING_FUNCTIONS = {
|
|
4158
|
+
concat: (...args) => args.map((a) => a == null ? "" : String(a)).join(""),
|
|
4159
|
+
uppercase: (val) => str(val).toUpperCase(),
|
|
4160
|
+
lowercase: (val) => str(val).toLowerCase(),
|
|
4161
|
+
trim: (val) => str(val).trim(),
|
|
4162
|
+
parseDate: (val) => {
|
|
4163
|
+
const d = new Date(String(val));
|
|
4164
|
+
return isNaN(d.getTime()) ? null : d.toISOString();
|
|
4165
|
+
},
|
|
4166
|
+
parseNumber: (val) => {
|
|
4167
|
+
const n = Number(val);
|
|
4168
|
+
return isNaN(n) ? null : n;
|
|
4169
|
+
},
|
|
4170
|
+
toString: (val) => str(val),
|
|
4171
|
+
substring: (val, start, end) => {
|
|
4172
|
+
const s = str(val);
|
|
4173
|
+
return s.substring(Number(start) || 0, end !== undefined ? Number(end) : undefined);
|
|
4174
|
+
},
|
|
4175
|
+
replace: (val, search, replacement) => str(val).replace(String(search), String(replacement)),
|
|
4176
|
+
default: (val, fallback) => val == null || val === "" ? fallback : val,
|
|
4177
|
+
normalizeUrl: (val, scheme = "https") => {
|
|
4178
|
+
const s = str(val).trim();
|
|
4179
|
+
if (!s)
|
|
4180
|
+
return "";
|
|
4181
|
+
if (HAS_SCHEME.test(s))
|
|
4182
|
+
return s;
|
|
4183
|
+
return `${String(scheme || "https")}://${s}`;
|
|
4184
|
+
},
|
|
4185
|
+
normalizeEmail: (val) => str(val).trim().toLowerCase(),
|
|
4186
|
+
normalizePhone: (val, countryCode) => {
|
|
4187
|
+
const raw = str(val).trim();
|
|
4188
|
+
if (!raw)
|
|
4189
|
+
return "";
|
|
4190
|
+
const plus = raw.startsWith("+");
|
|
4191
|
+
const digits = raw.replace(/\D/g, "");
|
|
4192
|
+
if (!digits)
|
|
4193
|
+
return "";
|
|
4194
|
+
if (plus)
|
|
4195
|
+
return `+${digits}`;
|
|
4196
|
+
const cc = str(countryCode).replace(/\D/g, "");
|
|
4197
|
+
return cc ? `+${cc}${digits}` : digits;
|
|
4198
|
+
},
|
|
4199
|
+
toDateOnly: (val) => {
|
|
4200
|
+
const d = new Date(String(val));
|
|
4201
|
+
return isNaN(d.getTime()) ? null : d.toISOString().slice(0, 10);
|
|
4202
|
+
}
|
|
4203
|
+
};
|
|
4204
|
+
var MAPPING_FUNCTION_SPECS = [
|
|
4205
|
+
{ name: "concat", signature: 'concat(value, "text", …)', description: "Joins the value and any literals into one string.", example: 'concat(value, " Ltd")' },
|
|
4206
|
+
{ name: "uppercase", signature: "uppercase(value)", description: "Upper-cases the value.", example: "uppercase(value)" },
|
|
4207
|
+
{ name: "lowercase", signature: "lowercase(value)", description: "Lower-cases the value.", example: "lowercase(value)" },
|
|
4208
|
+
{ name: "trim", signature: "trim(value)", description: "Removes leading and trailing whitespace.", example: "trim(value)" },
|
|
4209
|
+
{ name: "parseDate", signature: "parseDate(value)", description: "Parses to a full ISO timestamp, or null if unparseable.", example: "parseDate(value)" },
|
|
4210
|
+
{ name: "parseNumber", signature: "parseNumber(value)", description: "Parses to a number, or null if not numeric.", example: "parseNumber(value)" },
|
|
4211
|
+
{ name: "toString", signature: "toString(value)", description: 'Renders the value as a string ("" when empty).', example: "toString(value)" },
|
|
4212
|
+
{ name: "substring", signature: "substring(value, start, end?)", description: "Takes part of the value by index.", example: "substring(value, 0, 10)" },
|
|
4213
|
+
{ name: "replace", signature: 'replace(value, "find", "with")', description: "Replaces the first match.", example: 'replace(value, "Inc", "Inc.")' },
|
|
4214
|
+
{ name: "default", signature: 'default(value, "fallback")', description: "Uses the fallback when the value is empty or missing.", example: 'default(value, "Unknown")' },
|
|
4215
|
+
{ name: "normalizeUrl", signature: "normalizeUrl(value, scheme?)", description: "Adds https:// when the value names no scheme. Leaves http://, mailto: and others alone.", example: "normalizeUrl(value)" },
|
|
4216
|
+
{ name: "normalizeEmail", signature: "normalizeEmail(value)", description: "Trims and lower-cases an address.", example: "normalizeEmail(value)" },
|
|
4217
|
+
{ name: "normalizePhone", signature: "normalizePhone(value, countryCode?)", description: "Strips punctuation to digits, keeping a leading +. With a country code, prefixes numbers that lack one.", example: 'normalizePhone(value, "+1")' },
|
|
4218
|
+
{ name: "toDateOnly", signature: "toDateOnly(value)", description: "Reduces a timestamp to YYYY-MM-DD in UTC.", example: "toDateOnly(value)" }
|
|
4219
|
+
];
|
|
4220
|
+
var USER_FUNCTION_PREFIX = "user.";
|
|
4221
|
+
function isMappingFunction(name) {
|
|
4222
|
+
if (!name)
|
|
4223
|
+
return false;
|
|
4224
|
+
if (name.startsWith(USER_FUNCTION_PREFIX))
|
|
4225
|
+
return name.length > USER_FUNCTION_PREFIX.length;
|
|
4226
|
+
return Object.hasOwn(MAPPING_FUNCTIONS, name);
|
|
4227
|
+
}
|
|
4228
|
+
function mappingTransformFunctionName(transform) {
|
|
4229
|
+
const trimmed = (transform ?? "").trim();
|
|
4230
|
+
if (!trimmed)
|
|
4231
|
+
return null;
|
|
4232
|
+
const normalised = /^[A-Za-z_][\w.]*$/.test(trimmed) ? `${trimmed}(value)` : trimmed;
|
|
4233
|
+
const match = normalised.match(/^([A-Za-z_][\w.]*)\((.*)\)$/s);
|
|
4234
|
+
return match ? match[1] : null;
|
|
4235
|
+
}
|
|
4154
4236
|
export {
|
|
4155
4237
|
ActionCategory,
|
|
4156
4238
|
ActionStatus,
|
|
@@ -4166,6 +4248,8 @@ export {
|
|
|
4166
4248
|
ForEachConfig,
|
|
4167
4249
|
JoinType,
|
|
4168
4250
|
LogicalOperator,
|
|
4251
|
+
MAPPING_FUNCTIONS,
|
|
4252
|
+
MAPPING_FUNCTION_SPECS,
|
|
4169
4253
|
MAX_STEP_CONFIG_BYTES,
|
|
4170
4254
|
ManualTriggerConfig,
|
|
4171
4255
|
NodeStatus,
|
|
@@ -4175,6 +4259,7 @@ export {
|
|
|
4175
4259
|
SortDirection,
|
|
4176
4260
|
TRANSFORM_OPERATIONS,
|
|
4177
4261
|
TransformConfig,
|
|
4262
|
+
USER_FUNCTION_PREFIX,
|
|
4178
4263
|
VariablesConfig,
|
|
4179
4264
|
actionsRegistry,
|
|
4180
4265
|
buildPathOptions,
|
|
@@ -4196,7 +4281,9 @@ export {
|
|
|
4196
4281
|
getNodeValidationStatus,
|
|
4197
4282
|
getStorageProvider,
|
|
4198
4283
|
inferSchemaFromSample,
|
|
4284
|
+
isMappingFunction,
|
|
4199
4285
|
isQueryableConnector,
|
|
4286
|
+
mappingTransformFunctionName,
|
|
4200
4287
|
migrateDelayConfig,
|
|
4201
4288
|
queryDefinition,
|
|
4202
4289
|
storageConfigKeys,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The functions a Field Mapper `transform` may call — implementations AND the
|
|
3
|
+
* list, in one place.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS. A Transform step's mapping has two slots, and they are not
|
|
6
|
+
* interchangeable:
|
|
7
|
+
*
|
|
8
|
+
* sourceField names a FIELD `{{Website}}`
|
|
9
|
+
* transform transforms the VALUE `uppercase(value)`
|
|
10
|
+
*
|
|
11
|
+
* Neither one evaluates an expression. `sourceField` is a literal path lookup,
|
|
12
|
+
* and `transform` is a single function call — so anything clever written in
|
|
13
|
+
* either resolves to nothing. Both failure modes were silent: a bad
|
|
14
|
+
* `sourceField` wrote an EMPTY value on every record, and an unknown
|
|
15
|
+
* `transform` function returned the source value unchanged, with a comment in
|
|
16
|
+
* the engine that said so out loud ("an unknown built-in falls through
|
|
17
|
+
* silently"). An agent hit the first one and blanked a field on every customer
|
|
18
|
+
* a live flow synced.
|
|
19
|
+
*
|
|
20
|
+
* Shipping the list without the implementations would just move the drift: the
|
|
21
|
+
* UI's FX sidebar already advertises 99 functions while the engine's mapping
|
|
22
|
+
* executor accepted 10, and four of those ten were not in the sidebar at all.
|
|
23
|
+
* So this module owns BOTH. One import, nothing to keep in step.
|
|
24
|
+
*
|
|
25
|
+
* ADDING ONE: put it in `MAPPING_FUNCTIONS` with a `MAPPING_FUNCTION_SPECS`
|
|
26
|
+
* entry. The spec is what the editor lists and what an agent is shown, so a
|
|
27
|
+
* function without one is invisible and effectively does not exist.
|
|
28
|
+
*/
|
|
29
|
+
/** A value-transform. `value` is the resolved source; the rest are literals. */
|
|
30
|
+
export type MappingFunction = (...args: unknown[]) => unknown;
|
|
31
|
+
export interface MappingFunctionSpec {
|
|
32
|
+
name: string;
|
|
33
|
+
/** How it is written, for the picker and for an agent's tool description. */
|
|
34
|
+
signature: string;
|
|
35
|
+
description: string;
|
|
36
|
+
example: string;
|
|
37
|
+
}
|
|
38
|
+
export declare const MAPPING_FUNCTIONS: Record<string, MappingFunction>;
|
|
39
|
+
export declare const MAPPING_FUNCTION_SPECS: readonly MappingFunctionSpec[];
|
|
40
|
+
/** Custom functions are written by the workspace and resolved at run time. */
|
|
41
|
+
export declare const USER_FUNCTION_PREFIX = "user.";
|
|
42
|
+
/**
|
|
43
|
+
* Is this a function a mapping may call?
|
|
44
|
+
*
|
|
45
|
+
* `user.*` is accepted on name alone — the workspace's own functions are
|
|
46
|
+
* resolved from a registry at run time, and this module cannot know them.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isMappingFunction(name: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The function a `transform` calls, or `null` when it is not a call at all.
|
|
51
|
+
*
|
|
52
|
+
* Mirrors the engine's parsing exactly, including the back-compat shim: a BARE
|
|
53
|
+
* name (`uppercase`) means `uppercase(value)`, because older saved mappings
|
|
54
|
+
* persisted just the name.
|
|
55
|
+
*/
|
|
56
|
+
export declare function mappingTransformFunctionName(transform: string): string | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octaviaflow/flow-rules",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "The flow action catalog and the rules engine behind Flow Doctor \u2014 one definition of what a step accepts and what makes a flow valid, shared by the editor and the server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|