@lotics/app-sdk 0.114.0 → 0.115.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.js +352 -207
- package/docs/mutations.md +2 -3
- package/docs/workflows.md +13 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23971,6 +23971,180 @@ function everyOptionMarked(nameOf2) {
|
|
|
23971
23971
|
};
|
|
23972
23972
|
}
|
|
23973
23973
|
|
|
23974
|
+
// ../shared/src/schemas/iam.ts
|
|
23975
|
+
var jsonSchemaSchema = zod_default.string();
|
|
23976
|
+
var organizationSchema = zod_default.object({
|
|
23977
|
+
id: zod_default.string().describe("Unique identifier for the organization"),
|
|
23978
|
+
name: zod_default.string().describe("Display name of the organization"),
|
|
23979
|
+
slug: zod_default.string().nullable().describe("Slug for the organization")
|
|
23980
|
+
});
|
|
23981
|
+
var memberAccountTypeSchema = zod_default.enum(["self_managed", "admin_managed", "service"]).describe(
|
|
23982
|
+
"How the account is credentialed. admin_managed means an admin chose the sign-in name and set the password; the account has NO email address at all, so nothing can be mailed to it until the member claims one they control. service means no credential exists at all \u2014 the membership is an integration's identity, reached only by an API key bound to it."
|
|
23983
|
+
);
|
|
23984
|
+
var ALL_ACCOUNT_TYPES = memberAccountTypeSchema.options;
|
|
23985
|
+
var memberSchema = zod_default.object({
|
|
23986
|
+
member_id: zod_default.string().describe("Unique identifier for the member"),
|
|
23987
|
+
organization_id: zod_default.string().describe("Organization ID the member belongs to"),
|
|
23988
|
+
name: zod_default.string().describe("Display name of the member"),
|
|
23989
|
+
email_address: zod_default.string().nullable().describe(
|
|
23990
|
+
"What the member types to sign in: an email address for a self-managed member, an administrator-issued name (e.g. giamdoc.sunrise) for an admin-managed one. The latter is NOT an address and cannot receive mail \u2014 the field name predates the split. Null for a service identity, which holds no credential and signs in nowhere."
|
|
23991
|
+
),
|
|
23992
|
+
role: zod_default.enum(["owner", "admin", "member"]).describe("Organization role"),
|
|
23993
|
+
image: zod_default.string().nullable().optional().describe("URL to the member's profile picture"),
|
|
23994
|
+
account_type: memberAccountTypeSchema,
|
|
23995
|
+
archived: zod_default.boolean().optional().describe(
|
|
23996
|
+
"True when this person has been removed from the organization. The membership survives removal so records naming them keep resolving; it grants nothing. Optional because an older server omits it \u2014 absent means active."
|
|
23997
|
+
),
|
|
23998
|
+
joined_at: zod_default.string().optional().describe(
|
|
23999
|
+
"ISO timestamp of when this membership began. Optional because an older server omits it \u2014 absent means not told, never 'joined at the epoch'."
|
|
24000
|
+
)
|
|
24001
|
+
});
|
|
24002
|
+
var pendingInvitationSchema = zod_default.object({
|
|
24003
|
+
invitation_id: zod_default.string().describe("Unique identifier for the pending invitation"),
|
|
24004
|
+
organization_id: zod_default.string().describe("Organization the invitee will join on accept"),
|
|
24005
|
+
email: zod_default.string().describe("Email the invitation was sent to"),
|
|
24006
|
+
name: zod_default.string().nullable().describe("Admin-supplied display name (carried onto member.name on accept; invitee can override at accept time)"),
|
|
24007
|
+
role: zod_default.enum(["owner", "admin", "member"]).describe("Role the invitee will receive on accept"),
|
|
24008
|
+
expires_at: zod_default.string().describe("ISO timestamp when the invitation expires")
|
|
24009
|
+
});
|
|
24010
|
+
var ianaTimezoneSchema = zod_default.string().refine(
|
|
24011
|
+
(tz) => {
|
|
24012
|
+
try {
|
|
24013
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
24014
|
+
return true;
|
|
24015
|
+
} catch {
|
|
24016
|
+
return false;
|
|
24017
|
+
}
|
|
24018
|
+
},
|
|
24019
|
+
{ message: "must be an IANA timezone name, e.g. Asia/Ho_Chi_Minh or America/New_York" }
|
|
24020
|
+
).describe("IANA timezone name (e.g., 'America/New_York', 'Asia/Ho_Chi_Minh')");
|
|
24021
|
+
var currencyCodeSchema = zod_default.string().trim().toUpperCase().regex(/^[A-Z]{3}$/, "must be an ISO 4217 currency code, e.g. VND, USD, EUR").describe("ISO 4217 currency code (e.g. 'VND', 'USD', 'EUR'). Upper-cased.");
|
|
24022
|
+
var workspaceSchema = zod_default.object({
|
|
24023
|
+
id: zod_default.string().describe("Unique identifier for the workspace"),
|
|
24024
|
+
name: zod_default.string().describe("Display name of the workspace"),
|
|
24025
|
+
default_currency: zod_default.string().describe("Default currency code for the workspace"),
|
|
24026
|
+
// Plain string on the way OUT, deliberately. This is a response schema, so
|
|
24027
|
+
// validating here would make the server reject its own stored rows — and on
|
|
24028
|
+
// the workspace LIST one bad row fails the entire array. The zone is enforced
|
|
24029
|
+
// where it is written; a read is too late to do anything about it anyway.
|
|
24030
|
+
timezone: zod_default.string().describe("IANA timezone name (e.g., 'America/New_York', 'Asia/Ho_Chi_Minh')"),
|
|
24031
|
+
organization_id: zod_default.string().describe("Organization ID this workspace belongs to"),
|
|
24032
|
+
created_at: zod_default.string().describe("Timestamp when workspace was created")
|
|
24033
|
+
});
|
|
24034
|
+
var memberGroupSchema = zod_default.object({
|
|
24035
|
+
id: zod_default.string().describe("Unique identifier for the member group"),
|
|
24036
|
+
name: zod_default.string().describe("Display name of the group"),
|
|
24037
|
+
description: zod_default.string().optional().describe("Optional description of the group's purpose"),
|
|
24038
|
+
member_ids: zod_default.array(zod_default.string()).describe("Array of member IDs in this group"),
|
|
24039
|
+
invitation_ids: zod_default.array(zod_default.string()).describe("Array of pending invitation IDs pre-granted membership; migrated to member_ids on accept"),
|
|
24040
|
+
organization_id: zod_default.string().describe("ID of the organization this group belongs to"),
|
|
24041
|
+
created_at: zod_default.string().describe("Timestamp when group was created"),
|
|
24042
|
+
created_by: zod_default.string().describe("Member ID of the user who created this group")
|
|
24043
|
+
});
|
|
24044
|
+
var principalGroupSchema = zod_default.object({
|
|
24045
|
+
id: zod_default.string().describe("Unique identifier for the member group"),
|
|
24046
|
+
name: zod_default.string().describe("Display name of the group"),
|
|
24047
|
+
description: zod_default.string().optional().describe("Optional description of the group's purpose"),
|
|
24048
|
+
member_count: zod_default.number().int().describe("Number of accepted members in the group"),
|
|
24049
|
+
invitation_count: zod_default.number().int().describe("Number of pending invitations pre-granted membership")
|
|
24050
|
+
});
|
|
24051
|
+
var PUBLIC_PRINCIPAL_ID = "public";
|
|
24052
|
+
var principalSchema = zod_default.discriminatedUnion("type", [
|
|
24053
|
+
zod_default.object({
|
|
24054
|
+
type: zod_default.literal("member").describe("Principal type: individual member"),
|
|
24055
|
+
id: zod_default.string().describe("Member ID")
|
|
24056
|
+
}),
|
|
24057
|
+
zod_default.object({
|
|
24058
|
+
type: zod_default.literal("member_group").describe("Principal type: group of members"),
|
|
24059
|
+
id: zod_default.string().describe("Member group ID")
|
|
24060
|
+
}),
|
|
24061
|
+
zod_default.object({
|
|
24062
|
+
type: zod_default.literal("organization").describe("Principal type: all members in organization"),
|
|
24063
|
+
id: zod_default.string().describe("Organization ID")
|
|
24064
|
+
}),
|
|
24065
|
+
zod_default.object({
|
|
24066
|
+
type: zod_default.literal("public").describe("Principal type: anyone, including unauthenticated visitors"),
|
|
24067
|
+
id: zod_default.literal(PUBLIC_PRINCIPAL_ID).describe("Always the `public` sentinel \u2014 no entity")
|
|
24068
|
+
}),
|
|
24069
|
+
// Pending invitation as principal: lets admins pre-grant group membership and
|
|
24070
|
+
// resource access before the invitee accepts. Never resolved as a Subject —
|
|
24071
|
+
// a pending invitee cannot authenticate, so these bindings stay dormant until
|
|
24072
|
+
// accept-time migration rewrites them to type:"member".
|
|
24073
|
+
zod_default.object({
|
|
24074
|
+
type: zod_default.literal("invitation").describe("Principal type: pending invitation (not yet accepted)"),
|
|
24075
|
+
id: zod_default.string().describe("Invitation ID")
|
|
24076
|
+
})
|
|
24077
|
+
]);
|
|
24078
|
+
var subjectSchema = zod_default.discriminatedUnion("type", [
|
|
24079
|
+
zod_default.object({
|
|
24080
|
+
type: zod_default.literal("member").describe("Subject type: authenticated member"),
|
|
24081
|
+
id: zod_default.string().describe("Member ID")
|
|
24082
|
+
}),
|
|
24083
|
+
zod_default.object({
|
|
24084
|
+
type: zod_default.literal("public").describe("Subject type: unauthenticated visitor")
|
|
24085
|
+
})
|
|
24086
|
+
]);
|
|
24087
|
+
var resourceTypeSchema = zod_default.enum(["table", "table_view", "app", "automation", "skill", "document_template", "instruction", "knowledge_doc", "connected_account"]);
|
|
24088
|
+
var RESOURCE_ROLE_ACTIONS = {
|
|
24089
|
+
table: {
|
|
24090
|
+
editor: ["read_data", "write_data"],
|
|
24091
|
+
viewer: ["read_data"]
|
|
24092
|
+
},
|
|
24093
|
+
app: { manager: ["use", "manage"], user: ["use"] },
|
|
24094
|
+
skill: { manager: ["use", "manage"], user: ["use"] },
|
|
24095
|
+
document_template: { manager: ["use", "manage"], user: ["use"] },
|
|
24096
|
+
instruction: { manager: ["use", "manage"], user: ["use"] },
|
|
24097
|
+
knowledge_doc: { manager: ["use", "manage"], user: ["use"] },
|
|
24098
|
+
connected_account: { manager: ["use", "manage"], user: ["use"] },
|
|
24099
|
+
table_view: {},
|
|
24100
|
+
automation: {}
|
|
24101
|
+
};
|
|
24102
|
+
function shareableRolesFor(type) {
|
|
24103
|
+
return Object.keys(RESOURCE_ROLE_ACTIONS[type]);
|
|
24104
|
+
}
|
|
24105
|
+
var SHAREABLE_RESOURCE_TYPES = resourceTypeSchema.options.filter(
|
|
24106
|
+
(type) => shareableRolesFor(type).length > 0
|
|
24107
|
+
);
|
|
24108
|
+
var accessPrincipalSchema = zod_default.discriminatedUnion("type", [
|
|
24109
|
+
zod_default.object({
|
|
24110
|
+
type: zod_default.literal("member").describe("Principal type: an individual member"),
|
|
24111
|
+
id: zod_default.string().describe("Member ID")
|
|
24112
|
+
}),
|
|
24113
|
+
zod_default.object({
|
|
24114
|
+
type: zod_default.literal("member_group").describe("Principal type: a member group"),
|
|
24115
|
+
id: zod_default.string().describe("Group ID")
|
|
24116
|
+
}),
|
|
24117
|
+
zod_default.object({
|
|
24118
|
+
type: zod_default.literal("organization").describe("Principal type: everyone in the organization"),
|
|
24119
|
+
id: zod_default.string().describe("Organization ID")
|
|
24120
|
+
})
|
|
24121
|
+
]);
|
|
24122
|
+
var resourceAccessSchema = zod_default.object({
|
|
24123
|
+
resource_id: zod_default.string().describe("ID of the resource these principals can reach"),
|
|
24124
|
+
principals: zod_default.array(accessPrincipalSchema).describe("Members, groups and the organization that have been granted access")
|
|
24125
|
+
});
|
|
24126
|
+
var contentResourceTypeSchema = zod_default.enum(["knowledge_doc", "document_template"]);
|
|
24127
|
+
var contentItemSchema = zod_default.object({
|
|
24128
|
+
id: zod_default.string().describe("ID of the knowledge doc or template"),
|
|
24129
|
+
name: zod_default.string().describe("Its name"),
|
|
24130
|
+
owner_member_id: zod_default.string().nullable().describe("Member who owns it; null when nobody does"),
|
|
24131
|
+
created_at: zod_default.string().describe("When it was created"),
|
|
24132
|
+
updated_at: zod_default.string().nullable().describe("When a knowledge doc was last changed; null for a template"),
|
|
24133
|
+
hidden_at: zod_default.string().nullable().describe("When a knowledge doc was hidden; null when it is in use, and for a template"),
|
|
24134
|
+
template_type: zod_default.string().nullable().describe("A template's type (html, email, word, excel, pdf-form); null for a knowledge doc"),
|
|
24135
|
+
can_open: zod_default.boolean().describe("Whether the caller may open it; false means it is not shared with them"),
|
|
24136
|
+
principals: zod_default.array(accessPrincipalSchema).describe("Members, groups and the organization it has been shared with, besides its owner")
|
|
24137
|
+
});
|
|
24138
|
+
var resourceRoleBindingSchema = zod_default.object({
|
|
24139
|
+
id: zod_default.string().describe("Unique identifier for the role binding"),
|
|
24140
|
+
principal: principalSchema.describe(
|
|
24141
|
+
"Who this role binding applies to (member, group, or organization)"
|
|
24142
|
+
),
|
|
24143
|
+
resource_type: resourceTypeSchema.describe("Type of resource being accessed"),
|
|
24144
|
+
resource_id: zod_default.string().describe("ID of the specific resource"),
|
|
24145
|
+
role: zod_default.string().describe("Role name (e.g., 'viewer', 'editor', 'manager', 'user')")
|
|
24146
|
+
});
|
|
24147
|
+
|
|
23974
24148
|
// ../shared/src/diacritics.ts
|
|
23975
24149
|
var COMBINING_MARKS = /[̀-ͯ]/g;
|
|
23976
24150
|
function foldDiacritics(value) {
|
|
@@ -24831,7 +25005,7 @@ var tableFieldTypeConfigSchema = zod_default.discriminatedUnion("type", [
|
|
|
24831
25005
|
zod_default.object({
|
|
24832
25006
|
type: zod_default.literal("number"),
|
|
24833
25007
|
format: numberFormatSchema.default("number"),
|
|
24834
|
-
currency:
|
|
25008
|
+
currency: currencyCodeSchema.optional(),
|
|
24835
25009
|
unit: zod_default.string().optional().describe(UNIT_DESCRIPTION),
|
|
24836
25010
|
unit_field: zod_default.string().optional().describe(UNIT_FIELD_DESCRIPTION),
|
|
24837
25011
|
currency_field: zod_default.string().optional().describe(CURRENCY_FIELD_DESCRIPTION),
|
|
@@ -24900,7 +25074,7 @@ var tableFieldTypeConfigSchema = zod_default.discriminatedUnion("type", [
|
|
|
24900
25074
|
}),
|
|
24901
25075
|
zod_default.object({
|
|
24902
25076
|
type: zod_default.literal("formula"),
|
|
24903
|
-
formula: formulaInputSchema
|
|
25077
|
+
formula: formulaInputSchema.extend({ currency: currencyCodeSchema.optional() })
|
|
24904
25078
|
}).superRefine((field, ctx) => figureIssue(["formula"])(field.formula, ctx)),
|
|
24905
25079
|
// `button` is deliberately absent: button fields are legacy — this union
|
|
24906
25080
|
// shapes CREATE input only, and new buttons can no longer be created
|
|
@@ -24972,173 +25146,6 @@ function projectCell(field, value) {
|
|
|
24972
25146
|
}
|
|
24973
25147
|
}
|
|
24974
25148
|
|
|
24975
|
-
// ../shared/src/walk_workflow_expression.ts
|
|
24976
|
-
var LINKED_CALL = "linked";
|
|
24977
|
-
var GRAMMAR_NAME_SHAPE = `${LINKED_CALL}(record["fld_link"])[0] fetches one linked row`;
|
|
24978
|
-
var RUNTIME_KEYS = /* @__PURE__ */ new Set([
|
|
24979
|
-
"timezone",
|
|
24980
|
-
"workflow_id",
|
|
24981
|
-
"execution_id",
|
|
24982
|
-
"workspace_id",
|
|
24983
|
-
"organization_id",
|
|
24984
|
-
"now",
|
|
24985
|
-
"change_origin",
|
|
24986
|
-
"execution_principal",
|
|
24987
|
-
"triggered_by_member_id"
|
|
24988
|
-
]);
|
|
24989
|
-
var CURRENT_MEMBER_IN_ANY_GROUP = "current_member_in_any_group";
|
|
24990
|
-
var PARKED_RUNTIME_KEYS = /* @__PURE__ */ new Map([
|
|
24991
|
-
[
|
|
24992
|
-
"execution_principal",
|
|
24993
|
-
`runtime.execution_principal is not readable \u2014 a workflow runs under the OWNER's authority, so it never answers "is the person who triggered this allowed?". Authorize the caller with ${CURRENT_MEMBER_IN_ANY_GROUP}(["grp_\u2026"]) or read runtime.triggered_by_member_id.`
|
|
24994
|
-
]
|
|
24995
|
-
]);
|
|
24996
|
-
var READABLE_RUNTIME_KEYS = Array.from(RUNTIME_KEYS).filter(
|
|
24997
|
-
(key) => !PARKED_RUNTIME_KEYS.has(key)
|
|
24998
|
-
);
|
|
24999
|
-
var INLINE_HELPERS = [
|
|
25000
|
-
"formatCurrency",
|
|
25001
|
-
"randomNumber",
|
|
25002
|
-
"randomAlphaNumeric",
|
|
25003
|
-
"sample",
|
|
25004
|
-
// Actor authorization predicate — resolves the triggering member's groups
|
|
25005
|
-
// server-side (bound in the backend helper registry, not a pure shared
|
|
25006
|
-
// helper).
|
|
25007
|
-
CURRENT_MEMBER_IN_ANY_GROUP
|
|
25008
|
-
];
|
|
25009
|
-
var HELPERS = /* @__PURE__ */ new Set([
|
|
25010
|
-
...Object.keys(expression_array_exports),
|
|
25011
|
-
...Object.keys(expression_string_exports),
|
|
25012
|
-
...Object.keys(expression_math_exports),
|
|
25013
|
-
...Object.keys(expression_object_exports),
|
|
25014
|
-
...Object.keys(expression_type_exports),
|
|
25015
|
-
...Object.keys(getDateExpressionFunctions()),
|
|
25016
|
-
...INLINE_HELPERS
|
|
25017
|
-
]);
|
|
25018
|
-
function isHelper(name) {
|
|
25019
|
-
return HELPERS.has(name);
|
|
25020
|
-
}
|
|
25021
|
-
var HELPER_MENU = [
|
|
25022
|
-
`text \u2014 ${Object.keys(expression_string_exports).sort().join(", ")}`,
|
|
25023
|
-
`number \u2014 ${Object.keys(expression_math_exports).sort().join(", ")}`,
|
|
25024
|
-
`array \u2014 ${Object.keys(expression_array_exports).sort().join(", ")}`,
|
|
25025
|
-
`object \u2014 ${Object.keys(expression_object_exports).sort().join(", ")}`,
|
|
25026
|
-
`type/null \u2014 ${Object.keys(expression_type_exports).sort().join(", ")}`,
|
|
25027
|
-
`date \u2014 ${Object.keys(getDateExpressionFunctions()).sort().join(", ")}`,
|
|
25028
|
-
`other \u2014 ${[...INLINE_HELPERS].sort().join(", ")}`
|
|
25029
|
-
].join("; ");
|
|
25030
|
-
var AWAIT_IF_FORM = "Write the if form: let x = null; if (<condition>) { x = await tool({...}); }";
|
|
25031
|
-
var AWAIT_IN_OPTIONAL_CHAIN = `An await after \`?.\` runs only when the value before it is not null. ${AWAIT_IF_FORM}`;
|
|
25032
|
-
var CALLBACK_HELPERS = /* @__PURE__ */ new Set([
|
|
25033
|
-
"filter",
|
|
25034
|
-
"find",
|
|
25035
|
-
"pluck",
|
|
25036
|
-
"sortBy",
|
|
25037
|
-
"sumBy",
|
|
25038
|
-
"meanBy",
|
|
25039
|
-
"minBy",
|
|
25040
|
-
"maxBy",
|
|
25041
|
-
"countBy",
|
|
25042
|
-
"groupBy",
|
|
25043
|
-
"uniqueBy",
|
|
25044
|
-
"some",
|
|
25045
|
-
"every",
|
|
25046
|
-
"differenceBy",
|
|
25047
|
-
"intersectionBy",
|
|
25048
|
-
"reduce"
|
|
25049
|
-
]);
|
|
25050
|
-
var LAMBDA_POSITION_HINT = `Function expressions are not supported here. A lambda \u2014 either \`(x) => <expr>\` or \`function (x) { return <expr>; }\` \u2014 is legal only INLINE as a callback argument to ${[...CALLBACK_HELPERS].sort().join(" / ")}, e.g. pluck(rows, (r) => r.qty * r.price). To NAME reusable logic, declare it at the top level instead: \`function toLine(l = {}) { return { ... }; }\` \u2014 every parameter needs a default, and the body is inlined at each call site. Binding a lambda to a name (const f = (x) => ...) or calling one directly stays unsupported.`;
|
|
25051
|
-
var HELPER_ALIASES = /* @__PURE__ */ new Map([
|
|
25052
|
-
["String", "toString"],
|
|
25053
|
-
["Number", "toNumber"],
|
|
25054
|
-
["parseFloat", "toNumber"],
|
|
25055
|
-
// faithful: toNumber IS parseFloat semantics
|
|
25056
|
-
["toUpperCase", "upper"],
|
|
25057
|
-
["toLowerCase", "lower"],
|
|
25058
|
-
["map", "pluck"],
|
|
25059
|
-
// arr.map(x => …) ≡ pluck(arr, x => …)
|
|
25060
|
-
["toFixed", "formatNumber"]
|
|
25061
|
-
// x.toFixed(2) ≡ formatNumber(x, 2)
|
|
25062
|
-
]);
|
|
25063
|
-
var REJECTED_HELPERS = /* @__PURE__ */ new Map([
|
|
25064
|
-
[
|
|
25065
|
-
"parseInt",
|
|
25066
|
-
{
|
|
25067
|
-
message: "parseInt is not supported \u2014 parse with toNumber(x), then apply floor(...), ceil(...), or round(...) to get a whole number. floor rounds down (toward negative infinity), so for negative values choose the rounding deliberately.",
|
|
25068
|
-
construct: "parse_int"
|
|
25069
|
-
}
|
|
25070
|
-
]
|
|
25071
|
-
]);
|
|
25072
|
-
var NAMESPACE_HELPERS = /* @__PURE__ */ new Map([
|
|
25073
|
-
["JSON", /* @__PURE__ */ new Map([["parse", "parseJson"], ["stringify", "toJson"]])],
|
|
25074
|
-
["Array", /* @__PURE__ */ new Map([["isArray", "isArray"]])],
|
|
25075
|
-
["Object", /* @__PURE__ */ new Map([["keys", "keys"], ["values", "values"], ["entries", "entries"]])],
|
|
25076
|
-
["Number", /* @__PURE__ */ new Map([["parseFloat", "toNumber"]])]
|
|
25077
|
-
]);
|
|
25078
|
-
var METHOD_FORM_NAMES = /* @__PURE__ */ new Set([
|
|
25079
|
-
// String.prototype
|
|
25080
|
-
"trim",
|
|
25081
|
-
"toUpperCase",
|
|
25082
|
-
"toLowerCase",
|
|
25083
|
-
"replace",
|
|
25084
|
-
"replaceAll",
|
|
25085
|
-
"split",
|
|
25086
|
-
"substring",
|
|
25087
|
-
"padStart",
|
|
25088
|
-
"padEnd",
|
|
25089
|
-
"startsWith",
|
|
25090
|
-
"endsWith",
|
|
25091
|
-
// String.prototype + Array.prototype
|
|
25092
|
-
"includes",
|
|
25093
|
-
"slice",
|
|
25094
|
-
"at",
|
|
25095
|
-
"concat",
|
|
25096
|
-
// Array.prototype
|
|
25097
|
-
"join",
|
|
25098
|
-
"reverse",
|
|
25099
|
-
"map",
|
|
25100
|
-
"filter",
|
|
25101
|
-
"find",
|
|
25102
|
-
"some",
|
|
25103
|
-
"every",
|
|
25104
|
-
"reduce",
|
|
25105
|
-
// Number.prototype / Object.prototype
|
|
25106
|
-
"toFixed",
|
|
25107
|
-
"toString"
|
|
25108
|
-
]);
|
|
25109
|
-
var METHOD_FORM_MENU = [...METHOD_FORM_NAMES].sort().join(", ");
|
|
25110
|
-
var PROPERTY_HELPERS = /* @__PURE__ */ new Map([["length", "length"]]);
|
|
25111
|
-
var HELPER_SPELLINGS = (() => {
|
|
25112
|
-
const out = /* @__PURE__ */ new Map();
|
|
25113
|
-
const add = (helper, spelling) => {
|
|
25114
|
-
if (isHelper(helper) && !out.has(helper)) out.set(helper, spelling);
|
|
25115
|
-
};
|
|
25116
|
-
for (const name of METHOD_FORM_NAMES) add(HELPER_ALIASES.get(name) ?? name, { kind: "method", name });
|
|
25117
|
-
for (const [name, helper] of PROPERTY_HELPERS) add(helper, { kind: "property", name });
|
|
25118
|
-
for (const [namespace, members2] of NAMESPACE_HELPERS) {
|
|
25119
|
-
for (const [member, helper] of members2) add(helper, { kind: "namespace", namespace, member });
|
|
25120
|
-
}
|
|
25121
|
-
for (const fn of Object.keys(expression_math_exports)) {
|
|
25122
|
-
if (fn !== "random" && typeof Reflect.get(Math, fn) === "function") {
|
|
25123
|
-
add(fn, { kind: "namespace", namespace: "Math", member: fn });
|
|
25124
|
-
}
|
|
25125
|
-
}
|
|
25126
|
-
return out;
|
|
25127
|
-
})();
|
|
25128
|
-
var RESERVED_CALLABLE_NAMES = /* @__PURE__ */ new Set([
|
|
25129
|
-
...REJECTED_HELPERS.keys(),
|
|
25130
|
-
...NAMESPACE_HELPERS.keys(),
|
|
25131
|
-
"Math",
|
|
25132
|
-
"Boolean"
|
|
25133
|
-
]);
|
|
25134
|
-
var LINKED_ARITY = `${LINKED_CALL}(...) takes exactly one argument \u2014 the read of the link field, e.g. ${LINKED_CALL}(record["fld_link"])[0] for one row or ${LINKED_CALL}(record["fld_link"]) for every row.`;
|
|
25135
|
-
var LINKED_ARGUMENT_SHAPE = `The argument of ${LINKED_CALL}(...) must END on the link field, because that field is what it follows \u2014 ${LINKED_CALL}(record["fld_link"])[0]. A value that names no field (a binding, a literal, a helper result) cannot say which link to follow; read the field on it: ${LINKED_CALL}(ids["fld_link"]).`;
|
|
25136
|
-
var LINKED_OPTIONAL_RESULT = `${LINKED_CALL}(...) always returns an array, never null, so \`?.\` on its result can never fire. The argument takes a guard \u2014 ${LINKED_CALL}(record?.["fld_link"])[0] \u2014 and so does the fetched row, which is null where the linked record is deleted or unreadable: ${LINKED_CALL}(record["fld_link"])[0]?.["fld_x"].`;
|
|
25137
|
-
var LINKED_METHOD_FORM = `${LINKED_CALL} has no method form \u2014 write ${LINKED_CALL}(record["fld_link"])[0], with the link read as the argument.`;
|
|
25138
|
-
var LINKED_ON_NON_RECORD = `${LINKED_CALL}(...) follows a link field on a record, and this value is not one. Read the record first, then follow the link: ${LINKED_CALL}(record["fld_link"])[0].`;
|
|
25139
|
-
var LINKED_ON_CHANGES = `A change diff is not a record, so ${LINKED_CALL}(...) cannot follow a link off it. Read the post-state with ${LINKED_CALL}(record["fld_link"]) or the pre-state with ${LINKED_CALL}(prev_record["fld_link"]).`;
|
|
25140
|
-
var READ_ROOT_HINT = `A read starts from a name: a reserved identifier (record, prev_record, changes, runtime, trigger, index), a binding, or ${LINKED_CALL}(record["fld_link"]). Bind the value first \u2014 const x = <expression>; \u2014 then read x.y.`;
|
|
25141
|
-
|
|
25142
25149
|
// ../shared/src/schemas/workflow_expressions.ts
|
|
25143
25150
|
var runtimeKeySchema = zod_default.enum([
|
|
25144
25151
|
"timezone",
|
|
@@ -25148,15 +25155,6 @@ var runtimeKeySchema = zod_default.enum([
|
|
|
25148
25155
|
"organization_id",
|
|
25149
25156
|
"now",
|
|
25150
25157
|
"change_origin",
|
|
25151
|
-
// PARKED — accepted in stored ASTs, rejected for new saves at parse (see
|
|
25152
|
-
// `PARKED_RUNTIME_KEYS` in walk_workflow_expression.ts). It is the OWNER's
|
|
25153
|
-
// principal (a workflow runs under owner authority), so it can't answer the
|
|
25154
|
-
// caller-authorization question anyone reads it for; the generated `.d.ts`
|
|
25155
|
-
// omits it and the docs point at `current_member_in_any_group` /
|
|
25156
|
-
// `runtime.triggered_by_member_id` instead. The entry stays so any stored
|
|
25157
|
-
// AST keeps validating and executing — drop it only once a prod probe shows
|
|
25158
|
-
// zero references (`workflows.steps_v2::text LIKE '%execution_principal%'`).
|
|
25159
|
-
"execution_principal",
|
|
25160
25158
|
"triggered_by_member_id"
|
|
25161
25159
|
]);
|
|
25162
25160
|
var nestedExpressionSchema = zod_default.lazy(
|
|
@@ -25329,6 +25327,154 @@ var templatePartSchema = zod_default.lazy(
|
|
|
25329
25327
|
])
|
|
25330
25328
|
);
|
|
25331
25329
|
|
|
25330
|
+
// ../shared/src/walk_workflow_expression.ts
|
|
25331
|
+
var LINKED_CALL = "linked";
|
|
25332
|
+
var GRAMMAR_NAME_SHAPE = `${LINKED_CALL}(record["fld_link"])[0] fetches one linked row`;
|
|
25333
|
+
var RUNTIME_KEYS = new Set(runtimeKeySchema.options);
|
|
25334
|
+
var CURRENT_MEMBER_IN_ANY_GROUP = "current_member_in_any_group";
|
|
25335
|
+
var INLINE_HELPERS = [
|
|
25336
|
+
"formatCurrency",
|
|
25337
|
+
"randomNumber",
|
|
25338
|
+
"randomAlphaNumeric",
|
|
25339
|
+
"sample",
|
|
25340
|
+
// Actor authorization predicate — resolves the triggering member's groups
|
|
25341
|
+
// server-side (bound in the backend helper registry, not a pure shared
|
|
25342
|
+
// helper).
|
|
25343
|
+
CURRENT_MEMBER_IN_ANY_GROUP
|
|
25344
|
+
];
|
|
25345
|
+
var HELPERS = /* @__PURE__ */ new Set([
|
|
25346
|
+
...Object.keys(expression_array_exports),
|
|
25347
|
+
...Object.keys(expression_string_exports),
|
|
25348
|
+
...Object.keys(expression_math_exports),
|
|
25349
|
+
...Object.keys(expression_object_exports),
|
|
25350
|
+
...Object.keys(expression_type_exports),
|
|
25351
|
+
...Object.keys(getDateExpressionFunctions()),
|
|
25352
|
+
...INLINE_HELPERS
|
|
25353
|
+
]);
|
|
25354
|
+
function isHelper(name) {
|
|
25355
|
+
return HELPERS.has(name);
|
|
25356
|
+
}
|
|
25357
|
+
var HELPER_MENU = [
|
|
25358
|
+
`text \u2014 ${Object.keys(expression_string_exports).sort().join(", ")}`,
|
|
25359
|
+
`number \u2014 ${Object.keys(expression_math_exports).sort().join(", ")}`,
|
|
25360
|
+
`array \u2014 ${Object.keys(expression_array_exports).sort().join(", ")}`,
|
|
25361
|
+
`object \u2014 ${Object.keys(expression_object_exports).sort().join(", ")}`,
|
|
25362
|
+
`type/null \u2014 ${Object.keys(expression_type_exports).sort().join(", ")}`,
|
|
25363
|
+
`date \u2014 ${Object.keys(getDateExpressionFunctions()).sort().join(", ")}`,
|
|
25364
|
+
`other \u2014 ${[...INLINE_HELPERS].sort().join(", ")}`
|
|
25365
|
+
].join("; ");
|
|
25366
|
+
var AWAIT_IF_FORM = "Write the if form: let x = null; if (<condition>) { x = await tool({...}); }";
|
|
25367
|
+
var AWAIT_IN_OPTIONAL_CHAIN = `An await after \`?.\` runs only when the value before it is not null. ${AWAIT_IF_FORM}`;
|
|
25368
|
+
var CALLBACK_HELPERS = /* @__PURE__ */ new Set([
|
|
25369
|
+
"filter",
|
|
25370
|
+
"find",
|
|
25371
|
+
"pluck",
|
|
25372
|
+
"sortBy",
|
|
25373
|
+
"sumBy",
|
|
25374
|
+
"meanBy",
|
|
25375
|
+
"minBy",
|
|
25376
|
+
"maxBy",
|
|
25377
|
+
"countBy",
|
|
25378
|
+
"groupBy",
|
|
25379
|
+
"uniqueBy",
|
|
25380
|
+
"some",
|
|
25381
|
+
"every",
|
|
25382
|
+
"differenceBy",
|
|
25383
|
+
"intersectionBy",
|
|
25384
|
+
"reduce"
|
|
25385
|
+
]);
|
|
25386
|
+
var LAMBDA_POSITION_HINT = `Function expressions are not supported here. A lambda \u2014 either \`(x) => <expr>\` or \`function (x) { return <expr>; }\` \u2014 is legal only INLINE as a callback argument to ${[...CALLBACK_HELPERS].sort().join(" / ")}, e.g. pluck(rows, (r) => r.qty * r.price). To NAME reusable logic, declare it at the top level instead: \`function toLine(l = {}) { return { ... }; }\` \u2014 every parameter needs a default, and the body is inlined at each call site. Binding a lambda to a name (const f = (x) => ...) or calling one directly stays unsupported.`;
|
|
25387
|
+
var HELPER_ALIASES = /* @__PURE__ */ new Map([
|
|
25388
|
+
["String", "toString"],
|
|
25389
|
+
["Number", "toNumber"],
|
|
25390
|
+
["parseFloat", "toNumber"],
|
|
25391
|
+
// faithful: toNumber IS parseFloat semantics
|
|
25392
|
+
["toUpperCase", "upper"],
|
|
25393
|
+
["toLowerCase", "lower"],
|
|
25394
|
+
["map", "pluck"],
|
|
25395
|
+
// arr.map(x => …) ≡ pluck(arr, x => …)
|
|
25396
|
+
["toFixed", "formatNumber"]
|
|
25397
|
+
// x.toFixed(2) ≡ formatNumber(x, 2)
|
|
25398
|
+
]);
|
|
25399
|
+
var REJECTED_HELPERS = /* @__PURE__ */ new Map([
|
|
25400
|
+
[
|
|
25401
|
+
"parseInt",
|
|
25402
|
+
{
|
|
25403
|
+
message: "parseInt is not supported \u2014 parse with toNumber(x), then apply floor(...), ceil(...), or round(...) to get a whole number. floor rounds down (toward negative infinity), so for negative values choose the rounding deliberately.",
|
|
25404
|
+
construct: "parse_int"
|
|
25405
|
+
}
|
|
25406
|
+
]
|
|
25407
|
+
]);
|
|
25408
|
+
var NAMESPACE_HELPERS = /* @__PURE__ */ new Map([
|
|
25409
|
+
["JSON", /* @__PURE__ */ new Map([["parse", "parseJson"], ["stringify", "toJson"]])],
|
|
25410
|
+
["Array", /* @__PURE__ */ new Map([["isArray", "isArray"]])],
|
|
25411
|
+
["Object", /* @__PURE__ */ new Map([["keys", "keys"], ["values", "values"], ["entries", "entries"]])],
|
|
25412
|
+
["Number", /* @__PURE__ */ new Map([["parseFloat", "toNumber"]])]
|
|
25413
|
+
]);
|
|
25414
|
+
var METHOD_FORM_NAMES = /* @__PURE__ */ new Set([
|
|
25415
|
+
// String.prototype
|
|
25416
|
+
"trim",
|
|
25417
|
+
"toUpperCase",
|
|
25418
|
+
"toLowerCase",
|
|
25419
|
+
"replace",
|
|
25420
|
+
"replaceAll",
|
|
25421
|
+
"split",
|
|
25422
|
+
"substring",
|
|
25423
|
+
"padStart",
|
|
25424
|
+
"padEnd",
|
|
25425
|
+
"startsWith",
|
|
25426
|
+
"endsWith",
|
|
25427
|
+
// String.prototype + Array.prototype
|
|
25428
|
+
"includes",
|
|
25429
|
+
"slice",
|
|
25430
|
+
"at",
|
|
25431
|
+
"concat",
|
|
25432
|
+
// Array.prototype
|
|
25433
|
+
"join",
|
|
25434
|
+
"reverse",
|
|
25435
|
+
"map",
|
|
25436
|
+
"filter",
|
|
25437
|
+
"find",
|
|
25438
|
+
"some",
|
|
25439
|
+
"every",
|
|
25440
|
+
"reduce",
|
|
25441
|
+
// Number.prototype / Object.prototype
|
|
25442
|
+
"toFixed",
|
|
25443
|
+
"toString"
|
|
25444
|
+
]);
|
|
25445
|
+
var METHOD_FORM_MENU = [...METHOD_FORM_NAMES].sort().join(", ");
|
|
25446
|
+
var PROPERTY_HELPERS = /* @__PURE__ */ new Map([["length", "length"]]);
|
|
25447
|
+
var HELPER_SPELLINGS = (() => {
|
|
25448
|
+
const out = /* @__PURE__ */ new Map();
|
|
25449
|
+
const add = (helper, spelling) => {
|
|
25450
|
+
if (isHelper(helper) && !out.has(helper)) out.set(helper, spelling);
|
|
25451
|
+
};
|
|
25452
|
+
for (const name of METHOD_FORM_NAMES) add(HELPER_ALIASES.get(name) ?? name, { kind: "method", name });
|
|
25453
|
+
for (const [name, helper] of PROPERTY_HELPERS) add(helper, { kind: "property", name });
|
|
25454
|
+
for (const [namespace, members2] of NAMESPACE_HELPERS) {
|
|
25455
|
+
for (const [member, helper] of members2) add(helper, { kind: "namespace", namespace, member });
|
|
25456
|
+
}
|
|
25457
|
+
for (const fn of Object.keys(expression_math_exports)) {
|
|
25458
|
+
if (fn !== "random" && typeof Reflect.get(Math, fn) === "function") {
|
|
25459
|
+
add(fn, { kind: "namespace", namespace: "Math", member: fn });
|
|
25460
|
+
}
|
|
25461
|
+
}
|
|
25462
|
+
return out;
|
|
25463
|
+
})();
|
|
25464
|
+
var RESERVED_CALLABLE_NAMES = /* @__PURE__ */ new Set([
|
|
25465
|
+
...REJECTED_HELPERS.keys(),
|
|
25466
|
+
...NAMESPACE_HELPERS.keys(),
|
|
25467
|
+
"Math",
|
|
25468
|
+
"Boolean"
|
|
25469
|
+
]);
|
|
25470
|
+
var LINKED_ARITY = `${LINKED_CALL}(...) takes exactly one argument \u2014 the read of the link field, e.g. ${LINKED_CALL}(record["fld_link"])[0] for one row or ${LINKED_CALL}(record["fld_link"]) for every row.`;
|
|
25471
|
+
var LINKED_ARGUMENT_SHAPE = `The argument of ${LINKED_CALL}(...) must END on the link field, because that field is what it follows \u2014 ${LINKED_CALL}(record["fld_link"])[0]. A value that names no field (a binding, a literal, a helper result) cannot say which link to follow; read the field on it: ${LINKED_CALL}(ids["fld_link"]).`;
|
|
25472
|
+
var LINKED_OPTIONAL_RESULT = `${LINKED_CALL}(...) always returns an array, never null, so \`?.\` on its result can never fire. The argument takes a guard \u2014 ${LINKED_CALL}(record?.["fld_link"])[0] \u2014 and so does the fetched row, which is null where the linked record is deleted or unreadable: ${LINKED_CALL}(record["fld_link"])[0]?.["fld_x"].`;
|
|
25473
|
+
var LINKED_METHOD_FORM = `${LINKED_CALL} has no method form \u2014 write ${LINKED_CALL}(record["fld_link"])[0], with the link read as the argument.`;
|
|
25474
|
+
var LINKED_ON_NON_RECORD = `${LINKED_CALL}(...) follows a link field on a record, and this value is not one. Read the record first, then follow the link: ${LINKED_CALL}(record["fld_link"])[0].`;
|
|
25475
|
+
var LINKED_ON_CHANGES = `A change diff is not a record, so ${LINKED_CALL}(...) cannot follow a link off it. Read the post-state with ${LINKED_CALL}(record["fld_link"]) or the pre-state with ${LINKED_CALL}(prev_record["fld_link"]).`;
|
|
25476
|
+
var READ_ROOT_HINT = `A read starts from a name: a reserved identifier (record, prev_record, changes, runtime, trigger, index), a binding, or ${LINKED_CALL}(record["fld_link"]). Bind the value first \u2014 const x = <expression>; \u2014 then read x.y.`;
|
|
25477
|
+
|
|
25332
25478
|
// ../shared/src/chat_models.ts
|
|
25333
25479
|
var MODEL_TIERS = ["haiku", "sonnet", "opus"];
|
|
25334
25480
|
var TIER_MODELS = {
|
|
@@ -26976,36 +27122,46 @@ var WorkflowEvalError = class extends Error {
|
|
|
26976
27122
|
detail;
|
|
26977
27123
|
};
|
|
26978
27124
|
var WorkflowTimeBudgetError = class extends Error {
|
|
26979
|
-
|
|
26980
|
-
|
|
27125
|
+
/** `waitedFor`: what the run's deadline passed waiting on, when that was not the run's own work. */
|
|
27126
|
+
constructor(subject, budgetMs, waitedFor) {
|
|
27127
|
+
super(`${subject} exceeded its time budget of ${budgetMs / 1e3}s${waitedFor === void 0 ? "" : ` waiting for ${waitedFor}`}.`);
|
|
26981
27128
|
this.name = "WorkflowTimeBudgetError";
|
|
26982
27129
|
}
|
|
26983
27130
|
};
|
|
26984
27131
|
var WorkflowExpressionBudgetError = class extends Error {
|
|
26985
|
-
constructor(
|
|
26986
|
-
super(
|
|
26987
|
-
|
|
26988
|
-
);
|
|
26989
|
-
this.exceeded = exceeded;
|
|
27132
|
+
constructor(budget) {
|
|
27133
|
+
super(budgetMessage(budget));
|
|
27134
|
+
this.budget = budget;
|
|
26990
27135
|
this.name = "WorkflowExpressionBudgetError";
|
|
26991
27136
|
}
|
|
26992
|
-
|
|
27137
|
+
budget;
|
|
26993
27138
|
};
|
|
26994
|
-
|
|
27139
|
+
function budgetMessage(budget) {
|
|
27140
|
+
switch (budget.exceeded) {
|
|
27141
|
+
case "bytes":
|
|
27142
|
+
return `The run built more than ${MAX_RUN_BUILT_BYTES / 1024 / 1024}MB of arrays, objects and text.`;
|
|
27143
|
+
case "heap":
|
|
27144
|
+
return `The expression used more than ${budget.heap_mb}MB of memory.`;
|
|
27145
|
+
case "compute":
|
|
27146
|
+
return `The expression computed for more than ${budget.compute_ms / 1e3}s.`;
|
|
27147
|
+
}
|
|
27148
|
+
}
|
|
26995
27149
|
var SLOT_BYTES = 8;
|
|
26996
27150
|
var CHAR_BYTES = 2;
|
|
27151
|
+
var OBJECT_BYTES = 56;
|
|
26997
27152
|
var EVALUATION_SLICE_MS = 20;
|
|
26998
27153
|
var turn = {
|
|
26999
27154
|
slice_started_ms: performance.now(),
|
|
27000
27155
|
pause: null
|
|
27001
27156
|
};
|
|
27002
|
-
function startRunBudget(subject, budgetMs) {
|
|
27003
|
-
return { deadline_ms: Date.now() + budgetMs, budget_ms: budgetMs, subject, built_bytes: 0 };
|
|
27157
|
+
function startRunBudget(subject, budgetMs, path) {
|
|
27158
|
+
return { host: "shared", deadline_ms: Date.now() + budgetMs, budget_ms: budgetMs, subject, path, built_bytes: 0 };
|
|
27004
27159
|
}
|
|
27005
27160
|
function runClock(run2) {
|
|
27006
27161
|
if (Date.now() > run2.deadline_ms) {
|
|
27007
27162
|
throw new WorkflowTimeBudgetError(run2.subject, run2.budget_ms);
|
|
27008
27163
|
}
|
|
27164
|
+
if (run2.host === "isolated") return null;
|
|
27009
27165
|
if (turn.pause === null && performance.now() - turn.slice_started_ms >= EVALUATION_SLICE_MS) {
|
|
27010
27166
|
turn.pause = new Promise((resolve) => setTimeout(resolve, 0)).then(() => {
|
|
27011
27167
|
turn.pause = null;
|
|
@@ -27015,15 +27171,16 @@ function runClock(run2) {
|
|
|
27015
27171
|
return turn.pause;
|
|
27016
27172
|
}
|
|
27017
27173
|
function chargeBuilt(run2, bytes) {
|
|
27174
|
+
if (run2.host === "isolated") return;
|
|
27018
27175
|
run2.built_bytes += bytes;
|
|
27019
27176
|
if (run2.built_bytes > MAX_RUN_BUILT_BYTES) {
|
|
27020
|
-
throw new WorkflowExpressionBudgetError("bytes");
|
|
27177
|
+
throw new WorkflowExpressionBudgetError({ exceeded: "bytes" });
|
|
27021
27178
|
}
|
|
27022
27179
|
}
|
|
27023
27180
|
function builtSize(value) {
|
|
27024
27181
|
if (typeof value === "string") return value.length * CHAR_BYTES;
|
|
27025
|
-
if (Array.isArray(value)) return value.length * SLOT_BYTES;
|
|
27026
|
-
if (typeof value === "object" && value !== null) return Object.keys(value).length * SLOT_BYTES;
|
|
27182
|
+
if (Array.isArray(value)) return OBJECT_BYTES + value.length * SLOT_BYTES;
|
|
27183
|
+
if (typeof value === "object" && value !== null) return OBJECT_BYTES + Object.keys(value).length * SLOT_BYTES;
|
|
27027
27184
|
return 0;
|
|
27028
27185
|
}
|
|
27029
27186
|
var PARSED_NODE_BYTES = 40;
|
|
@@ -27053,13 +27210,13 @@ function callbackWorkers() {
|
|
|
27053
27210
|
};
|
|
27054
27211
|
}
|
|
27055
27212
|
function evaluateWorkflowExpression(expr2, ctx) {
|
|
27056
|
-
const budget = {
|
|
27213
|
+
const budget = { workers: callbackWorkers(), settled: false };
|
|
27057
27214
|
return evaluateExpression(expr2, { ...ctx, evaluation_budget: budget }).finally(() => {
|
|
27058
27215
|
budget.settled = true;
|
|
27059
27216
|
});
|
|
27060
27217
|
}
|
|
27061
27218
|
async function evaluateExpression(expr2, ctx) {
|
|
27062
|
-
for (let pause =
|
|
27219
|
+
for (let pause = checkClock(ctx); pause !== null; pause = checkClock(ctx)) await pause;
|
|
27063
27220
|
switch (expr2.kind) {
|
|
27064
27221
|
case "lit":
|
|
27065
27222
|
return expr2.value;
|
|
@@ -27096,15 +27253,6 @@ async function evaluateExpression(expr2, ctx) {
|
|
|
27096
27253
|
return assertNever(expr2);
|
|
27097
27254
|
}
|
|
27098
27255
|
}
|
|
27099
|
-
function spendNode(ctx) {
|
|
27100
|
-
const budget = ctx.evaluation_budget;
|
|
27101
|
-
assertUnsettled(budget);
|
|
27102
|
-
budget.nodes += 1;
|
|
27103
|
-
if (budget.nodes > MAX_EVALUATION_NODES) {
|
|
27104
|
-
throw new WorkflowExpressionBudgetError("nodes");
|
|
27105
|
-
}
|
|
27106
|
-
return runClock(ctx.run_budget);
|
|
27107
|
-
}
|
|
27108
27256
|
function checkClock(ctx) {
|
|
27109
27257
|
assertUnsettled(ctx.evaluation_budget);
|
|
27110
27258
|
return runClock(ctx.run_budget);
|
|
@@ -28096,9 +28244,6 @@ function clientRuntime(input) {
|
|
|
28096
28244
|
},
|
|
28097
28245
|
get change_origin() {
|
|
28098
28246
|
return unread("change_origin");
|
|
28099
|
-
},
|
|
28100
|
-
get execution_principal() {
|
|
28101
|
-
return unread("execution_principal");
|
|
28102
28247
|
}
|
|
28103
28248
|
};
|
|
28104
28249
|
}
|
|
@@ -28131,7 +28276,7 @@ async function predictWorkflowEffects(input) {
|
|
|
28131
28276
|
trigger_table: null,
|
|
28132
28277
|
member_lookup: async () => unread("member display"),
|
|
28133
28278
|
lexical_bindings: [{}],
|
|
28134
|
-
run_budget: startRunBudget("Write prediction", PREDICTION_BUDGET_MS)
|
|
28279
|
+
run_budget: startRunBudget("Write prediction", PREDICTION_BUDGET_MS, "write")
|
|
28135
28280
|
};
|
|
28136
28281
|
const walk = { ctx, effects: [], written, input, unsure: false, aligned: true };
|
|
28137
28282
|
try {
|
package/docs/mutations.md
CHANGED
|
@@ -397,9 +397,8 @@ Why a full-form snapshot save is a bug, not a style choice — three independent
|
|
|
397
397
|
moment *any* field is touched — there is no value-equality pass (deep equality over stored
|
|
398
398
|
shapes would be a silent lock bypass). A snapshot touches every field, so it turns every
|
|
399
399
|
save against a locked record into a total rejection even when nothing changed.
|
|
400
|
-
2.
|
|
401
|
-
|
|
402
|
-
record — a snapshot save fires them for fields the user never edited.
|
|
400
|
+
2. **A table's checks and `after_update` workflows run on the write.** They run on every
|
|
401
|
+
write that touches the record — a snapshot save runs them for fields the user never edited.
|
|
403
402
|
3. **Snapshots clobber concurrent edits.** An update writes exactly the fields present in the
|
|
404
403
|
payload; unsent fields keep their current value. A diff therefore coexists with a
|
|
405
404
|
colleague's concurrent edit to a *different* field — a snapshot overwrites it with the
|
package/docs/workflows.md
CHANGED
|
@@ -344,8 +344,8 @@ try {
|
|
|
344
344
|
|
|
345
345
|
`e` is shaped `{ message, type, step_id?, detail? }`. Caught: tool errors and expression errors
|
|
346
346
|
thrown inside the body. **Not caught:** `validate` failures — a check that throws included — and
|
|
347
|
-
`return` (control flow), the run's own bounds (its time, its step count, and the
|
|
348
|
-
bounds in *Traps*), and errors from steps that run on *resume* after a `wait` inside the body —
|
|
347
|
+
`return` (control flow), the run's own bounds (its time, its step count, and the compute and
|
|
348
|
+
memory bounds in *Traps*), and errors from steps that run on *resume* after a `wait` inside the body —
|
|
349
349
|
the wrapper spans one execution pass. There is no `finally`: put always-run steps after the `try`,
|
|
350
350
|
always-on-failure steps in the `catch`.
|
|
351
351
|
|
|
@@ -611,7 +611,7 @@ key preserves. So passing a possibly-null read straight through is safe. Use
|
|
|
611
611
|
`set_skip_null` is what turns a bag of optional workflow inputs into a diff-write without a guard
|
|
612
612
|
per field; naming the same field in both `set` and `set_skip_null` is an error. A call whose
|
|
613
613
|
surfaces all resolve to nothing — an all-dropped `set_skip_null`, an op object with no entries —
|
|
614
|
-
is a no-op: no records touched, no
|
|
614
|
+
is a no-op: no records touched, no table check asked. So a conditionally-built op object needs
|
|
615
615
|
no guard around the call. Why diffs and not snapshots, plus locked records and the
|
|
616
616
|
`request_locked_record_change` path: [mutations](./mutations.md#diff-before-update--send-only-what-changed).
|
|
617
617
|
|
|
@@ -791,9 +791,6 @@ The rules that are easy to get wrong because the failing code looks correct.
|
|
|
791
791
|
init/update included. Resume re-enters *after* the whole loop, so a wait inside would run
|
|
792
792
|
iteration 0 and silently skip every remaining iteration and its mutations. Move the wait out,
|
|
793
793
|
or split the loop into separately triggered runs.
|
|
794
|
-
- **Waits and `agent` steps are disallowed in `before_*` table workflows.** Those run inside the
|
|
795
|
-
mutation pipeline and must complete synchronously; an LLM loop cannot return an accept/reject
|
|
796
|
-
verdict in time.
|
|
797
794
|
- **String contexts coerce to display text.** A read inside a template or a string-typed argument
|
|
798
795
|
renders the *label*; everywhere else it is the raw key. So
|
|
799
796
|
`` `Status: ${record["fld_status"]}` `` prints "Done" while
|
|
@@ -827,11 +824,14 @@ The rules that are easy to get wrong because the failing code looks correct.
|
|
|
827
824
|
takes an array of at most 100,000. `join`, `replaceAll`, `padStart`, `padEnd`, `toString`,
|
|
828
825
|
`toJson` and a template string write at most 10,000,000 characters; `randomNumber` /
|
|
829
826
|
`randomAlphaNumeric` take a length of at most 1,000. Past one of those the call throws an
|
|
830
|
-
expression error, which `try`/`catch` catches, and so does
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
827
|
+
expression error, which `try`/`catch` catches, and so does an expression whose value would write
|
|
828
|
+
more than 67,108,864 characters of JSON, or a step whose result would take what the run keeps
|
|
829
|
+
past that. One evaluation computes for at most 6 seconds — waiting on a linked row, a member's
|
|
830
|
+
name or `current_member_in_any_group` does not count — and uses at most 256 MB of memory (512 MB
|
|
831
|
+
on a private instance with 4 GB or more). The values a run's expressions answer come to at most
|
|
832
|
+
512 MB in all, counted each time one is answered, a text at every place the value holds it.
|
|
833
|
+
Past any of these, the run fails whatever catches it. A callback nested in another runs once
|
|
834
|
+
per pair of elements.
|
|
835
835
|
|
|
836
836
|
## The bright line
|
|
837
837
|
|
|
@@ -860,8 +860,7 @@ genuinely recursive.
|
|
|
860
860
|
`set_app_workflow` with `verify_only: true` gives the verdict a save would, and writes nothing. The
|
|
861
861
|
server runs every check a save runs — the subset parse, the type pass against the alias's
|
|
862
862
|
declared inputs, tool and kwarg resolution, field, option, table and group resolution, `switch`
|
|
863
|
-
case validation, the literal `formatDate` probe, waits in loops, the `
|
|
864
|
-
table reach, the `field_key` rule and the published-API guard. **Green means the save will take
|
|
863
|
+
case validation, the literal `formatDate` probe, waits in loops, table reach, the `field_key` rule and the published-API guard. **Green means the save will take
|
|
865
864
|
it, red means real** — do not push through a red check. Lint comes back as warnings, which do not
|
|
866
865
|
fail it.
|
|
867
866
|
|
|
@@ -900,7 +899,7 @@ check's message joined with `"; "`, and one `field_errors` entry per failing che
|
|
|
900
899
|
iterations of a `while` / `do_while` / `c_for`, 10 000 steps, 50 live reads, and 20 seconds — and
|
|
901
900
|
tripping any of them appends an entry naming the bound and ending "the plan below is incomplete",
|
|
902
901
|
then stops. It also stops, with an entry saying why, where the live run would fail: at a
|
|
903
|
-
`validate` check that throws, an evaluation past the run's
|
|
902
|
+
`validate` check that throws, an evaluation past the run's compute or memory bounds (*Traps*), or a
|
|
904
903
|
return too long to write.
|
|
905
904
|
So read `evaluation_errors` before reading `planned_calls`: an entry there may be a
|
|
906
905
|
truncated plan rather than a bug in your body. A `foreach` is deliberately NOT capped at 1 000 —
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lotics/app-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.115.0",
|
|
4
4
|
"description": "The SDK a Lotics custom-code app reads and writes through \u2014 typed hooks over the host bridge, cell readers, mount() and AppRouter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|