@tailor-platform/sdk 1.73.2 → 1.73.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/application-BAo_zFA4.mjs +3 -0
- package/dist/{application-Bb9NNp5m.mjs → application-DgTCDMZY.mjs} +177 -140
- package/dist/application-DgTCDMZY.mjs.map +1 -0
- package/dist/cli/index.mjs +3 -3
- package/dist/cli/lib.mjs +2 -2
- package/dist/completion/zsh-worker.zsh +1 -1
- package/dist/{runtime-B8O6JFS2.mjs → runtime-SxHHEzVb.mjs} +3 -3
- package/dist/{runtime-B8O6JFS2.mjs.map → runtime-SxHHEzVb.mjs.map} +1 -1
- package/docs/services/tailordb.md +23 -0
- package/package.json +1 -1
- package/dist/application-BXdCl6bp.mjs +0 -3
- package/dist/application-Bb9NNp5m.mjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @tailor-platform/sdk
|
|
2
2
|
|
|
3
|
+
## 1.73.3
|
|
4
|
+
### Patch Changes
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
- [#1632](https://github.com/tailor-platform/sdk/pull/1632) [`56cb49d`](https://github.com/tailor-platform/sdk/commit/56cb49db80fd47f37eb43ea5d5f0c4e093cb6909) Thanks [@dqn](https://github.com/dqn)! - Validate that script expressions generated from TailorDB hooks and validators are valid JavaScript, failing at build time with a clear error instead of deploying broken scripts
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
- [#1635](https://github.com/tailor-platform/sdk/pull/1635) [`0af2c23`](https://github.com/tailor-platform/sdk/commit/0af2c2346b34f5c026d0221e151ba35ca9148d9a) Thanks [@dqn](https://github.com/dqn)! - Fix TailorDB relations silently dropping a forward relationship when two fields on the same type default to the same forward name; this now throws a validation error instead, matching the existing behavior for duplicate backward relationship names
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
- [#1631](https://github.com/tailor-platform/sdk/pull/1631) [`fffc654`](https://github.com/tailor-platform/sdk/commit/fffc6548935e4329dcd65e8d91e2ae1a76833abf) Thanks [@dqn](https://github.com/dqn)! - Fail with a clear error instead of producing corrupted bundle code when build-time source rewriting would apply overlapping edits
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
- [#1630](https://github.com/tailor-platform/sdk/pull/1630) [`69a034e`](https://github.com/tailor-platform/sdk/commit/69a034e36f1e2dbf9dfcf6e9d86a4a615502d24d) Thanks [@dqn](https://github.com/dqn)! - Fix `workflow.trigger()` calls in resolvers, executors, and workflow jobs failing at runtime when called without an options argument, or with options passed as a variable, a spread, or an object without a literal `authInvoker` property. All these forms are now rewritten at build time as the documented `trigger(args, options?)` signature promises; previously they compiled but threw "workflow.trigger() is rewritten at build time and unavailable in the bundle" after deploy.
|
|
21
|
+
|
|
3
22
|
## 1.73.2
|
|
4
23
|
### Patch Changes
|
|
5
24
|
|
|
@@ -2324,6 +2324,32 @@ function getImportSource(node) {
|
|
|
2324
2324
|
}
|
|
2325
2325
|
return null;
|
|
2326
2326
|
}
|
|
2327
|
+
function argumentSourceText(arg, sourceText) {
|
|
2328
|
+
if (arg && typeof arg === "object" && "start" in arg && "end" in arg) return sourceText.slice(arg.start, arg.end);
|
|
2329
|
+
}
|
|
2330
|
+
/**
|
|
2331
|
+
* Get metadata for a static `identifier.trigger(...)` call.
|
|
2332
|
+
* @param node - AST node to inspect
|
|
2333
|
+
* @param sourceText - Source code text
|
|
2334
|
+
* @returns Trigger call metadata, or null when the node is not a trigger call
|
|
2335
|
+
*/
|
|
2336
|
+
function getTriggerCallInfo(node, sourceText) {
|
|
2337
|
+
if (!node || typeof node !== "object" || node.type !== "CallExpression") return null;
|
|
2338
|
+
const callExpr = node;
|
|
2339
|
+
const callee = callExpr.callee;
|
|
2340
|
+
if (callee.type !== "MemberExpression") return null;
|
|
2341
|
+
const memberExpr = callee;
|
|
2342
|
+
if (memberExpr.computed || memberExpr.object.type !== "Identifier" || memberExpr.property.name !== "trigger") return null;
|
|
2343
|
+
return {
|
|
2344
|
+
identifierName: memberExpr.object.name,
|
|
2345
|
+
callRange: {
|
|
2346
|
+
start: callExpr.start,
|
|
2347
|
+
end: callExpr.end
|
|
2348
|
+
},
|
|
2349
|
+
argsText: argumentSourceText(callExpr.arguments[0], sourceText) ?? "",
|
|
2350
|
+
optionsText: argumentSourceText(callExpr.arguments[1], sourceText)
|
|
2351
|
+
};
|
|
2352
|
+
}
|
|
2327
2353
|
/**
|
|
2328
2354
|
* Unwrap AwaitExpression to get the inner expression
|
|
2329
2355
|
* @param node - AST expression node
|
|
@@ -2370,12 +2396,20 @@ function findProperty(properties, name) {
|
|
|
2370
2396
|
/**
|
|
2371
2397
|
* Apply string replacements to source code
|
|
2372
2398
|
* Replacements are applied from end to start to maintain positions
|
|
2399
|
+
* Ranges must not overlap; applying an overlapping range on top of an
|
|
2400
|
+
* already-shifted string would splice at stale offsets and corrupt the output,
|
|
2401
|
+
* so overlap is rejected up front
|
|
2373
2402
|
* @param source - Original source code
|
|
2374
2403
|
* @param replacements - Replacements to apply
|
|
2375
2404
|
* @returns Transformed source code
|
|
2376
2405
|
*/
|
|
2377
2406
|
function applyReplacements(source, replacements) {
|
|
2378
2407
|
const sorted = replacements.toSorted((a, b) => b.start - a.start);
|
|
2408
|
+
for (let i = 0; i + 1 < sorted.length; i++) {
|
|
2409
|
+
const current = assertDefined(sorted[i], `replacement missing at index ${i}`);
|
|
2410
|
+
const previous = assertDefined(sorted[i + 1], `replacement missing at index ${i + 1}`);
|
|
2411
|
+
if (previous.end > current.start) throw new Error(`applyReplacements: overlapping replacement ranges [${previous.start}, ${previous.end}) and [${current.start}, ${current.end})`);
|
|
2412
|
+
}
|
|
2379
2413
|
let result = source;
|
|
2380
2414
|
for (const r of sorted) result = result.slice(0, r.start) + r.text + result.slice(r.end);
|
|
2381
2415
|
return result;
|
|
@@ -2570,29 +2604,12 @@ function detectTriggerCalls(program, sourceText) {
|
|
|
2570
2604
|
const calls = [];
|
|
2571
2605
|
function walk(node) {
|
|
2572
2606
|
if (!node || typeof node !== "object") return;
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
const identifierName = memberExpr.object.name;
|
|
2580
|
-
let argsText = "";
|
|
2581
|
-
if (callExpr.arguments.length > 0) {
|
|
2582
|
-
const firstArg = callExpr.arguments[0];
|
|
2583
|
-
if (firstArg && "start" in firstArg && "end" in firstArg) argsText = sourceText.slice(firstArg.start, firstArg.end);
|
|
2584
|
-
}
|
|
2585
|
-
calls.push({
|
|
2586
|
-
identifierName,
|
|
2587
|
-
callRange: {
|
|
2588
|
-
start: callExpr.start,
|
|
2589
|
-
end: callExpr.end
|
|
2590
|
-
},
|
|
2591
|
-
argsText
|
|
2592
|
-
});
|
|
2593
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
}
|
|
2607
|
+
const triggerCall = getTriggerCallInfo(node, sourceText);
|
|
2608
|
+
if (triggerCall) calls.push({
|
|
2609
|
+
identifierName: triggerCall.identifierName,
|
|
2610
|
+
callRange: triggerCall.callRange,
|
|
2611
|
+
argsText: triggerCall.argsText
|
|
2612
|
+
});
|
|
2596
2613
|
for (const key of Object.keys(node)) {
|
|
2597
2614
|
const child = node[key];
|
|
2598
2615
|
if (Array.isArray(child)) child.forEach((c) => walk(c));
|
|
@@ -2698,44 +2715,19 @@ function detectDefaultImports(program) {
|
|
|
2698
2715
|
* Name of the injected runtime normalizer helper. Chosen to be unique enough
|
|
2699
2716
|
* to avoid collisions with user code.
|
|
2700
2717
|
*/
|
|
2701
|
-
const NORMALIZER_IDENTIFIER = "
|
|
2718
|
+
const NORMALIZER_IDENTIFIER = "__tailor_normalizeTriggerOptions";
|
|
2702
2719
|
/**
|
|
2703
2720
|
* Build the source text of the injected normalizer helper.
|
|
2704
2721
|
*
|
|
2705
|
-
*
|
|
2706
|
-
* `{ namespace, machineUserName }
|
|
2707
|
-
* The auth namespace is baked in at
|
|
2722
|
+
* Expands a plain-string `authInvoker` (machine user name) in the trigger
|
|
2723
|
+
* options to the object form `{ namespace, machineUserName }`; any other
|
|
2724
|
+
* options value passes through unchanged. The auth namespace is baked in at
|
|
2725
|
+
* bundle time.
|
|
2708
2726
|
* @param authNamespace - Auth service namespace to embed
|
|
2709
2727
|
* @returns Source line defining the helper
|
|
2710
2728
|
*/
|
|
2711
2729
|
function buildNormalizerHelperSource(authNamespace) {
|
|
2712
|
-
return `const ${NORMALIZER_IDENTIFIER} = (
|
|
2713
|
-
}
|
|
2714
|
-
/**
|
|
2715
|
-
* Extract authInvoker info from a config object expression
|
|
2716
|
-
* Returns the authInvoker value text and whether it's a shorthand property
|
|
2717
|
-
* @param configArg - Config argument node
|
|
2718
|
-
* @param sourceText - Source code text
|
|
2719
|
-
* @returns Extracted authInvoker info, if any
|
|
2720
|
-
*/
|
|
2721
|
-
function extractAuthInvokerInfo(configArg, sourceText) {
|
|
2722
|
-
if (!configArg || typeof configArg !== "object") return void 0;
|
|
2723
|
-
if (configArg.type !== "ObjectExpression") return void 0;
|
|
2724
|
-
const objExpr = configArg;
|
|
2725
|
-
for (const prop of objExpr.properties) {
|
|
2726
|
-
if (prop.type !== "Property") continue;
|
|
2727
|
-
const objProp = prop;
|
|
2728
|
-
if ((objProp.key.type === "Identifier" ? objProp.key.name : objProp.key.type === "Literal" ? objProp.key.value : null) === "authInvoker") {
|
|
2729
|
-
if (objProp.shorthand) return {
|
|
2730
|
-
isShorthand: true,
|
|
2731
|
-
valueText: "authInvoker"
|
|
2732
|
-
};
|
|
2733
|
-
return {
|
|
2734
|
-
isShorthand: false,
|
|
2735
|
-
valueText: sourceText.slice(objProp.value.start, objProp.value.end)
|
|
2736
|
-
};
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2730
|
+
return `const ${NORMALIZER_IDENTIFIER} = (o) => o && typeof o.authInvoker === "string" ? { ...o, authInvoker: { namespace: ${JSON.stringify(authNamespace)}, machineUserName: o.authInvoker } } : o;\n`;
|
|
2739
2731
|
}
|
|
2740
2732
|
/**
|
|
2741
2733
|
* Check if an AST binding pattern (parameter, catch clause, etc.) contains an Identifier with the given name.
|
|
@@ -2865,48 +2857,23 @@ function detectExtendedTriggerCalls(program, sourceText, workflowNames, jobNames
|
|
|
2865
2857
|
const calls = [];
|
|
2866
2858
|
function walk(node) {
|
|
2867
2859
|
if (!node || typeof node !== "object") return;
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
const
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
if (isWorkflow && argCount >= 2) {
|
|
2886
|
-
const secondArg = callExpr.arguments[1];
|
|
2887
|
-
const authInvoker = extractAuthInvokerInfo(secondArg, sourceText);
|
|
2888
|
-
if (authInvoker) calls.push({
|
|
2889
|
-
kind: "workflow",
|
|
2890
|
-
identifierName,
|
|
2891
|
-
callRange: {
|
|
2892
|
-
start: callExpr.start,
|
|
2893
|
-
end: callExpr.end
|
|
2894
|
-
},
|
|
2895
|
-
argsText,
|
|
2896
|
-
authInvoker
|
|
2897
|
-
});
|
|
2898
|
-
} else if (isJob) calls.push({
|
|
2899
|
-
kind: "job",
|
|
2900
|
-
identifierName,
|
|
2901
|
-
callRange: {
|
|
2902
|
-
start: callExpr.start,
|
|
2903
|
-
end: callExpr.end
|
|
2904
|
-
},
|
|
2905
|
-
argsText
|
|
2906
|
-
});
|
|
2907
|
-
}
|
|
2908
|
-
}
|
|
2909
|
-
}
|
|
2860
|
+
const triggerCall = getTriggerCallInfo(node, sourceText);
|
|
2861
|
+
if (triggerCall) {
|
|
2862
|
+
const isWorkflow = workflowNames.has(triggerCall.identifierName);
|
|
2863
|
+
const isJob = jobNames.has(triggerCall.identifierName);
|
|
2864
|
+
if (isWorkflow) calls.push({
|
|
2865
|
+
kind: "workflow",
|
|
2866
|
+
identifierName: triggerCall.identifierName,
|
|
2867
|
+
callRange: triggerCall.callRange,
|
|
2868
|
+
argsText: triggerCall.argsText,
|
|
2869
|
+
optionsText: triggerCall.optionsText
|
|
2870
|
+
});
|
|
2871
|
+
else if (isJob) calls.push({
|
|
2872
|
+
kind: "job",
|
|
2873
|
+
identifierName: triggerCall.identifierName,
|
|
2874
|
+
callRange: triggerCall.callRange,
|
|
2875
|
+
argsText: triggerCall.argsText
|
|
2876
|
+
});
|
|
2910
2877
|
}
|
|
2911
2878
|
for (const key of Object.keys(node)) {
|
|
2912
2879
|
const child = node[key];
|
|
@@ -2945,20 +2912,32 @@ function transformFunctionTriggers(source, workflowNameMap, jobNameMap, workflow
|
|
|
2945
2912
|
}
|
|
2946
2913
|
}
|
|
2947
2914
|
}
|
|
2948
|
-
const
|
|
2915
|
+
const allTriggerCalls = detectExtendedTriggerCalls(program, source, new Set(localWorkflowNameMap.keys()), new Set(jobNameMap.keys()));
|
|
2916
|
+
const nestedTriggerCalls = [];
|
|
2917
|
+
const triggerCalls = allTriggerCalls.filter((call) => {
|
|
2918
|
+
const parent = allTriggerCalls.find((other) => other !== call && other.callRange.start <= call.callRange.start && call.callRange.end <= other.callRange.end);
|
|
2919
|
+
if (parent) {
|
|
2920
|
+
nestedTriggerCalls.push({
|
|
2921
|
+
call,
|
|
2922
|
+
parent
|
|
2923
|
+
});
|
|
2924
|
+
return false;
|
|
2925
|
+
}
|
|
2926
|
+
return true;
|
|
2927
|
+
});
|
|
2928
|
+
for (const { call, parent } of nestedTriggerCalls) logger.warn(`Nested trigger call "${call.identifierName}.trigger(...)" inside "${parent.identifierName}.trigger(...)" cannot be converted. Move it to a separate statement and pass the result instead.`);
|
|
2949
2929
|
const replacements = [];
|
|
2950
2930
|
let needsNormalizerHelper = false;
|
|
2951
2931
|
const transformedCallsPerIdentifier = /* @__PURE__ */ new Map();
|
|
2952
|
-
for (const call of triggerCalls) if (call.kind === "workflow"
|
|
2932
|
+
for (const call of triggerCalls) if (call.kind === "workflow") {
|
|
2953
2933
|
const workflowName = localWorkflowNameMap.get(call.identifierName);
|
|
2954
2934
|
if (workflowName) {
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
authInvokerExpr = `${NORMALIZER_IDENTIFIER}(${rawExpr})`;
|
|
2935
|
+
let optionsPart = "";
|
|
2936
|
+
if (call.optionsText !== void 0) if (authNamespace) {
|
|
2937
|
+
optionsPart = `, ${NORMALIZER_IDENTIFIER}(${call.optionsText})`;
|
|
2959
2938
|
needsNormalizerHelper = true;
|
|
2960
|
-
} else
|
|
2961
|
-
const transformedCall = `tailor.workflow.triggerWorkflow("${workflowName}", ${call.argsText || "undefined"}
|
|
2939
|
+
} else optionsPart = `, ${call.optionsText}`;
|
|
2940
|
+
const transformedCall = `tailor.workflow.triggerWorkflow("${workflowName}", ${call.argsText || "undefined"}${optionsPart})`;
|
|
2962
2941
|
replacements.push({
|
|
2963
2942
|
start: call.callRange.start,
|
|
2964
2943
|
end: call.callRange.end,
|
|
@@ -2966,7 +2945,7 @@ function transformFunctionTriggers(source, workflowNameMap, jobNameMap, workflow
|
|
|
2966
2945
|
});
|
|
2967
2946
|
transformedCallsPerIdentifier.set(call.identifierName, (transformedCallsPerIdentifier.get(call.identifierName) ?? 0) + 1);
|
|
2968
2947
|
}
|
|
2969
|
-
} else
|
|
2948
|
+
} else {
|
|
2970
2949
|
const jobName = jobNameMap.get(call.identifierName);
|
|
2971
2950
|
if (jobName) {
|
|
2972
2951
|
const transformedCall = `(async () => tailor.workflow.triggerJobFunction("${jobName}", ${call.argsText || "undefined"}))()`;
|
|
@@ -3161,6 +3140,28 @@ async function bundleAuthHooks(options) {
|
|
|
3161
3140
|
return bundledCode;
|
|
3162
3141
|
}
|
|
3163
3142
|
|
|
3143
|
+
//#endregion
|
|
3144
|
+
//#region src/utils/script-expr.ts
|
|
3145
|
+
const MAX_GENERATED_CODE_LENGTH = 2e3;
|
|
3146
|
+
function formatGeneratedCode(expr) {
|
|
3147
|
+
if (expr.length <= MAX_GENERATED_CODE_LENGTH) return `Generated code:\n${expr}`;
|
|
3148
|
+
return `Generated code (truncated to ${MAX_GENERATED_CODE_LENGTH} of ${expr.length} characters):\n${expr.slice(0, MAX_GENERATED_CODE_LENGTH)}\n...`;
|
|
3149
|
+
}
|
|
3150
|
+
/**
|
|
3151
|
+
* Assert that a generated script expression is syntactically valid JavaScript.
|
|
3152
|
+
* Invalid generated code would otherwise surface much later as a confusing
|
|
3153
|
+
* bundler or platform-side syntax error, far from the definition that caused it.
|
|
3154
|
+
* @param expr - Generated JavaScript expression
|
|
3155
|
+
* @param context - What generated the expression, used to label the error
|
|
3156
|
+
* @returns The expression unchanged when it parses successfully
|
|
3157
|
+
*/
|
|
3158
|
+
function assertParsableExpression(expr, context) {
|
|
3159
|
+
const { errors } = parseSync("generated-expr.js", `(${expr}\n);`);
|
|
3160
|
+
if (errors.length === 0) return expr;
|
|
3161
|
+
const details = errors.map((error) => ` - ${error.message}`).join("\n");
|
|
3162
|
+
throw new Error(`Generated ${context} script is not valid JavaScript.\nParse errors:\n${details}\n` + formatGeneratedCode(expr));
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3164
3165
|
//#endregion
|
|
3165
3166
|
//#region src/parser/service/tailordb/hooks-validate-precompiled-expr.ts
|
|
3166
3167
|
const PRECOMPILED_EXPR_KEY = "__precompiledScriptExpr";
|
|
@@ -3214,30 +3215,38 @@ const stringifyFunction = (fn) => {
|
|
|
3214
3215
|
if (firstObjectProperty(`({m: ${src}})`)) return src;
|
|
3215
3216
|
const wrapped = `({${src}})`;
|
|
3216
3217
|
const property = firstObjectProperty(wrapped);
|
|
3217
|
-
if (property?.type === "Property" && property.method &&
|
|
3218
|
+
if (property?.type === "Property" && property.method && property.computed) throw new Error("Computed-key method shorthand cannot be converted to a TailorDB script expression. Use an arrow function or function expression instead.");
|
|
3219
|
+
if (property?.type === "Property" && property.method && property.value.type === "FunctionExpression") {
|
|
3218
3220
|
const { async, generator } = property.value;
|
|
3219
3221
|
const body = wrapped.slice(property.value.start, property.value.end);
|
|
3220
3222
|
return `${async ? "async " : ""}function${generator ? "*" : ""} ${body}`;
|
|
3221
3223
|
}
|
|
3222
3224
|
return src;
|
|
3223
3225
|
};
|
|
3226
|
+
function formatScriptContext(kind, context) {
|
|
3227
|
+
if (!context) return kind === "validate" ? kind : "hooks";
|
|
3228
|
+
return `${kind} for ${context.typeName}.${context.fieldPath.join(".")}`;
|
|
3229
|
+
}
|
|
3224
3230
|
/**
|
|
3225
3231
|
* Convert a hook or validator function to a script expression.
|
|
3226
3232
|
* @param fn - Hook or validator function
|
|
3233
|
+
* @param kind - Label naming the source of the expression in conversion errors
|
|
3234
|
+
* @param context - Optional field context for conversion errors
|
|
3227
3235
|
* @returns JavaScript expression calling the function
|
|
3228
3236
|
*/
|
|
3229
|
-
const convertToScriptExpr = (fn) => {
|
|
3237
|
+
const convertToScriptExpr = (fn, kind, context) => {
|
|
3230
3238
|
const precompiledExpr = getPrecompiledScriptExpr(fn);
|
|
3231
3239
|
if (precompiledExpr) return precompiledExpr;
|
|
3232
|
-
return `(${stringifyFunction(fn)})({ value: _value, data: _data, user: ${tailorUserMap} })
|
|
3240
|
+
return assertParsableExpression(`(${stringifyFunction(fn)})({ value: _value, data: _data, user: ${tailorUserMap} })`, formatScriptContext(kind, context));
|
|
3233
3241
|
};
|
|
3234
3242
|
/**
|
|
3235
3243
|
* Parse TailorDBField into OperatorFieldConfig.
|
|
3236
3244
|
* This transforms user-defined functions into script expressions.
|
|
3237
3245
|
* @param field - TailorDB field definition
|
|
3246
|
+
* @param context - Optional field context for conversion errors
|
|
3238
3247
|
* @returns Parsed operator field configuration
|
|
3239
3248
|
*/
|
|
3240
|
-
function parseFieldConfig(field) {
|
|
3249
|
+
function parseFieldConfig(field, context) {
|
|
3241
3250
|
const metadata = field.metadata;
|
|
3242
3251
|
const fieldType = field.type;
|
|
3243
3252
|
const rawRelation = field.rawRelation;
|
|
@@ -3247,7 +3256,10 @@ function parseFieldConfig(field) {
|
|
|
3247
3256
|
...metadata,
|
|
3248
3257
|
rawRelation,
|
|
3249
3258
|
...fieldType === "nested" && nestedFields && Object.keys(nestedFields).length > 0 ? { fields: Object.entries(nestedFields).reduce((acc, [key, nestedField]) => {
|
|
3250
|
-
acc[key] = parseFieldConfig(nestedField
|
|
3259
|
+
acc[key] = parseFieldConfig(nestedField, context && {
|
|
3260
|
+
...context,
|
|
3261
|
+
fieldPath: [...context.fieldPath, key]
|
|
3262
|
+
});
|
|
3251
3263
|
return acc;
|
|
3252
3264
|
}, {}) } : {},
|
|
3253
3265
|
validate: metadata.validate?.map((v) => {
|
|
@@ -3259,13 +3271,13 @@ function parseFieldConfig(field) {
|
|
|
3259
3271
|
message: v[1]
|
|
3260
3272
|
};
|
|
3261
3273
|
return {
|
|
3262
|
-
script: { expr: convertToScriptExpr(fn) },
|
|
3274
|
+
script: { expr: convertToScriptExpr(fn, "validate", context) },
|
|
3263
3275
|
errorMessage: message
|
|
3264
3276
|
};
|
|
3265
3277
|
}),
|
|
3266
3278
|
hooks: metadata.hooks ? {
|
|
3267
|
-
create: metadata.hooks.create ? { expr: convertToScriptExpr(metadata.hooks.create) } : void 0,
|
|
3268
|
-
update: metadata.hooks.update ? { expr: convertToScriptExpr(metadata.hooks.update) } : void 0
|
|
3279
|
+
create: metadata.hooks.create ? { expr: convertToScriptExpr(metadata.hooks.create, "hooks.create", context) } : void 0,
|
|
3280
|
+
update: metadata.hooks.update ? { expr: convertToScriptExpr(metadata.hooks.update, "hooks.update", context) } : void 0
|
|
3269
3281
|
} : void 0,
|
|
3270
3282
|
serial: metadata.serial ? {
|
|
3271
3283
|
start: metadata.serial.start,
|
|
@@ -3508,7 +3520,7 @@ function applyRelationMetadataToFieldConfig(fieldConfig, metadata) {
|
|
|
3508
3520
|
function parseTypes(rawTypes, namespace, typeSourceInfo) {
|
|
3509
3521
|
const types = createRecord();
|
|
3510
3522
|
const allTypeNames = new Set(Object.keys(rawTypes));
|
|
3511
|
-
for (const [typeName, type] of Object.entries(rawTypes)) types[typeName] = parseTailorDBType(type, allTypeNames, rawTypes);
|
|
3523
|
+
for (const [typeName, type] of Object.entries(rawTypes)) types[typeName] = parseTailorDBType(type, allTypeNames, rawTypes, typeSourceInfo);
|
|
3512
3524
|
buildBackwardRelationships(types, namespace, typeSourceInfo);
|
|
3513
3525
|
validatePluralFormUniqueness(types, namespace, typeSourceInfo);
|
|
3514
3526
|
return types;
|
|
@@ -3518,21 +3530,26 @@ function parseTypes(rawTypes, namespace, typeSourceInfo) {
|
|
|
3518
3530
|
* @param type - TailorDB type to parse
|
|
3519
3531
|
* @param allTypeNames - Set of all TailorDB type names
|
|
3520
3532
|
* @param rawTypes - All raw TailorDB types keyed by name
|
|
3533
|
+
* @param typeSourceInfo - Optional type source information
|
|
3521
3534
|
* @returns Parsed TailorDB type
|
|
3522
3535
|
*/
|
|
3523
|
-
function parseTailorDBType(type, allTypeNames, rawTypes) {
|
|
3536
|
+
function parseTailorDBType(type, allTypeNames, rawTypes, typeSourceInfo) {
|
|
3524
3537
|
const metadata = type.metadata;
|
|
3525
3538
|
const pluralForm = metadata.settings?.pluralForm || inflection.pluralize(type.name);
|
|
3539
|
+
const typeLocation = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, type.name));
|
|
3526
3540
|
const fields = createRecord();
|
|
3527
3541
|
const forwardRelationships = createRecord();
|
|
3528
3542
|
for (const [fieldName, fieldDef] of Object.entries(type.fields)) {
|
|
3529
|
-
let fieldConfig = parseFieldConfig(fieldDef);
|
|
3530
|
-
const rawRelation = fieldConfig.rawRelation;
|
|
3531
3543
|
const context = {
|
|
3532
3544
|
typeName: type.name,
|
|
3533
3545
|
fieldName,
|
|
3534
3546
|
allTypeNames
|
|
3535
3547
|
};
|
|
3548
|
+
let fieldConfig = parseFieldConfig(fieldDef, {
|
|
3549
|
+
typeName: type.name,
|
|
3550
|
+
fieldPath: [fieldName]
|
|
3551
|
+
});
|
|
3552
|
+
const rawRelation = fieldConfig.rawRelation;
|
|
3536
3553
|
if (rawRelation) {
|
|
3537
3554
|
validateRelationConfig(rawRelation, context);
|
|
3538
3555
|
if ([
|
|
@@ -3552,9 +3569,18 @@ function parseTailorDBType(type, allTypeNames, rawTypes) {
|
|
|
3552
3569
|
const relationInfo = rawRelation ? buildRelationInfo(rawRelation, context) : void 0;
|
|
3553
3570
|
if (relationInfo) {
|
|
3554
3571
|
parsedField.relation = { ...relationInfo };
|
|
3572
|
+
const forwardName = relationInfo.forwardName;
|
|
3573
|
+
if (forwardName.length === 0) throw new Error(`Forward relation name for field "${fieldName}" on type "${type.name}"${typeLocation} cannot be empty. Use the "as" option in .relation({ toward: { as: ... } }) to specify a non-empty name.`);
|
|
3574
|
+
const existingForward = forwardRelationships[forwardName];
|
|
3575
|
+
if (existingForward) throw new Error(`Forward relation name "${forwardName}" on type "${type.name}"${typeLocation} is duplicated between fields "${existingForward.targetField}" and "${fieldName}". Use the "as" option in .relation({ toward: { as: ... } }) to specify a unique name.`);
|
|
3576
|
+
if (Object.hasOwn(type.fields, forwardName)) {
|
|
3577
|
+
const message = forwardName === fieldName ? `Forward relation name "${forwardName}" on type "${type.name}"${typeLocation} is the same as its own relation field "${fieldName}". Use the "as" option in .relation({ toward: { as: ... } }) to specify a different name.` : `Forward relation name "${forwardName}" from field "${fieldName}" on type "${type.name}"${typeLocation} conflicts with existing field "${forwardName}". Use the "as" option in .relation({ toward: { as: ... } }) to specify a different name.`;
|
|
3578
|
+
throw new Error(message);
|
|
3579
|
+
}
|
|
3580
|
+
if (Object.hasOwn(metadata.files, forwardName)) throw new Error(`Forward relation name "${forwardName}" from field "${fieldName}" on type "${type.name}"${typeLocation} conflicts with files field "${forwardName}". Use the "as" option in .relation({ toward: { as: ... } }) to specify a different name.`);
|
|
3555
3581
|
const targetType = rawTypes[relationInfo.targetType];
|
|
3556
|
-
forwardRelationships[
|
|
3557
|
-
name:
|
|
3582
|
+
forwardRelationships[forwardName] = {
|
|
3583
|
+
name: forwardName,
|
|
3558
3584
|
targetType: relationInfo.targetType,
|
|
3559
3585
|
targetField: fieldName,
|
|
3560
3586
|
sourceField: relationInfo.key,
|
|
@@ -3615,13 +3641,11 @@ function buildBackwardRelationships(types, namespace, typeSourceInfo) {
|
|
|
3615
3641
|
for (const [targetTypeName, backwardNames] of Object.entries(backwardNameSources)) {
|
|
3616
3642
|
const targetType = types[targetTypeName];
|
|
3617
3643
|
if (targetType === void 0) throw new Error(`type not found: ${targetTypeName}`);
|
|
3618
|
-
const
|
|
3619
|
-
const targetLocation = targetTypeSourceInfo ? isPluginGeneratedType(targetTypeSourceInfo) ? ` (plugin: ${targetTypeSourceInfo.pluginId})` : ` (${targetTypeSourceInfo.filePath})` : "";
|
|
3644
|
+
const targetLocation = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, targetTypeName));
|
|
3620
3645
|
for (const [backwardName, sources] of Object.entries(backwardNames)) {
|
|
3621
3646
|
if (sources.length > 1) {
|
|
3622
3647
|
const sourceList = sources.map((s) => {
|
|
3623
|
-
const
|
|
3624
|
-
const location = sourceInfo ? isPluginGeneratedType(sourceInfo) ? ` (plugin: ${sourceInfo.pluginId})` : ` (${sourceInfo.filePath})` : "";
|
|
3648
|
+
const location = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, s.sourceType));
|
|
3625
3649
|
return `${s.sourceType}.${s.fieldName}${location}`;
|
|
3626
3650
|
}).join(", ");
|
|
3627
3651
|
errors.push(`Backward relation name "${backwardName}" on type "${targetTypeName}" is duplicated from: ${sourceList}. Use the "backward" option in .relation() to specify unique names.`);
|
|
@@ -3629,17 +3653,21 @@ function buildBackwardRelationships(types, namespace, typeSourceInfo) {
|
|
|
3629
3653
|
if (Object.hasOwn(targetType.fields, backwardName)) {
|
|
3630
3654
|
const source = sources[0];
|
|
3631
3655
|
if (source === void 0) throw new Error(`no source found for backward name: ${backwardName}`);
|
|
3632
|
-
const
|
|
3633
|
-
const sourceLocation = sourceInfo ? isPluginGeneratedType(sourceInfo) ? ` (plugin: ${sourceInfo.pluginId})` : ` (${sourceInfo.filePath})` : "";
|
|
3656
|
+
const sourceLocation = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, source.sourceType));
|
|
3634
3657
|
errors.push(`Backward relation name "${backwardName}" from ${source.sourceType}.${source.fieldName}${sourceLocation} conflicts with existing field "${backwardName}" on type "${targetTypeName}"${targetLocation}. Use the "backward" option in .relation() to specify a different name.`);
|
|
3635
3658
|
}
|
|
3636
3659
|
if (targetType.files && Object.hasOwn(targetType.files, backwardName)) {
|
|
3637
3660
|
const source = sources[0];
|
|
3638
3661
|
if (source === void 0) throw new Error(`no source found for backward name: ${backwardName}`);
|
|
3639
|
-
const
|
|
3640
|
-
const sourceLocation = sourceInfo ? isPluginGeneratedType(sourceInfo) ? ` (plugin: ${sourceInfo.pluginId})` : ` (${sourceInfo.filePath})` : "";
|
|
3662
|
+
const sourceLocation = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, source.sourceType));
|
|
3641
3663
|
errors.push(`Backward relation name "${backwardName}" from ${source.sourceType}.${source.fieldName}${sourceLocation} conflicts with files field "${backwardName}" on type "${targetTypeName}"${targetLocation}. Use the "backward" option in .relation() to specify a different name.`);
|
|
3642
3664
|
}
|
|
3665
|
+
if (Object.hasOwn(targetType.forwardRelationships, backwardName)) {
|
|
3666
|
+
const source = sources[0];
|
|
3667
|
+
if (source === void 0) throw new Error(`no source found for backward name: ${backwardName}`);
|
|
3668
|
+
const sourceLocation = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, source.sourceType));
|
|
3669
|
+
errors.push(`Relation name "${backwardName}" on type "${targetTypeName}"${targetLocation} is used by both a forward relationship and a backward relationship from ${source.sourceType}.${source.fieldName}${sourceLocation}. Use the "as" option in .relation({ toward: { as: ... } }) or the "backward" option in .relation() to specify unique names.`);
|
|
3670
|
+
}
|
|
3643
3671
|
}
|
|
3644
3672
|
}
|
|
3645
3673
|
if (errors.length > 0) throw new Error(`Backward relation name conflicts detected in TailorDB service "${namespace}".\n${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
@@ -3658,8 +3686,7 @@ function validatePluralFormUniqueness(types, namespace, typeSourceInfo) {
|
|
|
3658
3686
|
for (const [, parsedType] of Object.entries(types)) {
|
|
3659
3687
|
const singularQuery = inflection.camelize(parsedType.name, true);
|
|
3660
3688
|
if (singularQuery === inflection.camelize(parsedType.pluralForm, true)) {
|
|
3661
|
-
const
|
|
3662
|
-
const location = sourceInfo ? isPluginGeneratedType(sourceInfo) ? ` (plugin: ${sourceInfo.pluginId})` : ` (${sourceInfo.filePath})` : "";
|
|
3689
|
+
const location = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, parsedType.name));
|
|
3663
3690
|
errors.push(`Type "${parsedType.name}"${location} has identical singular and plural query names "${singularQuery}". Use db.type(["${parsedType.name}", "UniquePluralForm"], {...}) to set a unique pluralForm.`);
|
|
3664
3691
|
}
|
|
3665
3692
|
}
|
|
@@ -3685,8 +3712,7 @@ function validatePluralFormUniqueness(types, namespace, typeSourceInfo) {
|
|
|
3685
3712
|
const duplicates = [...queryNameToSource].filter(([, sources]) => sources.length > 1);
|
|
3686
3713
|
for (const [queryName, sources] of duplicates) {
|
|
3687
3714
|
const sourceList = sources.map((s) => {
|
|
3688
|
-
const
|
|
3689
|
-
const location = sourceInfo ? isPluginGeneratedType(sourceInfo) ? ` (plugin: ${sourceInfo.pluginId})` : ` (${sourceInfo.filePath})` : "";
|
|
3715
|
+
const location = formatTypeSourceLocation(getTypeSourceInfo(typeSourceInfo, s.typeName));
|
|
3690
3716
|
return `"${s.typeName}"${location} (${s.kind})`;
|
|
3691
3717
|
}).join(", ");
|
|
3692
3718
|
errors.push(`GraphQL query field "${queryName}" conflicts between: ${sourceList}`);
|
|
@@ -3696,6 +3722,10 @@ function validatePluralFormUniqueness(types, namespace, typeSourceInfo) {
|
|
|
3696
3722
|
function getTypeSourceInfo(typeSourceInfo, typeName) {
|
|
3697
3723
|
return typeSourceInfo && Object.hasOwn(typeSourceInfo, typeName) ? typeSourceInfo[typeName] : void 0;
|
|
3698
3724
|
}
|
|
3725
|
+
function formatTypeSourceLocation(sourceInfo) {
|
|
3726
|
+
if (!sourceInfo) return "";
|
|
3727
|
+
return isPluginGeneratedType(sourceInfo) ? ` (plugin: ${sourceInfo.pluginId})` : ` (${sourceInfo.filePath})`;
|
|
3728
|
+
}
|
|
3699
3729
|
function createRecord() {
|
|
3700
3730
|
return Object.create(null);
|
|
3701
3731
|
}
|
|
@@ -4211,12 +4241,13 @@ function buildMinimalEntryFromResolved(imports, declarations, fnSource, sourceFi
|
|
|
4211
4241
|
}
|
|
4212
4242
|
async function bundleScriptTarget(args) {
|
|
4213
4243
|
const { fn, kind, sourceFilePath, sourceBindings, tempDir, targetIndex, tsconfig } = args;
|
|
4244
|
+
const context = `${kind} in ${sourceFilePath}`;
|
|
4214
4245
|
const fnSource = stringifyFunction(fn);
|
|
4215
|
-
const inlineExpr = `(${fnSource})({ value: _value, data: _data, user: ${tailorUserMap} })
|
|
4246
|
+
const inlineExpr = assertParsableExpression(`(${fnSource})({ value: _value, data: _data, user: ${tailorUserMap} })`, context);
|
|
4216
4247
|
const freeVars = findUndefinedReferences(`const __fn = ${fnSource};`);
|
|
4217
4248
|
if (freeVars.size === 0) return inlineExpr;
|
|
4218
4249
|
const { imports, declarations, unresolved } = resolveNeededBindings(freeVars, sourceBindings);
|
|
4219
|
-
if (unresolved.length > 0) throw new Error(`${
|
|
4250
|
+
if (unresolved.length > 0) throw new Error(`${context} captures unresolvable variables (${unresolved.join(", ")}). Hooks and validators must not reference variables that cannot be resolved from the source file.
|
|
4220
4251
|
${kind}: ${fnSource}`);
|
|
4221
4252
|
const entryContent = buildMinimalEntryFromResolved(imports, declarations, fnSource, sourceFilePath);
|
|
4222
4253
|
const entryPath = join(tempDir, `tailordb-script-${targetIndex}.entry.ts`);
|
|
@@ -4239,7 +4270,7 @@ async function bundleScriptTarget(args) {
|
|
|
4239
4270
|
},
|
|
4240
4271
|
logLevel: "silent"
|
|
4241
4272
|
})).output[0].code;
|
|
4242
|
-
return buildPrecompiledExpr(bundledCode);
|
|
4273
|
+
return assertParsableExpression(buildPrecompiledExpr(bundledCode), context);
|
|
4243
4274
|
}
|
|
4244
4275
|
/**
|
|
4245
4276
|
* Precompile TailorDB hooks/validators into self-contained script expressions using rolldown.
|
|
@@ -5444,11 +5475,17 @@ function transformWorkflowSource(source, targetJobName, targetJobExportName, oth
|
|
|
5444
5475
|
end: endPos,
|
|
5445
5476
|
text: ""
|
|
5446
5477
|
});
|
|
5447
|
-
} else if (!job.statementRange)
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5478
|
+
} else if (!job.statementRange) {
|
|
5479
|
+
removedRanges.push({
|
|
5480
|
+
start: job.bodyValueRange.start,
|
|
5481
|
+
end: job.bodyValueRange.end
|
|
5482
|
+
});
|
|
5483
|
+
replacements.push({
|
|
5484
|
+
start: job.bodyValueRange.start,
|
|
5485
|
+
end: job.bodyValueRange.end,
|
|
5486
|
+
text: "() => {}"
|
|
5487
|
+
});
|
|
5488
|
+
}
|
|
5452
5489
|
}
|
|
5453
5490
|
if (otherJobExportNames) for (const exportName of otherJobExportNames) {
|
|
5454
5491
|
if (exportName === targetJobExportName) continue;
|
|
@@ -6455,4 +6492,4 @@ async function loadApplication(params) {
|
|
|
6455
6492
|
|
|
6456
6493
|
//#endregion
|
|
6457
6494
|
export { initOperatorClient as $, loadAccessToken as A, saveUserTokens as B, hashContent as C, fetchLatestToken as D, deleteUserTokens as E, loadStoredUserTokens as F, fetchMachineUserToken as G, closeConnectionPool as H, loadWorkspaceId as I, fetchUserInfo as J, fetchPaged as K, platformConfigFromProfile as L, loadConsoleBaseUrl as M, loadMachineUserName as N, hasAnyUserTokenEntry as O, loadPlatformClientConfig as P, initOAuth2Client as Q, readPlatformConfig as R, getDistDir as S, loadConfig as T, defaultPlatformBaseUrl as U, writePlatformConfig as V, fetchAll as W, getOAuth2ClientId as X, getConsoleBaseUrl as Y, getPlatformBaseUrl as Z, createLogLevelTreeshakeOptions as _, WorkflowJobSchema as a, hasGenerationHooks as b, INVOKER_EXPR as c, assertUniqueLocalTailorDBTypeNames as d, isDefaultPlatform as et, assertUniqueTailorDBTypeNamesWithExternal as f, composeFunctionTreeshakeOptions as g, platformBundleDefinePlugin as h, resolveInlineSourcemap as i, loadConfigPath as j, hasUserTokenEntry as k, buildExecutorArgsExpr as l, stringifyFunction as m, generatePluginFilesIfNeeded as n, byName as nt, ResolverSchema as o, TailorDBTypeSchema as p, fetchPlatformMachineUserToken as q, loadApplication as r, HTTP_METHODS as s, defineApplication as t, resolveStaticWebsiteUrls as tt, buildResolverOperationHookExpr as u, resolveBundleLogLevel as v, hashFile as w, createBundleCache as x, getPluginGenerationDependencies as y, resolveUserTokenKey as z };
|
|
6458
|
-
//# sourceMappingURL=application-
|
|
6495
|
+
//# sourceMappingURL=application-DgTCDMZY.mjs.map
|