@lotics/cli 0.69.0 → 0.70.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.
|
@@ -26,7 +26,9 @@ export declare function generateAppWorkflowsDts(workflows: Record<string, AppWor
|
|
|
26
26
|
* to `unknown` rather than throwing — the server's deploy-time schema parse
|
|
27
27
|
* catches structurally-invalid declarations before they reach us.
|
|
28
28
|
*/
|
|
29
|
-
export declare function inputsToType(inputs: Record<string, unknown
|
|
29
|
+
export declare function inputsToType(inputs: Record<string, unknown>, opts?: {
|
|
30
|
+
nullableOptional?: boolean;
|
|
31
|
+
}): string;
|
|
30
32
|
/**
|
|
31
33
|
* Map an output `fields` map (also the top-level `outputs`) to a TS object type.
|
|
32
34
|
* Mirrors the backend `outputObjectToTsType`; recursive for nested object/array.
|
|
@@ -41,7 +41,7 @@ declare module "@lotics/app-sdk" {
|
|
|
41
41
|
// No typed inputs ⇒ untyped `Record<string, unknown>` callable. An alias with
|
|
42
42
|
// declared `outputs` also gets an `AppWorkflowResults` entry → typed result.data.
|
|
43
43
|
const valueType = declaration.inputs
|
|
44
|
-
? inputsToType(declaration.inputs)
|
|
44
|
+
? inputsToType(declaration.inputs, { nullableOptional: true })
|
|
45
45
|
: "Record<string, unknown>";
|
|
46
46
|
const aliasKey = isValidIdentifier(alias) ? alias : JSON.stringify(alias);
|
|
47
47
|
inputLines.push(` ${aliasKey}: ${valueType};`);
|
|
@@ -74,16 +74,27 @@ ${inputLines.join("\n")}
|
|
|
74
74
|
* to `unknown` rather than throwing — the server's deploy-time schema parse
|
|
75
75
|
* catches structurally-invalid declarations before they reach us.
|
|
76
76
|
*/
|
|
77
|
-
export function inputsToType(inputs) {
|
|
77
|
+
export function inputsToType(inputs, opts) {
|
|
78
|
+
// A WORKFLOW optional input also accepts `null` — the diff-write "clear this
|
|
79
|
+
// field" signal (the backend validates it, the body forwards it to
|
|
80
|
+
// `update_records({ set: { fld: null } })`). So `useWorkflow` call sites can
|
|
81
|
+
// pass `null` to unset a field without a loose `Record<string, unknown>` cast.
|
|
82
|
+
// Query params + agent inputs have no "clear", so they stay `T` (the caller
|
|
83
|
+
// omits the flag). Only the TOP level is nullable: a nested object's optional
|
|
84
|
+
// field mirrors the backend's plain `.optional()` (the recursive call below
|
|
85
|
+
// omits the flag), so the type never claims a nested clear the server rejects.
|
|
86
|
+
const nullableOptional = opts?.nullableOptional === true;
|
|
78
87
|
const fields = [];
|
|
79
88
|
for (const [key, decl] of Object.entries(inputs)) {
|
|
80
89
|
if (decl === null || typeof decl !== "object")
|
|
81
90
|
continue;
|
|
82
91
|
const d = decl;
|
|
83
92
|
const tsType = inputDeclToTsType(d);
|
|
84
|
-
const
|
|
93
|
+
const isOptional = d.required === false;
|
|
94
|
+
const fieldType = isOptional && nullableOptional ? `${tsType} | null` : tsType;
|
|
95
|
+
const optional = isOptional ? "?" : "";
|
|
85
96
|
const fieldKey = isValidIdentifier(key) ? key : JSON.stringify(key);
|
|
86
|
-
fields.push(` ${fieldKey}${optional}: ${
|
|
97
|
+
fields.push(` ${fieldKey}${optional}: ${fieldType};`);
|
|
87
98
|
}
|
|
88
99
|
if (fields.length === 0)
|
|
89
100
|
return "Record<string, never>";
|
|
@@ -58,6 +58,32 @@ describe("generateAppWorkflowsDts", () => {
|
|
|
58
58
|
expect(dts).toContain("note?: string;");
|
|
59
59
|
expect(dts).toContain("ref: string;");
|
|
60
60
|
});
|
|
61
|
+
it("types a TOP-LEVEL optional workflow input as `T | null` (the diff-write clear signal), required stays `T`, nested stays plain optional", () => {
|
|
62
|
+
const dts = generateAppWorkflowsDts({
|
|
63
|
+
updateTask: {
|
|
64
|
+
workflow_id: "wfl_u",
|
|
65
|
+
inputs: {
|
|
66
|
+
record_id: { type: "record_link", table_id: "tbl_x" },
|
|
67
|
+
due: { type: "date", required: false },
|
|
68
|
+
status: { type: "select", required: false, options: [{ label: "Done", value: "opt_done" }] },
|
|
69
|
+
tags: { type: "select", multi: true, required: false, options: [{ label: "A", value: "opt_a" }] },
|
|
70
|
+
meta: {
|
|
71
|
+
type: "object",
|
|
72
|
+
required: false,
|
|
73
|
+
fields: { note: { type: "text", required: false } },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
expect(dts).toContain("record_id: string;"); // required → NOT nullable
|
|
79
|
+
expect(dts).toContain("due?: string | null;"); // optional scalar → clearable
|
|
80
|
+
expect(dts).toContain('status?: "opt_done" | null;');
|
|
81
|
+
expect(dts).toContain('tags?: ReadonlyArray<"opt_a"> | null;'); // optional multi
|
|
82
|
+
// The object itself is a top-level optional → nullable; its NESTED optional
|
|
83
|
+
// field mirrors the backend's plain `.optional()` (no `| null`).
|
|
84
|
+
expect(dts).toContain("note?: string;");
|
|
85
|
+
expect(dts).not.toContain("note?: string | null;");
|
|
86
|
+
});
|
|
61
87
|
it("maps record_link/select outputs the same way as inputs", () => {
|
|
62
88
|
const dts = generateAppWorkflowsDts({
|
|
63
89
|
lookup: {
|
package/dist/src/cli.js
CHANGED
|
@@ -31759,7 +31759,7 @@ declare module "@lotics/app-sdk" {
|
|
|
31759
31759
|
const inputLines = [];
|
|
31760
31760
|
const resultLines = [];
|
|
31761
31761
|
for (const [alias, declaration] of entries) {
|
|
31762
|
-
const valueType = declaration.inputs ? inputsToType(declaration.inputs) : "Record<string, unknown>";
|
|
31762
|
+
const valueType = declaration.inputs ? inputsToType(declaration.inputs, { nullableOptional: true }) : "Record<string, unknown>";
|
|
31763
31763
|
const aliasKey = isValidIdentifier(alias) ? alias : JSON.stringify(alias);
|
|
31764
31764
|
inputLines.push(` ${aliasKey}: ${valueType};`);
|
|
31765
31765
|
if (declaration.outputs) {
|
|
@@ -31778,15 +31778,18 @@ ${inputLines.join("\n")}
|
|
|
31778
31778
|
}
|
|
31779
31779
|
`;
|
|
31780
31780
|
}
|
|
31781
|
-
function inputsToType(inputs) {
|
|
31781
|
+
function inputsToType(inputs, opts) {
|
|
31782
|
+
const nullableOptional = opts?.nullableOptional === true;
|
|
31782
31783
|
const fields = [];
|
|
31783
31784
|
for (const [key, decl] of Object.entries(inputs)) {
|
|
31784
31785
|
if (decl === null || typeof decl !== "object") continue;
|
|
31785
31786
|
const d = decl;
|
|
31786
31787
|
const tsType = inputDeclToTsType(d);
|
|
31787
|
-
const
|
|
31788
|
+
const isOptional = d.required === false;
|
|
31789
|
+
const fieldType = isOptional && nullableOptional ? `${tsType} | null` : tsType;
|
|
31790
|
+
const optional = isOptional ? "?" : "";
|
|
31788
31791
|
const fieldKey = isValidIdentifier(key) ? key : JSON.stringify(key);
|
|
31789
|
-
fields.push(` ${fieldKey}${optional}: ${
|
|
31792
|
+
fields.push(` ${fieldKey}${optional}: ${fieldType};`);
|
|
31790
31793
|
}
|
|
31791
31794
|
if (fields.length === 0) return "Record<string, never>";
|
|
31792
31795
|
return `{
|