@lotics/app-sdk 0.113.1 → 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/{chunk-CRJQYSDI.js → chunk-EWCMZCKA.js} +80 -3
- package/dist/index.js +356 -243
- package/dist/router.js +1 -1
- package/docs/data_fetching.md +1 -1
- package/docs/mutations.md +2 -3
- package/docs/workflows.md +13 -14
- package/package.json +1 -1
|
@@ -174,6 +174,42 @@ function transportErrorMessage(status, parsed) {
|
|
|
174
174
|
return status < 500 || authored ? jsonMessage : gatewayErrorMessage(status);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
// ../shared/src/schemas/read_row.ts
|
|
178
|
+
var RECORD_ID_COLUMN = "__source_record_id";
|
|
179
|
+
|
|
180
|
+
// ../shared/src/schemas/app_query_aggregate.ts
|
|
181
|
+
var APP_QUERY_BATCH_MAX = 16;
|
|
182
|
+
var AGGREGATE_COUNT_COLUMN = "__count";
|
|
183
|
+
var AGGREGATE_SUM_COLUMN = "__sum";
|
|
184
|
+
var AGGREGATE_DAY_COLUMN = "__day";
|
|
185
|
+
function dayOf(cell) {
|
|
186
|
+
return typeof cell === "string" && cell !== "" ? cell.slice(0, 10) : null;
|
|
187
|
+
}
|
|
188
|
+
function aggregateRows(rows, aggregate) {
|
|
189
|
+
const by = aggregate.by ?? [];
|
|
190
|
+
const day = aggregate.day;
|
|
191
|
+
const sum = aggregate.sum;
|
|
192
|
+
const keyed = /* @__PURE__ */ new Map();
|
|
193
|
+
for (const row of rows) {
|
|
194
|
+
const key = JSON.stringify([...by.map((column) => row[column] ?? null), ...day === void 0 ? [] : [dayOf(row[day])]]);
|
|
195
|
+
keyed.set(key, [...keyed.get(key) ?? [], row]);
|
|
196
|
+
}
|
|
197
|
+
return [...keyed.values()].map((members) => {
|
|
198
|
+
const [first2] = members;
|
|
199
|
+
const figures = members.flatMap((member) => {
|
|
200
|
+
const value = sum === void 0 ? void 0 : member[sum];
|
|
201
|
+
return typeof value === "number" ? [value] : [];
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
[RECORD_ID_COLUMN]: null,
|
|
205
|
+
...Object.fromEntries(by.map((column) => [column, first2?.[column] ?? null])),
|
|
206
|
+
...day === void 0 ? {} : { [AGGREGATE_DAY_COLUMN]: dayOf(first2?.[day]) },
|
|
207
|
+
[AGGREGATE_COUNT_COLUMN]: members.length,
|
|
208
|
+
...sum === void 0 ? {} : { [AGGREGATE_SUM_COLUMN]: figures.length === 0 ? null : figures.reduce((total, value) => total + value, 0) }
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
177
213
|
// src/upload/optimize.ts
|
|
178
214
|
var DEFAULT_IMAGE_FIDELITY = "high";
|
|
179
215
|
var IMAGE_POLICIES = {
|
|
@@ -1032,9 +1068,47 @@ async function standaloneWriteModel() {
|
|
|
1032
1068
|
const { app_id } = await boot();
|
|
1033
1069
|
return apiCall("GET", `/v1/apps/${app_id}/write_model`, void 0, { appId: app_id });
|
|
1034
1070
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1071
|
+
var READ_BATCH_WINDOW_MS = 5;
|
|
1072
|
+
var askedReads = [];
|
|
1073
|
+
function standaloneQuery(payload) {
|
|
1074
|
+
return new Promise((resolve, reject) => {
|
|
1075
|
+
askedReads = [...askedReads, { payload, resolve, reject }];
|
|
1076
|
+
if (askedReads.length === 1) setTimeout(() => void sendAskedReads(), READ_BATCH_WINDOW_MS);
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
async function sendAskedReads() {
|
|
1080
|
+
const asked = askedReads;
|
|
1081
|
+
askedReads = [];
|
|
1082
|
+
const booted = await boot().catch((error) => {
|
|
1083
|
+
asked.forEach((read) => read.reject(error));
|
|
1084
|
+
return void 0;
|
|
1085
|
+
});
|
|
1086
|
+
if (booted === void 0) return;
|
|
1087
|
+
for (let at = 0; at < asked.length; at += APP_QUERY_BATCH_MAX) {
|
|
1088
|
+
const chunk = asked.slice(at, at + APP_QUERY_BATCH_MAX);
|
|
1089
|
+
apiCall("POST", `/v1/apps/${booted.app_id}/query_batch`, { reads: chunk.map((read) => read.payload) }, { appId: booted.app_id }).then((answered) => {
|
|
1090
|
+
const results = batchResults(answered);
|
|
1091
|
+
chunk.forEach((read, k) => {
|
|
1092
|
+
const answer = results[k];
|
|
1093
|
+
if (answer === void 0) read.reject(new Error(`The server answered ${results.length} of ${chunk.length} reads.`));
|
|
1094
|
+
else if ("result" in answer) read.resolve(answer.result);
|
|
1095
|
+
else read.reject(new ApiRefusal(transportErrorMessage(answer.error.status, answer.error)));
|
|
1096
|
+
});
|
|
1097
|
+
}).catch((error) => chunk.forEach((read) => read.reject(error)));
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
function batchResults(answered) {
|
|
1101
|
+
if (answered === null || typeof answered !== "object" || !("results" in answered) || !Array.isArray(answered.results)) {
|
|
1102
|
+
throw new Error("The server's batch answer carried no results.");
|
|
1103
|
+
}
|
|
1104
|
+
return answered.results.map((answer, at) => {
|
|
1105
|
+
if (answer !== null && typeof answer === "object" && "result" in answer) return { result: answer.result };
|
|
1106
|
+
const error = answer !== null && typeof answer === "object" && "error" in answer ? answer.error : void 0;
|
|
1107
|
+
if (error !== null && typeof error === "object" && "status" in error && typeof error.status === "number" && "message" in error && typeof error.message === "string") {
|
|
1108
|
+
return { error: { status: error.status, message: error.message, code: "code" in error ? error.code : void 0 } };
|
|
1109
|
+
}
|
|
1110
|
+
throw new Error(`The server's batch answer ${at} is neither a result nor a refusal.`);
|
|
1111
|
+
});
|
|
1038
1112
|
}
|
|
1039
1113
|
async function standaloneExport(payload) {
|
|
1040
1114
|
const { app_id } = await boot();
|
|
@@ -1119,6 +1193,9 @@ async function standaloneUpload(file, fidelity) {
|
|
|
1119
1193
|
|
|
1120
1194
|
export {
|
|
1121
1195
|
__export,
|
|
1196
|
+
AGGREGATE_COUNT_COLUMN,
|
|
1197
|
+
AGGREGATE_SUM_COLUMN,
|
|
1198
|
+
aggregateRows,
|
|
1122
1199
|
DEFAULT_IMAGE_FIDELITY,
|
|
1123
1200
|
recordingsVersion,
|
|
1124
1201
|
subscribeRecordings,
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AGGREGATE_COUNT_COLUMN,
|
|
3
|
+
AGGREGATE_SUM_COLUMN,
|
|
2
4
|
DEFAULT_IMAGE_FIDELITY,
|
|
3
5
|
__export,
|
|
6
|
+
aggregateRows,
|
|
4
7
|
anyRecordingLive,
|
|
5
8
|
applyPatch,
|
|
6
9
|
decodeAll,
|
|
@@ -22,7 +25,7 @@ import {
|
|
|
22
25
|
subscribeRecordings,
|
|
23
26
|
subscribeUrlParams,
|
|
24
27
|
urlParam
|
|
25
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-EWCMZCKA.js";
|
|
26
29
|
|
|
27
30
|
// src/mount.tsx
|
|
28
31
|
import { createRoot } from "react-dom/client";
|
|
@@ -23968,6 +23971,180 @@ function everyOptionMarked(nameOf2) {
|
|
|
23968
23971
|
};
|
|
23969
23972
|
}
|
|
23970
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
|
+
|
|
23971
24148
|
// ../shared/src/diacritics.ts
|
|
23972
24149
|
var COMBINING_MARKS = /[̀-ͯ]/g;
|
|
23973
24150
|
function foldDiacritics(value) {
|
|
@@ -24828,7 +25005,7 @@ var tableFieldTypeConfigSchema = zod_default.discriminatedUnion("type", [
|
|
|
24828
25005
|
zod_default.object({
|
|
24829
25006
|
type: zod_default.literal("number"),
|
|
24830
25007
|
format: numberFormatSchema.default("number"),
|
|
24831
|
-
currency:
|
|
25008
|
+
currency: currencyCodeSchema.optional(),
|
|
24832
25009
|
unit: zod_default.string().optional().describe(UNIT_DESCRIPTION),
|
|
24833
25010
|
unit_field: zod_default.string().optional().describe(UNIT_FIELD_DESCRIPTION),
|
|
24834
25011
|
currency_field: zod_default.string().optional().describe(CURRENCY_FIELD_DESCRIPTION),
|
|
@@ -24897,7 +25074,7 @@ var tableFieldTypeConfigSchema = zod_default.discriminatedUnion("type", [
|
|
|
24897
25074
|
}),
|
|
24898
25075
|
zod_default.object({
|
|
24899
25076
|
type: zod_default.literal("formula"),
|
|
24900
|
-
formula: formulaInputSchema
|
|
25077
|
+
formula: formulaInputSchema.extend({ currency: currencyCodeSchema.optional() })
|
|
24901
25078
|
}).superRefine((field, ctx) => figureIssue(["formula"])(field.formula, ctx)),
|
|
24902
25079
|
// `button` is deliberately absent: button fields are legacy — this union
|
|
24903
25080
|
// shapes CREATE input only, and new buttons can no longer be created
|
|
@@ -24969,173 +25146,6 @@ function projectCell(field, value) {
|
|
|
24969
25146
|
}
|
|
24970
25147
|
}
|
|
24971
25148
|
|
|
24972
|
-
// ../shared/src/walk_workflow_expression.ts
|
|
24973
|
-
var LINKED_CALL = "linked";
|
|
24974
|
-
var GRAMMAR_NAME_SHAPE = `${LINKED_CALL}(record["fld_link"])[0] fetches one linked row`;
|
|
24975
|
-
var RUNTIME_KEYS = /* @__PURE__ */ new Set([
|
|
24976
|
-
"timezone",
|
|
24977
|
-
"workflow_id",
|
|
24978
|
-
"execution_id",
|
|
24979
|
-
"workspace_id",
|
|
24980
|
-
"organization_id",
|
|
24981
|
-
"now",
|
|
24982
|
-
"change_origin",
|
|
24983
|
-
"execution_principal",
|
|
24984
|
-
"triggered_by_member_id"
|
|
24985
|
-
]);
|
|
24986
|
-
var CURRENT_MEMBER_IN_ANY_GROUP = "current_member_in_any_group";
|
|
24987
|
-
var PARKED_RUNTIME_KEYS = /* @__PURE__ */ new Map([
|
|
24988
|
-
[
|
|
24989
|
-
"execution_principal",
|
|
24990
|
-
`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.`
|
|
24991
|
-
]
|
|
24992
|
-
]);
|
|
24993
|
-
var READABLE_RUNTIME_KEYS = Array.from(RUNTIME_KEYS).filter(
|
|
24994
|
-
(key) => !PARKED_RUNTIME_KEYS.has(key)
|
|
24995
|
-
);
|
|
24996
|
-
var INLINE_HELPERS = [
|
|
24997
|
-
"formatCurrency",
|
|
24998
|
-
"randomNumber",
|
|
24999
|
-
"randomAlphaNumeric",
|
|
25000
|
-
"sample",
|
|
25001
|
-
// Actor authorization predicate — resolves the triggering member's groups
|
|
25002
|
-
// server-side (bound in the backend helper registry, not a pure shared
|
|
25003
|
-
// helper).
|
|
25004
|
-
CURRENT_MEMBER_IN_ANY_GROUP
|
|
25005
|
-
];
|
|
25006
|
-
var HELPERS = /* @__PURE__ */ new Set([
|
|
25007
|
-
...Object.keys(expression_array_exports),
|
|
25008
|
-
...Object.keys(expression_string_exports),
|
|
25009
|
-
...Object.keys(expression_math_exports),
|
|
25010
|
-
...Object.keys(expression_object_exports),
|
|
25011
|
-
...Object.keys(expression_type_exports),
|
|
25012
|
-
...Object.keys(getDateExpressionFunctions()),
|
|
25013
|
-
...INLINE_HELPERS
|
|
25014
|
-
]);
|
|
25015
|
-
function isHelper(name) {
|
|
25016
|
-
return HELPERS.has(name);
|
|
25017
|
-
}
|
|
25018
|
-
var HELPER_MENU = [
|
|
25019
|
-
`text \u2014 ${Object.keys(expression_string_exports).sort().join(", ")}`,
|
|
25020
|
-
`number \u2014 ${Object.keys(expression_math_exports).sort().join(", ")}`,
|
|
25021
|
-
`array \u2014 ${Object.keys(expression_array_exports).sort().join(", ")}`,
|
|
25022
|
-
`object \u2014 ${Object.keys(expression_object_exports).sort().join(", ")}`,
|
|
25023
|
-
`type/null \u2014 ${Object.keys(expression_type_exports).sort().join(", ")}`,
|
|
25024
|
-
`date \u2014 ${Object.keys(getDateExpressionFunctions()).sort().join(", ")}`,
|
|
25025
|
-
`other \u2014 ${[...INLINE_HELPERS].sort().join(", ")}`
|
|
25026
|
-
].join("; ");
|
|
25027
|
-
var AWAIT_IF_FORM = "Write the if form: let x = null; if (<condition>) { x = await tool({...}); }";
|
|
25028
|
-
var AWAIT_IN_OPTIONAL_CHAIN = `An await after \`?.\` runs only when the value before it is not null. ${AWAIT_IF_FORM}`;
|
|
25029
|
-
var CALLBACK_HELPERS = /* @__PURE__ */ new Set([
|
|
25030
|
-
"filter",
|
|
25031
|
-
"find",
|
|
25032
|
-
"pluck",
|
|
25033
|
-
"sortBy",
|
|
25034
|
-
"sumBy",
|
|
25035
|
-
"meanBy",
|
|
25036
|
-
"minBy",
|
|
25037
|
-
"maxBy",
|
|
25038
|
-
"countBy",
|
|
25039
|
-
"groupBy",
|
|
25040
|
-
"uniqueBy",
|
|
25041
|
-
"some",
|
|
25042
|
-
"every",
|
|
25043
|
-
"differenceBy",
|
|
25044
|
-
"intersectionBy",
|
|
25045
|
-
"reduce"
|
|
25046
|
-
]);
|
|
25047
|
-
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.`;
|
|
25048
|
-
var HELPER_ALIASES = /* @__PURE__ */ new Map([
|
|
25049
|
-
["String", "toString"],
|
|
25050
|
-
["Number", "toNumber"],
|
|
25051
|
-
["parseFloat", "toNumber"],
|
|
25052
|
-
// faithful: toNumber IS parseFloat semantics
|
|
25053
|
-
["toUpperCase", "upper"],
|
|
25054
|
-
["toLowerCase", "lower"],
|
|
25055
|
-
["map", "pluck"],
|
|
25056
|
-
// arr.map(x => …) ≡ pluck(arr, x => …)
|
|
25057
|
-
["toFixed", "formatNumber"]
|
|
25058
|
-
// x.toFixed(2) ≡ formatNumber(x, 2)
|
|
25059
|
-
]);
|
|
25060
|
-
var REJECTED_HELPERS = /* @__PURE__ */ new Map([
|
|
25061
|
-
[
|
|
25062
|
-
"parseInt",
|
|
25063
|
-
{
|
|
25064
|
-
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.",
|
|
25065
|
-
construct: "parse_int"
|
|
25066
|
-
}
|
|
25067
|
-
]
|
|
25068
|
-
]);
|
|
25069
|
-
var NAMESPACE_HELPERS = /* @__PURE__ */ new Map([
|
|
25070
|
-
["JSON", /* @__PURE__ */ new Map([["parse", "parseJson"], ["stringify", "toJson"]])],
|
|
25071
|
-
["Array", /* @__PURE__ */ new Map([["isArray", "isArray"]])],
|
|
25072
|
-
["Object", /* @__PURE__ */ new Map([["keys", "keys"], ["values", "values"], ["entries", "entries"]])],
|
|
25073
|
-
["Number", /* @__PURE__ */ new Map([["parseFloat", "toNumber"]])]
|
|
25074
|
-
]);
|
|
25075
|
-
var METHOD_FORM_NAMES = /* @__PURE__ */ new Set([
|
|
25076
|
-
// String.prototype
|
|
25077
|
-
"trim",
|
|
25078
|
-
"toUpperCase",
|
|
25079
|
-
"toLowerCase",
|
|
25080
|
-
"replace",
|
|
25081
|
-
"replaceAll",
|
|
25082
|
-
"split",
|
|
25083
|
-
"substring",
|
|
25084
|
-
"padStart",
|
|
25085
|
-
"padEnd",
|
|
25086
|
-
"startsWith",
|
|
25087
|
-
"endsWith",
|
|
25088
|
-
// String.prototype + Array.prototype
|
|
25089
|
-
"includes",
|
|
25090
|
-
"slice",
|
|
25091
|
-
"at",
|
|
25092
|
-
"concat",
|
|
25093
|
-
// Array.prototype
|
|
25094
|
-
"join",
|
|
25095
|
-
"reverse",
|
|
25096
|
-
"map",
|
|
25097
|
-
"filter",
|
|
25098
|
-
"find",
|
|
25099
|
-
"some",
|
|
25100
|
-
"every",
|
|
25101
|
-
"reduce",
|
|
25102
|
-
// Number.prototype / Object.prototype
|
|
25103
|
-
"toFixed",
|
|
25104
|
-
"toString"
|
|
25105
|
-
]);
|
|
25106
|
-
var METHOD_FORM_MENU = [...METHOD_FORM_NAMES].sort().join(", ");
|
|
25107
|
-
var PROPERTY_HELPERS = /* @__PURE__ */ new Map([["length", "length"]]);
|
|
25108
|
-
var HELPER_SPELLINGS = (() => {
|
|
25109
|
-
const out = /* @__PURE__ */ new Map();
|
|
25110
|
-
const add = (helper, spelling) => {
|
|
25111
|
-
if (isHelper(helper) && !out.has(helper)) out.set(helper, spelling);
|
|
25112
|
-
};
|
|
25113
|
-
for (const name of METHOD_FORM_NAMES) add(HELPER_ALIASES.get(name) ?? name, { kind: "method", name });
|
|
25114
|
-
for (const [name, helper] of PROPERTY_HELPERS) add(helper, { kind: "property", name });
|
|
25115
|
-
for (const [namespace, members2] of NAMESPACE_HELPERS) {
|
|
25116
|
-
for (const [member, helper] of members2) add(helper, { kind: "namespace", namespace, member });
|
|
25117
|
-
}
|
|
25118
|
-
for (const fn of Object.keys(expression_math_exports)) {
|
|
25119
|
-
if (fn !== "random" && typeof Reflect.get(Math, fn) === "function") {
|
|
25120
|
-
add(fn, { kind: "namespace", namespace: "Math", member: fn });
|
|
25121
|
-
}
|
|
25122
|
-
}
|
|
25123
|
-
return out;
|
|
25124
|
-
})();
|
|
25125
|
-
var RESERVED_CALLABLE_NAMES = /* @__PURE__ */ new Set([
|
|
25126
|
-
...REJECTED_HELPERS.keys(),
|
|
25127
|
-
...NAMESPACE_HELPERS.keys(),
|
|
25128
|
-
"Math",
|
|
25129
|
-
"Boolean"
|
|
25130
|
-
]);
|
|
25131
|
-
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.`;
|
|
25132
|
-
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"]).`;
|
|
25133
|
-
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"].`;
|
|
25134
|
-
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.`;
|
|
25135
|
-
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].`;
|
|
25136
|
-
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"]).`;
|
|
25137
|
-
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.`;
|
|
25138
|
-
|
|
25139
25149
|
// ../shared/src/schemas/workflow_expressions.ts
|
|
25140
25150
|
var runtimeKeySchema = zod_default.enum([
|
|
25141
25151
|
"timezone",
|
|
@@ -25145,15 +25155,6 @@ var runtimeKeySchema = zod_default.enum([
|
|
|
25145
25155
|
"organization_id",
|
|
25146
25156
|
"now",
|
|
25147
25157
|
"change_origin",
|
|
25148
|
-
// PARKED — accepted in stored ASTs, rejected for new saves at parse (see
|
|
25149
|
-
// `PARKED_RUNTIME_KEYS` in walk_workflow_expression.ts). It is the OWNER's
|
|
25150
|
-
// principal (a workflow runs under owner authority), so it can't answer the
|
|
25151
|
-
// caller-authorization question anyone reads it for; the generated `.d.ts`
|
|
25152
|
-
// omits it and the docs point at `current_member_in_any_group` /
|
|
25153
|
-
// `runtime.triggered_by_member_id` instead. The entry stays so any stored
|
|
25154
|
-
// AST keeps validating and executing — drop it only once a prod probe shows
|
|
25155
|
-
// zero references (`workflows.steps_v2::text LIKE '%execution_principal%'`).
|
|
25156
|
-
"execution_principal",
|
|
25157
25158
|
"triggered_by_member_id"
|
|
25158
25159
|
]);
|
|
25159
25160
|
var nestedExpressionSchema = zod_default.lazy(
|
|
@@ -25326,6 +25327,154 @@ var templatePartSchema = zod_default.lazy(
|
|
|
25326
25327
|
])
|
|
25327
25328
|
);
|
|
25328
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
|
+
|
|
25329
25478
|
// ../shared/src/chat_models.ts
|
|
25330
25479
|
var MODEL_TIERS = ["haiku", "sonnet", "opus"];
|
|
25331
25480
|
var TIER_MODELS = {
|
|
@@ -26973,36 +27122,46 @@ var WorkflowEvalError = class extends Error {
|
|
|
26973
27122
|
detail;
|
|
26974
27123
|
};
|
|
26975
27124
|
var WorkflowTimeBudgetError = class extends Error {
|
|
26976
|
-
|
|
26977
|
-
|
|
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}`}.`);
|
|
26978
27128
|
this.name = "WorkflowTimeBudgetError";
|
|
26979
27129
|
}
|
|
26980
27130
|
};
|
|
26981
27131
|
var WorkflowExpressionBudgetError = class extends Error {
|
|
26982
|
-
constructor(
|
|
26983
|
-
super(
|
|
26984
|
-
|
|
26985
|
-
);
|
|
26986
|
-
this.exceeded = exceeded;
|
|
27132
|
+
constructor(budget) {
|
|
27133
|
+
super(budgetMessage(budget));
|
|
27134
|
+
this.budget = budget;
|
|
26987
27135
|
this.name = "WorkflowExpressionBudgetError";
|
|
26988
27136
|
}
|
|
26989
|
-
|
|
27137
|
+
budget;
|
|
26990
27138
|
};
|
|
26991
|
-
|
|
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
|
+
}
|
|
26992
27149
|
var SLOT_BYTES = 8;
|
|
26993
27150
|
var CHAR_BYTES = 2;
|
|
27151
|
+
var OBJECT_BYTES = 56;
|
|
26994
27152
|
var EVALUATION_SLICE_MS = 20;
|
|
26995
27153
|
var turn = {
|
|
26996
27154
|
slice_started_ms: performance.now(),
|
|
26997
27155
|
pause: null
|
|
26998
27156
|
};
|
|
26999
|
-
function startRunBudget(subject, budgetMs) {
|
|
27000
|
-
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 };
|
|
27001
27159
|
}
|
|
27002
27160
|
function runClock(run2) {
|
|
27003
27161
|
if (Date.now() > run2.deadline_ms) {
|
|
27004
27162
|
throw new WorkflowTimeBudgetError(run2.subject, run2.budget_ms);
|
|
27005
27163
|
}
|
|
27164
|
+
if (run2.host === "isolated") return null;
|
|
27006
27165
|
if (turn.pause === null && performance.now() - turn.slice_started_ms >= EVALUATION_SLICE_MS) {
|
|
27007
27166
|
turn.pause = new Promise((resolve) => setTimeout(resolve, 0)).then(() => {
|
|
27008
27167
|
turn.pause = null;
|
|
@@ -27012,15 +27171,16 @@ function runClock(run2) {
|
|
|
27012
27171
|
return turn.pause;
|
|
27013
27172
|
}
|
|
27014
27173
|
function chargeBuilt(run2, bytes) {
|
|
27174
|
+
if (run2.host === "isolated") return;
|
|
27015
27175
|
run2.built_bytes += bytes;
|
|
27016
27176
|
if (run2.built_bytes > MAX_RUN_BUILT_BYTES) {
|
|
27017
|
-
throw new WorkflowExpressionBudgetError("bytes");
|
|
27177
|
+
throw new WorkflowExpressionBudgetError({ exceeded: "bytes" });
|
|
27018
27178
|
}
|
|
27019
27179
|
}
|
|
27020
27180
|
function builtSize(value) {
|
|
27021
27181
|
if (typeof value === "string") return value.length * CHAR_BYTES;
|
|
27022
|
-
if (Array.isArray(value)) return value.length * SLOT_BYTES;
|
|
27023
|
-
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;
|
|
27024
27184
|
return 0;
|
|
27025
27185
|
}
|
|
27026
27186
|
var PARSED_NODE_BYTES = 40;
|
|
@@ -27050,13 +27210,13 @@ function callbackWorkers() {
|
|
|
27050
27210
|
};
|
|
27051
27211
|
}
|
|
27052
27212
|
function evaluateWorkflowExpression(expr2, ctx) {
|
|
27053
|
-
const budget = {
|
|
27213
|
+
const budget = { workers: callbackWorkers(), settled: false };
|
|
27054
27214
|
return evaluateExpression(expr2, { ...ctx, evaluation_budget: budget }).finally(() => {
|
|
27055
27215
|
budget.settled = true;
|
|
27056
27216
|
});
|
|
27057
27217
|
}
|
|
27058
27218
|
async function evaluateExpression(expr2, ctx) {
|
|
27059
|
-
for (let pause =
|
|
27219
|
+
for (let pause = checkClock(ctx); pause !== null; pause = checkClock(ctx)) await pause;
|
|
27060
27220
|
switch (expr2.kind) {
|
|
27061
27221
|
case "lit":
|
|
27062
27222
|
return expr2.value;
|
|
@@ -27093,15 +27253,6 @@ async function evaluateExpression(expr2, ctx) {
|
|
|
27093
27253
|
return assertNever(expr2);
|
|
27094
27254
|
}
|
|
27095
27255
|
}
|
|
27096
|
-
function spendNode(ctx) {
|
|
27097
|
-
const budget = ctx.evaluation_budget;
|
|
27098
|
-
assertUnsettled(budget);
|
|
27099
|
-
budget.nodes += 1;
|
|
27100
|
-
if (budget.nodes > MAX_EVALUATION_NODES) {
|
|
27101
|
-
throw new WorkflowExpressionBudgetError("nodes");
|
|
27102
|
-
}
|
|
27103
|
-
return runClock(ctx.run_budget);
|
|
27104
|
-
}
|
|
27105
27256
|
function checkClock(ctx) {
|
|
27106
27257
|
assertUnsettled(ctx.evaluation_budget);
|
|
27107
27258
|
return runClock(ctx.run_budget);
|
|
@@ -28093,9 +28244,6 @@ function clientRuntime(input) {
|
|
|
28093
28244
|
},
|
|
28094
28245
|
get change_origin() {
|
|
28095
28246
|
return unread("change_origin");
|
|
28096
|
-
},
|
|
28097
|
-
get execution_principal() {
|
|
28098
|
-
return unread("execution_principal");
|
|
28099
28247
|
}
|
|
28100
28248
|
};
|
|
28101
28249
|
}
|
|
@@ -28128,7 +28276,7 @@ async function predictWorkflowEffects(input) {
|
|
|
28128
28276
|
trigger_table: null,
|
|
28129
28277
|
member_lookup: async () => unread("member display"),
|
|
28130
28278
|
lexical_bindings: [{}],
|
|
28131
|
-
run_budget: startRunBudget("Write prediction", PREDICTION_BUDGET_MS)
|
|
28279
|
+
run_budget: startRunBudget("Write prediction", PREDICTION_BUDGET_MS, "write")
|
|
28132
28280
|
};
|
|
28133
28281
|
const walk = { ctx, effects: [], written, input, unsure: false, aligned: true };
|
|
28134
28282
|
try {
|
|
@@ -29066,41 +29214,6 @@ function useAgentRuns(sessionId, opts) {
|
|
|
29066
29214
|
// src/queries.ts
|
|
29067
29215
|
import { useCallback as useCallback5, useMemo as useMemo3, useState as useState3 } from "react";
|
|
29068
29216
|
|
|
29069
|
-
// ../shared/src/schemas/read_row.ts
|
|
29070
|
-
var RECORD_ID_COLUMN = "__source_record_id";
|
|
29071
|
-
|
|
29072
|
-
// ../shared/src/schemas/app_query_aggregate.ts
|
|
29073
|
-
var AGGREGATE_COUNT_COLUMN = "__count";
|
|
29074
|
-
var AGGREGATE_SUM_COLUMN = "__sum";
|
|
29075
|
-
var AGGREGATE_DAY_COLUMN = "__day";
|
|
29076
|
-
function dayOf(cell) {
|
|
29077
|
-
return typeof cell === "string" && cell !== "" ? cell.slice(0, 10) : null;
|
|
29078
|
-
}
|
|
29079
|
-
function aggregateRows(rows, aggregate) {
|
|
29080
|
-
const by = aggregate.by ?? [];
|
|
29081
|
-
const day = aggregate.day;
|
|
29082
|
-
const sum2 = aggregate.sum;
|
|
29083
|
-
const keyed = /* @__PURE__ */ new Map();
|
|
29084
|
-
for (const row2 of rows) {
|
|
29085
|
-
const key = JSON.stringify([...by.map((column) => row2[column] ?? null), ...day === void 0 ? [] : [dayOf(row2[day])]]);
|
|
29086
|
-
keyed.set(key, [...keyed.get(key) ?? [], row2]);
|
|
29087
|
-
}
|
|
29088
|
-
return [...keyed.values()].map((members2) => {
|
|
29089
|
-
const [first2] = members2;
|
|
29090
|
-
const figures = members2.flatMap((member) => {
|
|
29091
|
-
const value = sum2 === void 0 ? void 0 : member[sum2];
|
|
29092
|
-
return typeof value === "number" ? [value] : [];
|
|
29093
|
-
});
|
|
29094
|
-
return {
|
|
29095
|
-
[RECORD_ID_COLUMN]: null,
|
|
29096
|
-
...Object.fromEntries(by.map((column) => [column, first2?.[column] ?? null])),
|
|
29097
|
-
...day === void 0 ? {} : { [AGGREGATE_DAY_COLUMN]: dayOf(first2?.[day]) },
|
|
29098
|
-
[AGGREGATE_COUNT_COLUMN]: members2.length,
|
|
29099
|
-
...sum2 === void 0 ? {} : { [AGGREGATE_SUM_COLUMN]: figures.length === 0 ? null : figures.reduce((total, value) => total + value, 0) }
|
|
29100
|
-
};
|
|
29101
|
-
});
|
|
29102
|
-
}
|
|
29103
|
-
|
|
29104
29217
|
// src/row.ts
|
|
29105
29218
|
var PENDING = "__pending";
|
|
29106
29219
|
function opt(v) {
|
package/dist/router.js
CHANGED
package/docs/data_fetching.md
CHANGED
|
@@ -143,7 +143,7 @@ write it as `{ node_type: "condition", type: "record_id", operator, value }`.
|
|
|
143
143
|
inline `{ status: "open" }` each render is the same key; you never need to memoize params.
|
|
144
144
|
- **The cache survives unmount/remount**, and **arrival is a refresh event**: returning to a screen
|
|
145
145
|
renders the cached rows instantly *and* re-reads them in the background, so a list reflects what
|
|
146
|
-
another screen changed while you were away. Identical concurrent reads share one request.
|
|
146
|
+
another screen changed while you were away. Identical concurrent reads share one request, and reads asked within a few milliseconds of each other go to the server together, each answered or refused on its own.
|
|
147
147
|
Freshness comes from four places — arrival, window focus / tab return / network reconnect
|
|
148
148
|
(`revalidateOnFocus`, default on; a read asked within the last few seconds is not asked again),
|
|
149
149
|
a **realtime push**, and **this app's own successful write**, which re-reads the mounted queries
|
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": {
|