@lotics/app-sdk 0.120.3 → 0.122.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/AGENTS.md +1 -1
- package/dist/activity.d.ts +15 -2
- package/dist/{chunk-ABDSJ25E.js → chunk-MK4DBY6R.js} +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +293 -112
- package/dist/router.js +1 -1
- package/dist/rpc.d.ts +1 -1
- package/dist/shared_types.d.ts +2 -1
- package/dist/store.d.ts +2 -0
- package/dist/viewer.d.ts +2 -0
- package/docs/members_and_options.md +21 -5
- package/docs/runtime.md +1 -0
- package/docs/security.md +1 -0
- package/docs/workflows.md +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -20,7 +20,7 @@ This file is the index. The **exact type** of anything is its shipped declaratio
|
|
|
20
20
|
| [docs/workflows.md](./docs/workflows.md) | **The workflow-BODY reference** — the JS subset a body may use, opaque `fld_*`/`opt_*` keys, every step form, the accepted sugar, helpers, record-write surfaces, the traps and the verify loop. |
|
|
21
21
|
| [docs/recipes.md](./docs/recipes.md) | Task-shaped how-tos — returning a generated file, returning structured data, parameterized lookups, composable optional filters, cell decoding, testing an AI action without spending credits. |
|
|
22
22
|
| [docs/files.md](./docs/files.md) | Files end to end — `useFileUpload` and its `fidelity` and a photo's `place`, `renameFile` (a new file over the same bytes), `useAttachments`/`useAttachmentPiles`, `readFiles` and presigned URLs (a bearer credential — never logged or persisted), workflow-generated files, naming a zip's entries, the delivery bounds. |
|
|
23
|
-
| [docs/members_and_options.md](./docs/members_and_options.md) | People, select options and comments — `useMembers`, `useFieldOptions`, `useViewer`, `useWorkspaceTimezone`/`useWorkspaceCurrency`, `useAppContext`, the workspace's zone at the root, `useComments`, a record's activity (`useRecordActivity`). |
|
|
23
|
+
| [docs/members_and_options.md](./docs/members_and_options.md) | People, select options and comments — `useMembers`, `useFieldOptions`, `useViewer`, `useWorkspaceTimezone`/`useWorkspaceCurrency`, `useAppContext`, the workspace's zone at the root, `useComments`, a record's activity and the undo of one change (`useRecordActivity`, `undoRecordChange`). |
|
|
24
24
|
| [docs/navigation_and_state.md](./docs/navigation_and_state.md) | `AppRouter` (embedded/standalone URL model, the screens and `drawers` it reports to the host), `useUrlState` + `urlParam` codecs, `useRecents`, `useFolderPick`. |
|
|
25
25
|
| [docs/ai.md](./docs/ai.md) | `useAgentRun` (structured vs free-text, streaming parts, the agent's ask-back), `askAi`, `useAiContext`, and what the member's own chat agent can do with the app while it is open. |
|
|
26
26
|
| [docs/security.md](./docs/security.md) | **Read before shipping** — the owner-principal model, `is_current_member` scoping, write attribution, group gates, public-app bounds, why a per-input bound is a tenancy floor rather than an authorization check. |
|
package/dist/activity.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ActivityEntry } from "./shared_types.js";
|
|
1
|
+
import type { ActivityEntry, ActivityUndoResult } from "./shared_types.js";
|
|
2
2
|
export interface RecordActivityState {
|
|
3
3
|
/** Newest first. */
|
|
4
4
|
entries: ActivityEntry[];
|
|
@@ -6,7 +6,7 @@ export interface RecordActivityState {
|
|
|
6
6
|
next: string | undefined;
|
|
7
7
|
loading: boolean;
|
|
8
8
|
error: string | null;
|
|
9
|
-
/** A signed-in member;
|
|
9
|
+
/** A signed-in member of the app's organization; for anyone else nothing is read and `entries` stays empty. */
|
|
10
10
|
available: boolean;
|
|
11
11
|
refetch: () => void;
|
|
12
12
|
}
|
|
@@ -25,3 +25,16 @@ export interface RecordActivityOptions {
|
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
export declare function useRecordActivity(entity: string, record_id: string, options?: RecordActivityOptions): RecordActivityState;
|
|
28
|
+
/**
|
|
29
|
+
* Put one change entry of the record's activity back, as the viewer's own save: each field the entry changed returns
|
|
30
|
+
* to what it held before it where the record still holds what the entry wrote. The answer names the fields put back
|
|
31
|
+
* (`reverted`) and those left as they stand (`kept`), each with its `reason` — `changed_since`, or `refused` where the
|
|
32
|
+
* table refused the write (a lock, a check) — and arrives once the record's reads are read anew. Refused for an entry
|
|
33
|
+
* whose `undoable` is false: the record's making, an undo, an entry already put back, and a viewer who may not write
|
|
34
|
+
* the record.
|
|
35
|
+
*
|
|
36
|
+
* ```tsx
|
|
37
|
+
* const { reverted, kept } = await undoRecordChange("job", recordId, entry.id);
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function undoRecordChange(entity: string, record_id: string, entry_id: string): Promise<ActivityUndoResult>;
|
|
@@ -1656,6 +1656,7 @@ function rpcStandalone(op, payload) {
|
|
|
1656
1656
|
case "comments.counts":
|
|
1657
1657
|
return rejectCommentsStandalone();
|
|
1658
1658
|
case "activity.list":
|
|
1659
|
+
case "activity.undo":
|
|
1659
1660
|
return Promise.reject(new Error("A record's activity is available only in embedded apps \u2014 a signed-in member is required."));
|
|
1660
1661
|
case "recording.start":
|
|
1661
1662
|
case "recording.stop":
|
package/dist/index.d.ts
CHANGED
|
@@ -11,9 +11,9 @@ export type { ExportedFile } from "./shared_types.js";
|
|
|
11
11
|
export type { AttachedFile, AttachmentPiles, AttachmentPilesOptions, AttachmentsOptions, AttachmentsState } from "./attachments.js";
|
|
12
12
|
export { useComments, useCommentCounts } from "./comments.js";
|
|
13
13
|
export type { AppComment, AppCommentAuthor, AppCommentFile, CommentsState, UseCommentsArgs, CommentCountsState, UseCommentCountsArgs, } from "./comments.js";
|
|
14
|
-
export { useRecordActivity } from "./activity.js";
|
|
14
|
+
export { undoRecordChange, useRecordActivity } from "./activity.js";
|
|
15
15
|
export type { RecordActivityOptions, RecordActivityState } from "./activity.js";
|
|
16
|
-
export type { ActivityActor, ActivityActorKind, ActivityChange, ActivityComment, ActivityEntry, ActivityValue, RecordActivityPage, } from "./shared_types.js";
|
|
16
|
+
export type { ActivityActor, ActivityActorKind, ActivityChange, ActivityComment, ActivityEntry, ActivityUndoResult, ActivityValue, RecordActivityPage, UndoKeptReason, } from "./shared_types.js";
|
|
17
17
|
export { useAppContext, useViewer, useWorkspaceCurrency, useWorkspaceTimezone } from "./viewer.js";
|
|
18
18
|
export type { AppAbsence } from "./shared_types.js";
|
|
19
19
|
export { useRecording, useRecordings } from "./recording.js";
|
package/dist/index.js
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
sumDecimal,
|
|
41
41
|
tiersAscend,
|
|
42
42
|
urlParam
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-MK4DBY6R.js";
|
|
44
44
|
|
|
45
45
|
// src/mount.tsx
|
|
46
46
|
import { createRoot } from "react-dom/client";
|
|
@@ -611,6 +611,14 @@ function readNow(aliases) {
|
|
|
611
611
|
function refetchReads(aliases) {
|
|
612
612
|
for (const slot of readNow(aliases)) void ask(slot, true);
|
|
613
613
|
}
|
|
614
|
+
async function readsAnswered(aliases) {
|
|
615
|
+
await Promise.all(
|
|
616
|
+
readNow(aliases).map(async (slot) => {
|
|
617
|
+
await ask(slot, true);
|
|
618
|
+
while (slot.inflight !== null) await slot.inflight;
|
|
619
|
+
})
|
|
620
|
+
);
|
|
621
|
+
}
|
|
614
622
|
function askInOnePaint(read2) {
|
|
615
623
|
if (read2.length === 0) return;
|
|
616
624
|
const paint = { waiting: new Set(read2), held: [], drawn: false, cap: setTimeout(() => draw(paint), PAINT_CAP_MS) };
|
|
@@ -1840,13 +1848,16 @@ async function callEach(arr, cb) {
|
|
|
1840
1848
|
let next = 0;
|
|
1841
1849
|
let failed = false;
|
|
1842
1850
|
const work = async () => {
|
|
1843
|
-
while (
|
|
1851
|
+
while (next < arr.length && !failed) {
|
|
1844
1852
|
const i = next;
|
|
1845
1853
|
next += 1;
|
|
1846
|
-
|
|
1854
|
+
try {
|
|
1855
|
+
const result = cb(arr[i], i);
|
|
1856
|
+
results[i] = result instanceof Promise ? await result : result;
|
|
1857
|
+
} catch (error51) {
|
|
1847
1858
|
failed = true;
|
|
1848
1859
|
throw error51;
|
|
1849
|
-
}
|
|
1860
|
+
}
|
|
1850
1861
|
}
|
|
1851
1862
|
};
|
|
1852
1863
|
const extra = arr.length > 1 ? cb.workers?.claim(arr.length - 1) ?? 0 : 0;
|
|
@@ -2039,7 +2050,8 @@ function find(arr, fieldOrCb, value) {
|
|
|
2039
2050
|
}
|
|
2040
2051
|
async function findByCallback(arr, cb) {
|
|
2041
2052
|
for (let i = 0; i < arr.length; i++) {
|
|
2042
|
-
|
|
2053
|
+
const hit = cb(arr[i], i);
|
|
2054
|
+
if (hit instanceof Promise ? await hit : hit) return arr[i];
|
|
2043
2055
|
}
|
|
2044
2056
|
return void 0;
|
|
2045
2057
|
}
|
|
@@ -2275,7 +2287,8 @@ function some(arr, fieldOrCb, value) {
|
|
|
2275
2287
|
assertCallbackInput("some", arr);
|
|
2276
2288
|
return (async () => {
|
|
2277
2289
|
for (let i = 0; i < arr.length; i++) {
|
|
2278
|
-
|
|
2290
|
+
const hit = fieldOrCb(arr[i], i);
|
|
2291
|
+
if (hit instanceof Promise ? await hit : hit) return true;
|
|
2279
2292
|
}
|
|
2280
2293
|
return false;
|
|
2281
2294
|
})();
|
|
@@ -2295,7 +2308,8 @@ function every(arr, fieldOrCb, value) {
|
|
|
2295
2308
|
assertCallbackInput("every", arr);
|
|
2296
2309
|
return (async () => {
|
|
2297
2310
|
for (let i = 0; i < arr.length; i++) {
|
|
2298
|
-
|
|
2311
|
+
const hit = fieldOrCb(arr[i], i);
|
|
2312
|
+
if (!(hit instanceof Promise ? await hit : hit)) return false;
|
|
2299
2313
|
}
|
|
2300
2314
|
return true;
|
|
2301
2315
|
})();
|
|
@@ -2390,7 +2404,8 @@ async function reduce(arr, callback, initial) {
|
|
|
2390
2404
|
assertCallbackInput("reduce", arr);
|
|
2391
2405
|
let acc = initial;
|
|
2392
2406
|
for (let i = 0; i < arr.length; i++) {
|
|
2393
|
-
|
|
2407
|
+
const next = callback(acc, arr[i], i);
|
|
2408
|
+
acc = next instanceof Promise ? await next : next;
|
|
2394
2409
|
}
|
|
2395
2410
|
return acc;
|
|
2396
2411
|
}
|
|
@@ -6113,11 +6128,9 @@ var HELPER_COST = {
|
|
|
6113
6128
|
typeOf: "reads"
|
|
6114
6129
|
};
|
|
6115
6130
|
function helperCost(name) {
|
|
6116
|
-
return
|
|
6117
|
-
}
|
|
6118
|
-
function isHelperName(name) {
|
|
6119
|
-
return Object.prototype.hasOwnProperty.call(HELPER_COST, name);
|
|
6131
|
+
return HELPER_COST_BY_NAME.get(name) ?? "builds";
|
|
6120
6132
|
}
|
|
6133
|
+
var HELPER_COST_BY_NAME = new Map(Object.entries(HELPER_COST));
|
|
6121
6134
|
function createTimezoneAwareFunctions(defaultTimezone) {
|
|
6122
6135
|
return {
|
|
6123
6136
|
// Number functions
|
|
@@ -26395,6 +26408,9 @@ var appQueryDeclarationSchema = zod_default.object({
|
|
|
26395
26408
|
),
|
|
26396
26409
|
templates: zod_default.record(zod_default.string().regex(APP_ALIAS_REGEX), appExportTemplateSchema).optional().describe(
|
|
26397
26410
|
"Document templates an export of this query's rows may fill, by the name the export asks for. An export names one of these or none; a template it does not declare is refused."
|
|
26411
|
+
),
|
|
26412
|
+
screen_only: zod_default.literal(true).optional().describe(
|
|
26413
|
+
"The query feeds one control of the app's screen and answers no question a person asks. It runs like any other; a catalog of what an agent may ask leaves it out."
|
|
26398
26414
|
)
|
|
26399
26415
|
});
|
|
26400
26416
|
var appAgentWritesSchema = zod_default.object({
|
|
@@ -26736,6 +26752,15 @@ var TOOL_INPUT_SEMANTICS = {
|
|
|
26736
26752
|
// same position `query_records.field_keys` holds.
|
|
26737
26753
|
return_fields: { kind: "field_key_refs", table_from: "table_id" }
|
|
26738
26754
|
},
|
|
26755
|
+
import_rows: {
|
|
26756
|
+
table_id: { kind: "table_ref", rows: "change" },
|
|
26757
|
+
match: { kind: "field_key_refs", table_from: "table_id" },
|
|
26758
|
+
// Each row's `values` are keyed by field, a link cell possibly `{ lookup }`; the tool resolves both at execute.
|
|
26759
|
+
rows: { kind: "dynamic" },
|
|
26760
|
+
create: { kind: "scalar" },
|
|
26761
|
+
// `fields` is keyed by field; the tool resolves it at execute.
|
|
26762
|
+
conflicts: { kind: "dynamic" }
|
|
26763
|
+
},
|
|
26739
26764
|
delete_records: {
|
|
26740
26765
|
// THE ONE CALL THAT TAKES ROWS. Every column of the row goes with it, so
|
|
26741
26766
|
// nothing field-keyed can carry the fact and it rides the table position —
|
|
@@ -26771,6 +26796,22 @@ var TOOL_INPUT_SEMANTICS = {
|
|
|
26771
26796
|
changes: { kind: "dynamic" },
|
|
26772
26797
|
reason: { kind: "text" }
|
|
26773
26798
|
},
|
|
26799
|
+
// The record names its own table; an entry's id and a cursor are the trail's.
|
|
26800
|
+
get_record_activity: {
|
|
26801
|
+
record_id: { kind: "record_ref" },
|
|
26802
|
+
after: { kind: "scalar" }
|
|
26803
|
+
},
|
|
26804
|
+
undo_record_change: {
|
|
26805
|
+
record_id: { kind: "record_ref" },
|
|
26806
|
+
entry_id: { kind: "scalar" }
|
|
26807
|
+
},
|
|
26808
|
+
// `table_id` names the table whose rows the undo changes, moves to the trash and brings back; a run's id is the
|
|
26809
|
+
// trail's.
|
|
26810
|
+
undo_record_changes: {
|
|
26811
|
+
table_id: { kind: "table_ref", rows: "remove" },
|
|
26812
|
+
record_ids: { kind: "record_refs" },
|
|
26813
|
+
workflow_execution_id: { kind: "scalar" }
|
|
26814
|
+
},
|
|
26774
26815
|
// ─── Query surfaces ────────────────────────────────────────────────────
|
|
26775
26816
|
query_records: {
|
|
26776
26817
|
table_id: { kind: "table_ref", rows: "none" },
|
|
@@ -27340,7 +27381,11 @@ function resolveLambdaItemFrame(frames, name) {
|
|
|
27340
27381
|
}
|
|
27341
27382
|
return void 0;
|
|
27342
27383
|
}
|
|
27384
|
+
function lambdaItemAnchor(ctx, name) {
|
|
27385
|
+
return resolveLambdaItemFrame(ctx.lambda_items, name)?.anchor ?? null;
|
|
27386
|
+
}
|
|
27343
27387
|
function readRecordAnchor(source, path, ctx) {
|
|
27388
|
+
if (source.from === "lambda_param") return lambdaItemAnchor(ctx, source.name);
|
|
27344
27389
|
return anchorForRead(source, path, ctx, /* @__PURE__ */ new Set());
|
|
27345
27390
|
}
|
|
27346
27391
|
function anchorForRead(source, path, ctx, seen) {
|
|
@@ -27350,7 +27395,7 @@ function anchorForRead(source, path, ctx, seen) {
|
|
|
27350
27395
|
case "foreach_item":
|
|
27351
27396
|
return resolveNamedForeachFrame(ctx.foreach_items, source.name)?.anchor ?? null;
|
|
27352
27397
|
case "lambda_param":
|
|
27353
|
-
return
|
|
27398
|
+
return lambdaItemAnchor(ctx, source.name);
|
|
27354
27399
|
case "trigger_record":
|
|
27355
27400
|
return rootAnchor(ctx.trigger_record);
|
|
27356
27401
|
case "trigger_record_prev":
|
|
@@ -27661,7 +27706,7 @@ function assertCheap(budget, ...values2) {
|
|
|
27661
27706
|
function charge(ctx, bytes) {
|
|
27662
27707
|
const budget = ctx.evaluation_budget;
|
|
27663
27708
|
assertUnsettled(budget);
|
|
27664
|
-
if (Date.now() > ctx.run_budget.deadline_ms) throw new WorkflowTimeBudgetError(ctx.run_budget.subject, ctx.run_budget.budget_ms);
|
|
27709
|
+
if (readsClock(ctx) && Date.now() > ctx.run_budget.deadline_ms) throw new WorkflowTimeBudgetError(ctx.run_budget.subject, ctx.run_budget.budget_ms);
|
|
27665
27710
|
if (budget.bounded && budget.built + bytes > MAX_IN_PLACE_BYTES) throw new ExpressionNotCheapError();
|
|
27666
27711
|
budget.built += bytes;
|
|
27667
27712
|
chargeBuilt(ctx.run_budget, bytes);
|
|
@@ -27680,8 +27725,8 @@ function callbackWorkers() {
|
|
|
27680
27725
|
};
|
|
27681
27726
|
}
|
|
27682
27727
|
function evaluateWorkflowExpression(expr2, ctx, options = {}) {
|
|
27683
|
-
const budget = { workers: callbackWorkers(), bounded: options.bounded === true, built: 0, settled: false };
|
|
27684
|
-
return
|
|
27728
|
+
const budget = { workers: callbackWorkers(), bounded: options.bounded === true, built: 0, clock_checks: 0, helpers: /* @__PURE__ */ new Map(), settled: false };
|
|
27729
|
+
return evaluateRoot(expr2, { ...ctx, evaluation_budget: budget, anchor_context: evalAnchorContext(ctx) }).finally(() => {
|
|
27685
27730
|
budget.settled = true;
|
|
27686
27731
|
}).then((answer) => {
|
|
27687
27732
|
if (budget.bounded && expandedSize(answer, MAX_IN_PLACE_BYTES) > MAX_IN_PLACE_BYTES) throw new ExpressionNotCheapError();
|
|
@@ -27691,8 +27736,19 @@ function evaluateWorkflowExpression(expr2, ctx, options = {}) {
|
|
|
27691
27736
|
throw error51;
|
|
27692
27737
|
});
|
|
27693
27738
|
}
|
|
27694
|
-
async function
|
|
27739
|
+
async function evaluateRoot(expr2, ctx) {
|
|
27695
27740
|
for (let pause = checkClock(ctx); pause !== null; pause = checkClock(ctx)) await pause;
|
|
27741
|
+
return evaluateExpression(expr2, ctx);
|
|
27742
|
+
}
|
|
27743
|
+
function failBeside(error51, started) {
|
|
27744
|
+
void Promise.allSettled(started);
|
|
27745
|
+
throw error51;
|
|
27746
|
+
}
|
|
27747
|
+
async function startBeside(expr2, ctx) {
|
|
27748
|
+
return evaluateExpression(expr2, ctx);
|
|
27749
|
+
}
|
|
27750
|
+
function evaluateExpression(expr2, ctx) {
|
|
27751
|
+
assertUnsettled(ctx.evaluation_budget);
|
|
27696
27752
|
switch (expr2.kind) {
|
|
27697
27753
|
case "lit":
|
|
27698
27754
|
return expr2.value;
|
|
@@ -27705,31 +27761,75 @@ async function evaluateExpression(expr2, ctx) {
|
|
|
27705
27761
|
case "unary":
|
|
27706
27762
|
return evaluateUnary(expr2.op, expr2.arg, ctx);
|
|
27707
27763
|
case "ternary": {
|
|
27708
|
-
const cond =
|
|
27709
|
-
return cond ?
|
|
27764
|
+
const cond = evaluateExpression(expr2.cond, ctx);
|
|
27765
|
+
return cond instanceof Promise ? branchAfter(cond, expr2.then, expr2.else, ctx) : evaluateExpression(cond ? expr2.then : expr2.else, ctx);
|
|
27710
27766
|
}
|
|
27711
27767
|
case "template":
|
|
27712
27768
|
return evaluateTemplate(expr2.parts, ctx);
|
|
27713
27769
|
case "lambda":
|
|
27714
27770
|
return evaluateLambda(expr2.params, expr2.body, ctx, []);
|
|
27715
|
-
case "array_literal":
|
|
27716
|
-
|
|
27717
|
-
|
|
27718
|
-
return
|
|
27719
|
-
}
|
|
27720
|
-
case "object_literal": {
|
|
27721
|
-
const out = {};
|
|
27722
|
-
for (const entry of expr2.entries) defineOwn(out, entry.key, await evaluateExpression(entry.value, ctx));
|
|
27723
|
-
charge(ctx, builtSize(out));
|
|
27724
|
-
return out;
|
|
27725
|
-
}
|
|
27771
|
+
case "array_literal":
|
|
27772
|
+
return evaluateArrayLiteral(expr2.elements, ctx);
|
|
27773
|
+
case "object_literal":
|
|
27774
|
+
return fillObjectLiteral({}, expr2.entries, 0, ctx);
|
|
27726
27775
|
default:
|
|
27727
27776
|
return assertNever(expr2);
|
|
27728
27777
|
}
|
|
27729
27778
|
}
|
|
27779
|
+
async function branchAfter(cond, then, otherwise, ctx) {
|
|
27780
|
+
return evaluateExpression(await cond ? then : otherwise, ctx);
|
|
27781
|
+
}
|
|
27782
|
+
function evaluateArrayLiteral(elements, ctx) {
|
|
27783
|
+
const values2 = [];
|
|
27784
|
+
let pending = false;
|
|
27785
|
+
try {
|
|
27786
|
+
for (const element of elements) {
|
|
27787
|
+
const value = evaluateExpression(element, ctx);
|
|
27788
|
+
if (value instanceof Promise) pending = true;
|
|
27789
|
+
values2.push(value);
|
|
27790
|
+
}
|
|
27791
|
+
} catch (error51) {
|
|
27792
|
+
return failBeside(error51, values2);
|
|
27793
|
+
}
|
|
27794
|
+
return pending ? chargeAfter(values2, ctx) : chargedArray(values2, ctx);
|
|
27795
|
+
}
|
|
27796
|
+
async function chargeAfter(values2, ctx) {
|
|
27797
|
+
return chargedArray(await Promise.all(values2), ctx);
|
|
27798
|
+
}
|
|
27799
|
+
function chargedArray(elements, ctx) {
|
|
27800
|
+
charge(ctx, builtSize(elements));
|
|
27801
|
+
return elements;
|
|
27802
|
+
}
|
|
27803
|
+
function fillObjectLiteral(out, entries3, from, ctx) {
|
|
27804
|
+
for (let at2 = from; at2 < entries3.length; at2++) {
|
|
27805
|
+
const entry = entries3[at2];
|
|
27806
|
+
const value = evaluateExpression(entry.value, ctx);
|
|
27807
|
+
if (value instanceof Promise) return fillObjectAfter(out, entries3, at2, value, ctx);
|
|
27808
|
+
defineOwn(out, entry.key, value);
|
|
27809
|
+
}
|
|
27810
|
+
charge(ctx, builtSize(out));
|
|
27811
|
+
return out;
|
|
27812
|
+
}
|
|
27813
|
+
async function fillObjectAfter(out, entries3, at2, value, ctx) {
|
|
27814
|
+
defineOwn(out, entries3[at2].key, await value);
|
|
27815
|
+
return fillObjectLiteral(out, entries3, at2 + 1, ctx);
|
|
27816
|
+
}
|
|
27730
27817
|
function checkClock(ctx) {
|
|
27731
27818
|
assertUnsettled(ctx.evaluation_budget);
|
|
27732
|
-
return runClock(ctx.run_budget);
|
|
27819
|
+
return readsClock(ctx) ? runClock(ctx.run_budget) : null;
|
|
27820
|
+
}
|
|
27821
|
+
var ISOLATED_CLOCK_STRIDE = 16;
|
|
27822
|
+
function readsClock(ctx) {
|
|
27823
|
+
if (ctx.run_budget.host === "shared") return true;
|
|
27824
|
+
const budget = ctx.evaluation_budget;
|
|
27825
|
+
budget.clock_checks += 1;
|
|
27826
|
+
if (budget.clock_checks % ISOLATED_CLOCK_STRIDE !== 1) return false;
|
|
27827
|
+
if (budget.clock_checks === 1) {
|
|
27828
|
+
queueMicrotask(() => {
|
|
27829
|
+
budget.clock_checks = 0;
|
|
27830
|
+
});
|
|
27831
|
+
}
|
|
27832
|
+
return true;
|
|
27733
27833
|
}
|
|
27734
27834
|
function assertUnsettled(budget) {
|
|
27735
27835
|
if (budget.settled) {
|
|
@@ -27739,21 +27839,19 @@ function assertUnsettled(budget) {
|
|
|
27739
27839
|
}
|
|
27740
27840
|
}
|
|
27741
27841
|
function evaluateLambda(params, body, ctx, items) {
|
|
27742
|
-
const
|
|
27743
|
-
|
|
27744
|
-
|
|
27745
|
-
|
|
27746
|
-
|
|
27747
|
-
|
|
27748
|
-
|
|
27749
|
-
|
|
27750
|
-
|
|
27751
|
-
|
|
27752
|
-
|
|
27753
|
-
|
|
27754
|
-
|
|
27755
|
-
};
|
|
27756
|
-
return evaluateExpression(body, innerCtx);
|
|
27842
|
+
const lambdaItems = items.length === 0 ? ctx.lambda_items : [...ctx.lambda_items ?? [], ...items];
|
|
27843
|
+
const bodyCtx = {
|
|
27844
|
+
...ctx,
|
|
27845
|
+
lambda_items: lambdaItems,
|
|
27846
|
+
anchor_context: { ...ctx.anchor_context, lambda_items: lambdaItems ?? [] }
|
|
27847
|
+
};
|
|
27848
|
+
const stack = ctx.lambda_params ?? [];
|
|
27849
|
+
const call = (...args) => {
|
|
27850
|
+
const pause = checkClock(bodyCtx);
|
|
27851
|
+
if (pause !== null) return pause.then(() => call(...args));
|
|
27852
|
+
const frame2 = /* @__PURE__ */ Object.create(null);
|
|
27853
|
+
for (let i = 0; i < params.length; i++) frame2[params[i]] = args[i];
|
|
27854
|
+
return evaluateExpression(body, { ...bodyCtx, lambda_params: [...stack, frame2] });
|
|
27757
27855
|
};
|
|
27758
27856
|
return Object.assign(call, { workers: ctx.evaluation_budget.workers });
|
|
27759
27857
|
}
|
|
@@ -27773,9 +27871,6 @@ function evalAnchorContext(ctx) {
|
|
|
27773
27871
|
trigger_changes: ctx.trigger_table
|
|
27774
27872
|
};
|
|
27775
27873
|
}
|
|
27776
|
-
function resolveReadAnchor(source, path, ctx) {
|
|
27777
|
-
return readRecordAnchor(source, path, evalAnchorContext(ctx));
|
|
27778
|
-
}
|
|
27779
27874
|
function resolveReadRoot(source, ctx) {
|
|
27780
27875
|
switch (source.from) {
|
|
27781
27876
|
case "trigger":
|
|
@@ -27885,29 +27980,37 @@ function resolveForeachFrame(ctx, name, at2) {
|
|
|
27885
27980
|
{ kind: "read", at: at2, extra: { name } }
|
|
27886
27981
|
);
|
|
27887
27982
|
}
|
|
27888
|
-
|
|
27889
|
-
|
|
27890
|
-
|
|
27891
|
-
|
|
27892
|
-
|
|
27893
|
-
|
|
27894
|
-
|
|
27983
|
+
function evaluateRead(source, path, coerce, ctx) {
|
|
27984
|
+
const root = source.from === "value" ? evaluateExpression(source.expr, ctx) : resolveReadRoot(source, ctx);
|
|
27985
|
+
return root instanceof Promise ? readAfter(root, source, path, coerce, ctx) : walkPath(path, coerce, ctx, readRecordAnchor(source, path, ctx.anchor_context), 0, root, null, void 0, void 0);
|
|
27986
|
+
}
|
|
27987
|
+
async function readAfter(root, source, path, coerce, ctx) {
|
|
27988
|
+
const value = await root;
|
|
27989
|
+
return walkPath(path, coerce, ctx, readRecordAnchor(source, path, ctx.anchor_context), 0, value, null, void 0, void 0);
|
|
27990
|
+
}
|
|
27991
|
+
function walkPath(path, coerce, ctx, anchor, from, start, startTable, startField, startTerminal) {
|
|
27992
|
+
let current = start;
|
|
27993
|
+
let table = startTable;
|
|
27994
|
+
let prevField = startField;
|
|
27995
|
+
let terminalField = startTerminal;
|
|
27996
|
+
for (let i = from; i < path.length; i++) {
|
|
27895
27997
|
const segment = path[i];
|
|
27896
27998
|
const isLast = i === path.length - 1;
|
|
27897
|
-
if (anchor && i === anchor.envelopeLen)
|
|
27999
|
+
if (anchor && i === anchor.envelopeLen) table ??= anchor.table;
|
|
27898
28000
|
const carried = carriedTable(current, ctx);
|
|
27899
|
-
if (carried?.at === "self")
|
|
27900
|
-
const
|
|
28001
|
+
if (carried?.at === "self") table = carried.table;
|
|
28002
|
+
const field = (segment.at === "field" || segment.at === "key") && table ? findField(table, segment.key) : void 0;
|
|
27901
28003
|
if (isLast) {
|
|
27902
|
-
terminalField =
|
|
28004
|
+
terminalField = field ?? (segment.at === "key" && (segment.key === "next_value" || segment.key === "prev_value") ? prevField : void 0);
|
|
27903
28005
|
}
|
|
27904
|
-
|
|
27905
|
-
|
|
27906
|
-
|
|
27907
|
-
prevFieldForSegment = fieldForSegment;
|
|
27908
|
-
if (current === void 0 || current === null) {
|
|
27909
|
-
return current;
|
|
28006
|
+
const walked = walkSegment(current, segment, ctx);
|
|
28007
|
+
if (walked instanceof Promise) {
|
|
28008
|
+
return walkPending({ path, coerce, ctx, anchor, table, carried, field, terminal_field: terminalField }, i, walked);
|
|
27910
28009
|
}
|
|
28010
|
+
table = tableAfter(segment, table, carried, ctx);
|
|
28011
|
+
prevField = field;
|
|
28012
|
+
current = walked;
|
|
28013
|
+
if (current === void 0 || current === null) return current;
|
|
27911
28014
|
}
|
|
27912
28015
|
if (terminalField && isScalarWrappedField(terminalField)) {
|
|
27913
28016
|
current = unwrapScalarField(current);
|
|
@@ -27917,6 +28020,17 @@ async function evaluateRead(source, path, coerce, ctx) {
|
|
|
27917
28020
|
}
|
|
27918
28021
|
return current;
|
|
27919
28022
|
}
|
|
28023
|
+
async function walkPending(read2, at2, walked) {
|
|
28024
|
+
const { path, coerce, ctx, anchor, carried, field, terminal_field: terminalField } = read2;
|
|
28025
|
+
const current = await walked;
|
|
28026
|
+
if (current === void 0 || current === null) return current;
|
|
28027
|
+
const table = tableAfter(path[at2], read2.table, carried, ctx);
|
|
28028
|
+
return walkPath(path, coerce, ctx, anchor, at2 + 1, current, table, field, terminalField);
|
|
28029
|
+
}
|
|
28030
|
+
function tableAfter(segment, table, carried, ctx) {
|
|
28031
|
+
const advanced = advanceTable(segment, table, ctx);
|
|
28032
|
+
return carried?.at === "data" && isKeyNamed(segment, "data") ? carried.table : advanced;
|
|
28033
|
+
}
|
|
27920
28034
|
function isScalarWrappedField(field) {
|
|
27921
28035
|
if (field.type === "formula") return field.formula.options !== void 0;
|
|
27922
28036
|
return (field.type === "select" || field.type === "select_member") && field.multi !== true;
|
|
@@ -28022,7 +28136,7 @@ function advanceTable(segment, currentTable, ctx) {
|
|
|
28022
28136
|
function findField(table, fieldKey) {
|
|
28023
28137
|
return table.fields.find((f) => f.key === fieldKey);
|
|
28024
28138
|
}
|
|
28025
|
-
|
|
28139
|
+
function walkSegment(value, segment, ctx) {
|
|
28026
28140
|
switch (segment.at) {
|
|
28027
28141
|
case "key":
|
|
28028
28142
|
case "field": {
|
|
@@ -28043,12 +28157,14 @@ async function walkSegment(value, segment, ctx) {
|
|
|
28043
28157
|
case "index": {
|
|
28044
28158
|
if (value === null || value === void 0) return void 0;
|
|
28045
28159
|
if (Array.isArray(value) || typeof value === "string") {
|
|
28046
|
-
|
|
28047
|
-
|
|
28160
|
+
if (typeof segment.index === "number") return value[segment.index];
|
|
28161
|
+
const idx = resolveIndexExpression(segment.index, ctx, "array index");
|
|
28162
|
+
return idx instanceof Promise ? idx.then((at2) => value[at2]) : value[idx];
|
|
28048
28163
|
}
|
|
28049
28164
|
if (typeof value === "object") {
|
|
28050
|
-
|
|
28051
|
-
|
|
28165
|
+
if (typeof segment.index === "number") return ownMember(value, String(segment.index));
|
|
28166
|
+
const key = resolveObjectKeyExpression(segment.index, ctx);
|
|
28167
|
+
return key instanceof Promise ? key.then((named) => ownMember(value, named)) : ownMember(value, key);
|
|
28052
28168
|
}
|
|
28053
28169
|
throw new WorkflowEvalError(
|
|
28054
28170
|
`Cannot index ${typeof value} \u2014 only an array or an object can be read by index.`,
|
|
@@ -28059,8 +28175,11 @@ async function walkSegment(value, segment, ctx) {
|
|
|
28059
28175
|
return walkLinked(value, segment, ctx);
|
|
28060
28176
|
}
|
|
28061
28177
|
}
|
|
28062
|
-
|
|
28063
|
-
const value =
|
|
28178
|
+
function resolveObjectKeyExpression(expr2, ctx) {
|
|
28179
|
+
const value = evaluateExpression(expr2, ctx);
|
|
28180
|
+
return value instanceof Promise ? value.then(objectKey) : objectKey(value);
|
|
28181
|
+
}
|
|
28182
|
+
function objectKey(value) {
|
|
28064
28183
|
if (typeof value === "string") return value;
|
|
28065
28184
|
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
28066
28185
|
throw new WorkflowEvalError(
|
|
@@ -28072,8 +28191,11 @@ async function resolveObjectKeyExpression(expr2, ctx) {
|
|
|
28072
28191
|
);
|
|
28073
28192
|
}
|
|
28074
28193
|
var VALUE_PREVIEW_LENGTH = 120;
|
|
28075
|
-
|
|
28076
|
-
const value =
|
|
28194
|
+
function resolveIndexExpression(expr2, ctx, context) {
|
|
28195
|
+
const value = evaluateExpression(expr2, ctx);
|
|
28196
|
+
return value instanceof Promise ? value.then((settled) => arrayIndex(settled, context)) : arrayIndex(value, context);
|
|
28197
|
+
}
|
|
28198
|
+
function arrayIndex(value, context) {
|
|
28077
28199
|
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
28078
28200
|
throw new WorkflowEvalError(
|
|
28079
28201
|
`Computed ${context} must evaluate to a non-negative integer, got ${describeValue(value)}`,
|
|
@@ -28145,46 +28267,77 @@ async function fetchRecordsBounded(ids, reading, ctx) {
|
|
|
28145
28267
|
}
|
|
28146
28268
|
return results;
|
|
28147
28269
|
}
|
|
28148
|
-
|
|
28270
|
+
function evaluateCall(fn, args, ctx) {
|
|
28271
|
+
const pause = checkClock(ctx);
|
|
28272
|
+
if (pause !== null) return pause.then(() => evaluateCall(fn, args, ctx));
|
|
28273
|
+
const helper = resolveHelper(fn, ctx);
|
|
28274
|
+
const values2 = [];
|
|
28275
|
+
let pending = false;
|
|
28276
|
+
try {
|
|
28277
|
+
for (const arg of args) {
|
|
28278
|
+
const value = arg.kind === "lambda" ? evaluateLambda(arg.params, arg.body, ctx, callbackItemFrames(fn, args, arg.params, ctx.anchor_context)) : evaluateExpression(arg, ctx);
|
|
28279
|
+
if (value instanceof Promise) pending = true;
|
|
28280
|
+
values2.push(value);
|
|
28281
|
+
}
|
|
28282
|
+
} catch (error51) {
|
|
28283
|
+
return failBeside(error51, values2);
|
|
28284
|
+
}
|
|
28285
|
+
return pending ? callAfter(helper, values2, ctx) : callHelper(helper, values2, ctx);
|
|
28286
|
+
}
|
|
28287
|
+
function resolveHelper(fn, ctx) {
|
|
28288
|
+
const known = ctx.evaluation_budget.helpers.get(fn);
|
|
28289
|
+
if (known !== void 0) return known;
|
|
28149
28290
|
if (!Object.prototype.hasOwnProperty.call(ctx.helpers, fn)) {
|
|
28150
28291
|
throw new WorkflowEvalError(`Unknown helper function "${fn}"`, {
|
|
28151
28292
|
kind: "call",
|
|
28152
28293
|
extra: { fn }
|
|
28153
28294
|
});
|
|
28154
28295
|
}
|
|
28155
|
-
const
|
|
28156
|
-
|
|
28157
|
-
|
|
28158
|
-
|
|
28159
|
-
|
|
28160
|
-
|
|
28161
|
-
);
|
|
28296
|
+
const resolved = { call: ctx.helpers[fn], cost: helperCost(fn) };
|
|
28297
|
+
ctx.evaluation_budget.helpers.set(fn, resolved);
|
|
28298
|
+
return resolved;
|
|
28299
|
+
}
|
|
28300
|
+
async function callAfter(helper, pending, ctx) {
|
|
28301
|
+
const values2 = await Promise.all(pending);
|
|
28162
28302
|
for (let pause = checkClock(ctx); pause !== null; pause = checkClock(ctx)) await pause;
|
|
28163
|
-
|
|
28164
|
-
|
|
28165
|
-
|
|
28303
|
+
return callHelper(helper, values2, ctx);
|
|
28304
|
+
}
|
|
28305
|
+
function callHelper({ call, cost }, values2, ctx) {
|
|
28306
|
+
if (cost === "parses") charge(ctx, parsedSize(values2[0]));
|
|
28307
|
+
const answer = Reflect.apply(call, void 0, values2);
|
|
28308
|
+
return answer instanceof Promise ? chargeAnswerAfter(cost, answer, values2, ctx) : chargeAnswer(cost, answer, values2, ctx);
|
|
28309
|
+
}
|
|
28310
|
+
async function chargeAnswerAfter(cost, answer, values2, ctx) {
|
|
28311
|
+
return chargeAnswer(cost, await answer, values2, ctx);
|
|
28312
|
+
}
|
|
28313
|
+
function chargeAnswer(cost, answer, values2, ctx) {
|
|
28166
28314
|
if (cost !== "builds" && cost !== "nested") return answer;
|
|
28167
|
-
const handedBack =
|
|
28168
|
-
if (handedBack !== -1) return
|
|
28169
|
-
charge(ctx, builtSize(answer) + (cost === "nested" ? builtSize(
|
|
28315
|
+
const handedBack = values2.indexOf(answer);
|
|
28316
|
+
if (handedBack !== -1) return values2[handedBack];
|
|
28317
|
+
charge(ctx, builtSize(answer) + (cost === "nested" ? builtSize(values2[0]) : 0));
|
|
28170
28318
|
return answer;
|
|
28171
28319
|
}
|
|
28172
|
-
|
|
28173
|
-
if (op === "and") {
|
|
28174
|
-
const
|
|
28175
|
-
|
|
28176
|
-
|
|
28177
|
-
|
|
28178
|
-
|
|
28179
|
-
|
|
28180
|
-
|
|
28181
|
-
|
|
28182
|
-
|
|
28183
|
-
|
|
28184
|
-
|
|
28185
|
-
|
|
28186
|
-
|
|
28320
|
+
function evaluateBinary(op, leftExpr, rightExpr, ctx) {
|
|
28321
|
+
if (op === "and" || op === "or") {
|
|
28322
|
+
const first2 = evaluateExpression(leftExpr, ctx);
|
|
28323
|
+
return first2 instanceof Promise ? shortCircuitAfter(first2, op, rightExpr, ctx) : shortCircuit(op, first2, rightExpr, ctx);
|
|
28324
|
+
}
|
|
28325
|
+
const left = evaluateExpression(leftExpr, ctx);
|
|
28326
|
+
const right = left instanceof Promise ? startBeside(rightExpr, ctx) : evaluateExpression(rightExpr, ctx);
|
|
28327
|
+
return left instanceof Promise || right instanceof Promise ? binaryAfter(op, left, right, ctx) : applyBinary(op, left, right, ctx);
|
|
28328
|
+
}
|
|
28329
|
+
function shortCircuit(op, left, rightExpr, ctx) {
|
|
28330
|
+
return (op === "and" ? !left : left) ? left : evaluateExpression(rightExpr, ctx);
|
|
28331
|
+
}
|
|
28332
|
+
async function shortCircuitAfter(left, op, rightExpr, ctx) {
|
|
28333
|
+
return shortCircuit(op, await left, rightExpr, ctx);
|
|
28334
|
+
}
|
|
28335
|
+
async function binaryAfter(op, left, right, ctx) {
|
|
28336
|
+
const [leftValue, rightValue] = await Promise.all([left, right]);
|
|
28187
28337
|
for (let pause = checkClock(ctx); pause !== null; pause = checkClock(ctx)) await pause;
|
|
28338
|
+
return applyBinary(op, leftValue, rightValue, ctx);
|
|
28339
|
+
}
|
|
28340
|
+
function applyBinary(op, left, right, ctx) {
|
|
28188
28341
|
switch (op) {
|
|
28189
28342
|
case "eq":
|
|
28190
28343
|
case "neq":
|
|
@@ -28291,8 +28444,14 @@ function describeValue(value) {
|
|
|
28291
28444
|
}
|
|
28292
28445
|
return value.length > 0 && length2 <= VALUE_PREVIEW_LENGTH ? `array ${value.join(",")}` : "array";
|
|
28293
28446
|
}
|
|
28294
|
-
|
|
28295
|
-
const value =
|
|
28447
|
+
function evaluateUnary(op, argExpr, ctx) {
|
|
28448
|
+
const value = evaluateExpression(argExpr, ctx);
|
|
28449
|
+
return value instanceof Promise ? unaryAfter(op, value) : applyUnary(op, value);
|
|
28450
|
+
}
|
|
28451
|
+
async function unaryAfter(op, value) {
|
|
28452
|
+
return applyUnary(op, await value);
|
|
28453
|
+
}
|
|
28454
|
+
function applyUnary(op, value) {
|
|
28296
28455
|
switch (op) {
|
|
28297
28456
|
case "not":
|
|
28298
28457
|
return !value;
|
|
@@ -28306,11 +28465,26 @@ async function evaluateUnary(op, argExpr, ctx) {
|
|
|
28306
28465
|
return -value;
|
|
28307
28466
|
}
|
|
28308
28467
|
}
|
|
28309
|
-
|
|
28310
|
-
const evaluated2 =
|
|
28311
|
-
|
|
28312
|
-
|
|
28468
|
+
function evaluateTemplate(parts, ctx) {
|
|
28469
|
+
const evaluated2 = [];
|
|
28470
|
+
let pending = false;
|
|
28471
|
+
try {
|
|
28472
|
+
for (const part of parts) {
|
|
28473
|
+
const value = part.t === "text" ? part.s : evaluateExpression(part.e, ctx);
|
|
28474
|
+
if (value instanceof Promise) pending = true;
|
|
28475
|
+
evaluated2.push(value);
|
|
28476
|
+
}
|
|
28477
|
+
} catch (error51) {
|
|
28478
|
+
return failBeside(error51, evaluated2);
|
|
28479
|
+
}
|
|
28480
|
+
return pending ? templateAfter(evaluated2, ctx) : writeTemplate(evaluated2, ctx);
|
|
28481
|
+
}
|
|
28482
|
+
async function templateAfter(pending, ctx) {
|
|
28483
|
+
const evaluated2 = await Promise.all(pending);
|
|
28313
28484
|
for (let pause = checkClock(ctx); pause !== null; pause = checkClock(ctx)) await pause;
|
|
28485
|
+
return writeTemplate(evaluated2, ctx);
|
|
28486
|
+
}
|
|
28487
|
+
function writeTemplate(evaluated2, ctx) {
|
|
28314
28488
|
assertCheap(ctx.evaluation_budget, ...evaluated2);
|
|
28315
28489
|
assertTextWithin("template", ...evaluated2);
|
|
28316
28490
|
const text2 = evaluated2.map(stringifyForTemplate).join("");
|
|
@@ -29032,6 +29206,7 @@ function useAppContext() {
|
|
|
29032
29206
|
const { data, error: error51, refetch } = useKey(APP_CONTEXT_KEY, readContext, { focus: false, arrival: false });
|
|
29033
29207
|
return {
|
|
29034
29208
|
memberId: data?.member_id ?? null,
|
|
29209
|
+
insider: data !== void 0 && data.member_id !== null && data.group_ids !== void 0,
|
|
29035
29210
|
groupIds: data?.group_ids ?? null,
|
|
29036
29211
|
absences: data?.absences ?? null,
|
|
29037
29212
|
openableApps: data?.openable_apps ?? null,
|
|
@@ -32717,8 +32892,7 @@ function oneQueryAlias(entity) {
|
|
|
32717
32892
|
var NO_ENTRIES = [];
|
|
32718
32893
|
function useRecordActivity(entity, record_id, options = {}) {
|
|
32719
32894
|
const { after } = options;
|
|
32720
|
-
const {
|
|
32721
|
-
const available = memberId !== null;
|
|
32895
|
+
const { insider: available, resolved } = useAppContext();
|
|
32722
32896
|
const page = useKey(
|
|
32723
32897
|
available ? JSON.stringify(["activity", entity, record_id, after ?? null]) : null,
|
|
32724
32898
|
() => rpc("activity.list", { entity, record_id, ...after === void 0 ? {} : { after } }),
|
|
@@ -32734,6 +32908,12 @@ function useRecordActivity(entity, record_id, options = {}) {
|
|
|
32734
32908
|
refetch: page.refetch
|
|
32735
32909
|
};
|
|
32736
32910
|
}
|
|
32911
|
+
async function undoRecordChange(entity, record_id, entry_id) {
|
|
32912
|
+
const result = await rpc("activity.undo", { entity, record_id, entry_id });
|
|
32913
|
+
refetchReads([listQueryAlias(entity)]);
|
|
32914
|
+
await readsAnswered([oneQueryAlias(entity)]);
|
|
32915
|
+
return result;
|
|
32916
|
+
}
|
|
32737
32917
|
|
|
32738
32918
|
// src/recording.ts
|
|
32739
32919
|
import { useCallback as useCallback7, useMemo as useMemo4, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
@@ -33077,6 +33257,7 @@ export {
|
|
|
33077
33257
|
requestGeofencedLocation,
|
|
33078
33258
|
row,
|
|
33079
33259
|
rpc,
|
|
33260
|
+
undoRecordChange,
|
|
33080
33261
|
urlParam,
|
|
33081
33262
|
useAgentRun,
|
|
33082
33263
|
useAgentRuns,
|
package/dist/router.js
CHANGED
package/dist/rpc.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { type UrlParams, type UrlParamsPatch } from "./url_params.js";
|
|
|
9
9
|
* app → host: { id, op, payload }
|
|
10
10
|
* host → app: { id, type: "result", data } | { id, type: "error", message }
|
|
11
11
|
*/
|
|
12
|
-
export type RpcOp = "query" | "export" | "field_options" | "workflow" | "agentRuns" | "agentRun.get" | "agentRun.cancel" | "upload" | "file.rename" | "members" | "context" | "write_model" | "prediction_miss" | "openExternal" | "openApp" | "askAi" | "urlState.get" | "urlState.set" | "folder.get" | "folder.set" | "comments.list" | "comments.create" | "comments.update" | "comments.delete" | "comments.counts" | "activity.list" | "recording.start" | "recording.stop";
|
|
12
|
+
export type RpcOp = "query" | "export" | "field_options" | "workflow" | "agentRuns" | "agentRun.get" | "agentRun.cancel" | "upload" | "file.rename" | "members" | "context" | "write_model" | "prediction_miss" | "openExternal" | "openApp" | "askAi" | "urlState.get" | "urlState.set" | "folder.get" | "folder.set" | "comments.list" | "comments.create" | "comments.update" | "comments.delete" | "comments.counts" | "activity.list" | "activity.undo" | "recording.start" | "recording.stop";
|
|
13
13
|
export interface AgentRunPayload {
|
|
14
14
|
alias: string;
|
|
15
15
|
session_id: string;
|
package/dist/shared_types.d.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* `scripts/build_sdk.mjs`, because that package is private and ships in no
|
|
4
4
|
* tarball. Written at every build, never edited.
|
|
5
5
|
*/
|
|
6
|
-
export type ActivityEntry = { changes: { field_id: string; label: string; before: string | number | boolean | { key: string; label: string; }[] | { id: string; display: string; }[] | { id: string; name: string | null; image?: string | undefined; }[] | { id: string; filename: string; mime_type: string; file_storage_key?: string | undefined; url?: string | undefined; thumbnail_url?: string | undefined; preview_url?: string | undefined; size?: number | null | undefined; created_at?: string | undefined; document_template_id?: string | undefined; }[] | null; after: string | number | boolean | { key: string; label: string; }[] | { id: string; display: string; }[] | { id: string; name: string | null; image?: string | undefined; }[] | { id: string; filename: string; mime_type: string; file_storage_key?: string | undefined; url?: string | undefined; thumbnail_url?: string | undefined; preview_url?: string | undefined; size?: number | null | undefined; created_at?: string | undefined; document_template_id?: string | undefined; }[] | null; removed?: true | undefined; }[]; id: string; actor: { kind: "member" | "agent" | "workflow" | "import"; id: string; name: string; image?: string | undefined; }; at: string; until: string; kind: "change"; created?: true | undefined; } | { comment: { id: string; body: string; mentions: { id: string; name: string; }[]; files?: { id: string; filename: string; mime_type: string; file_storage_key?: string | undefined; url?: string | undefined; thumbnail_url?: string | undefined; preview_url?: string | undefined; size?: number | null | undefined; created_at?: string | undefined; document_template_id?: string | undefined; }[] | undefined; }; id: string; actor: { kind: "member" | "agent" | "workflow" | "import"; id: string; name: string; image?: string | undefined; }; at: string; until: string; kind: "comment"; };
|
|
6
|
+
export type ActivityEntry = { undoable: boolean; changes: { field_id: string; label: string; before: string | number | boolean | { key: string; label: string; }[] | { id: string; display: string; }[] | { id: string; name: string | null; image?: string | undefined; }[] | { id: string; filename: string; mime_type: string; file_storage_key?: string | undefined; url?: string | undefined; thumbnail_url?: string | undefined; preview_url?: string | undefined; size?: number | null | undefined; created_at?: string | undefined; document_template_id?: string | undefined; }[] | null; after: string | number | boolean | { key: string; label: string; }[] | { id: string; display: string; }[] | { id: string; name: string | null; image?: string | undefined; }[] | { id: string; filename: string; mime_type: string; file_storage_key?: string | undefined; url?: string | undefined; thumbnail_url?: string | undefined; preview_url?: string | undefined; size?: number | null | undefined; created_at?: string | undefined; document_template_id?: string | undefined; }[] | null; removed?: true | undefined; }[]; id: string; actor: { kind: "member" | "agent" | "workflow" | "import"; id: string; name: string; image?: string | undefined; }; at: string; until: string; kind: "change"; created?: true | undefined; undo?: true | undefined; undone?: { by: { kind: "member" | "agent" | "workflow" | "import"; id: string; name: string; image?: string | undefined; }; at: string; } | undefined; } | { comment: { id: string; body: string; mentions: { id: string; name: string; }[]; files?: { id: string; filename: string; mime_type: string; file_storage_key?: string | undefined; url?: string | undefined; thumbnail_url?: string | undefined; preview_url?: string | undefined; size?: number | null | undefined; created_at?: string | undefined; document_template_id?: string | undefined; }[] | undefined; }; id: string; actor: { kind: "member" | "agent" | "workflow" | "import"; id: string; name: string; image?: string | undefined; }; at: string; until: string; kind: "comment"; };
|
|
7
|
+
export type ActivityUndoResult = { reverted: { field_id: string; label: string; }[]; kept: { field_id: string; label: string; reason: "changed_since" | "refused"; }[]; };
|
|
7
8
|
export type AppAbsence = { member: { id: string; name: string; }; stand_in: { id: string; name: string; }; starts_on: string; ends_on: string; group_ids: string[]; };
|
|
8
9
|
/** The file an app export made, as a link to download it: no workspace file, so no id, and kept a day. */
|
|
9
10
|
export type ExportedFile = {
|
package/dist/store.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export type Fetcher<T> = (askedAt: number, current: T | undefined) => Promise<T>
|
|
|
4
4
|
export declare function tick(): number;
|
|
5
5
|
/** Ask again every key reading one of `aliases` — every query key where none is named; an unmounted one on its next mount. */
|
|
6
6
|
export declare function refetchReads(aliases: readonly string[] | undefined): void;
|
|
7
|
+
/** `refetchReads`, settled once every key a screen reads now holds an answer asked after this call — or its refusal. */
|
|
8
|
+
export declare function readsAnswered(aliases: readonly string[]): Promise<void>;
|
|
7
9
|
/** The host's push, naming the aliases whose tables moved: each key reading one asked again (`refetchReads`), in one
|
|
8
10
|
* paint. */
|
|
9
11
|
export declare function refetchPushed(aliases: readonly string[]): void;
|
package/dist/viewer.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export declare function readContext(): Promise<AppContext>;
|
|
|
10
10
|
*/
|
|
11
11
|
export declare function useAppContext(): {
|
|
12
12
|
memberId: string | null;
|
|
13
|
+
/** The viewer is a signed-in member of the app's organization: the host named them and told their groups. */
|
|
14
|
+
insider: boolean;
|
|
13
15
|
/** The viewer's member groups, by id; `null` where the host did not say. */
|
|
14
16
|
groupIds: readonly string[] | null;
|
|
15
17
|
/** Who in the organization is away, or will be, and who stands in; `null` where the host did not say. */
|
|
@@ -373,7 +373,7 @@ table's id: read it off a row of the query that renders the badges (`rows[0]?.__
|
|
|
373
373
|
rather than pasting a `tbl_` id, which a copy of the app cannot carry. Pass `undefined` while no
|
|
374
374
|
row is on screen — no request is sent and `counts` is `{}`.
|
|
375
375
|
|
|
376
|
-
## Record activity: `useRecordActivity`
|
|
376
|
+
## Record activity: `useRecordActivity`, `undoRecordChange`
|
|
377
377
|
|
|
378
378
|
Who changed which of a record's fields, and who commented on it — one entry per save
|
|
379
379
|
(`dist/activity.d.ts`). For a JSON app: `entity` is the alias the app's spec names the record's
|
|
@@ -383,6 +383,8 @@ entity under.
|
|
|
383
383
|
const { entries, next, loading, error, available, refetch } = useRecordActivity("job", recordId);
|
|
384
384
|
// The page past it, mounted where the reader asks for more:
|
|
385
385
|
const older = useRecordActivity("job", recordId, { after: next });
|
|
386
|
+
// One change entry put back, as the viewer's own save:
|
|
387
|
+
const { reverted, kept } = await undoRecordChange("job", recordId, entry.id);
|
|
386
388
|
```
|
|
387
389
|
|
|
388
390
|
- **Authority: the app's own read of the record.** The server reaches the record through the
|
|
@@ -392,8 +394,8 @@ const older = useRecordActivity("job", recordId, { after: next });
|
|
|
392
394
|
entry. A field taken out of the app while its table keeps it stays hidden; a field gone from its
|
|
393
395
|
table appears where the version serving when it was written read it, marked `removed`. Comments
|
|
394
396
|
appear only where the app declares `comments`.
|
|
395
|
-
- **Members of the app's organization only** — the trail names its people.
|
|
396
|
-
is `false` and nothing is
|
|
397
|
+
- **Members of the app's organization only** — the trail names its people. For a public visitor,
|
|
398
|
+
a member of another organization, and standalone, `available` is `false` and nothing is asked.
|
|
397
399
|
- **One entry per save.** Edits by one actor on one record less than ten minutes apart read as one
|
|
398
400
|
entry: each field from its value before the first edit to its value after the last, a field edited
|
|
399
401
|
back to where it began dropped (and an entry left with nothing). A comment, another actor or a
|
|
@@ -401,7 +403,9 @@ const older = useRecordActivity("job", recordId, { after: next });
|
|
|
401
403
|
its maker's edits after it, however soon, are entries of their own. `at` is the entry's first
|
|
402
404
|
write, `until` its last.
|
|
403
405
|
- **An entry** (`ActivityEntry`) is `kind: "change"` with `changes`, or `kind: "comment"` with
|
|
404
|
-
`comment: { id, body, mentions }`.
|
|
406
|
+
`comment: { id, body, mentions }`. A change entry also says `undoable` — the viewer may put it
|
|
407
|
+
back — `undone: { by, at }` once someone has, and `undo: true` where it is itself the save that
|
|
408
|
+
put an earlier one back. `actor`: `{ kind, id, name, image? }` — `member` (a person),
|
|
405
409
|
`agent` (an AI acting for a member: the chat, an app's agent, an MCP client — named by that
|
|
406
410
|
member), `workflow` (a workflow on its own, named by the workflow) or `import` (a script over the
|
|
407
411
|
API or the CLI, named by the member whose key ran it). `name` is empty where nothing resolves it.
|
|
@@ -411,6 +415,17 @@ const older = useRecordActivity("job", recordId, { after: next });
|
|
|
411
415
|
`[{ key, label }]` (`readSelect`; each option's label as it was when written), a member field
|
|
412
416
|
`[{ id, name, image? }]`, a link `[{ id, display }]` (`readLinks`), files presigned (`readFiles`),
|
|
413
417
|
`null` for empty.
|
|
418
|
+
- **`undoRecordChange(entity, record_id, entry_id)`** puts one change entry back: each field it
|
|
419
|
+
changed that the app reads returns to what it held before the entry, where the record still holds
|
|
420
|
+
what the entry wrote. It answers `{ reverted, kept }`: the fields put back (`{ field_id, label }`),
|
|
421
|
+
and those left as they stand with a `reason` — `changed_since` (someone changed it since) or
|
|
422
|
+
`refused` (the table refused the write: a lock, a check). A field already holding what it held
|
|
423
|
+
before is in neither. The write is the viewer's own save: they hold `write_data` on the table as
|
|
424
|
+
the call arrives, on a row their own row rule covers, and the record's lock, the table's checks
|
|
425
|
+
and its write rules answer as for any save of theirs. `undoable` is `false`, and the call
|
|
426
|
+
refused, for the entry that made the record, an entry that is an undo, one already put back, and
|
|
427
|
+
a viewer who may not write the record.
|
|
428
|
+
The undo is an entry of its own; the answer arrives once the record's reads are read anew.
|
|
414
429
|
- **Pages**: newest first, about thirty entries each; `next` is where the trail goes on —
|
|
415
430
|
`undefined` at its end. A page may hold fewer entries and still say `next` where the trail it read
|
|
416
431
|
held little the reader may see.
|
|
@@ -428,4 +443,5 @@ const older = useRecordActivity("job", recordId, { after: next });
|
|
|
428
443
|
| `useViewer` | member id (view-as target) | `null` |
|
|
429
444
|
| `useWorkspaceTimezone` / `useWorkspaceCurrency` | ✓ | ✓ |
|
|
430
445
|
| `useComments` / `useCommentCounts` | ✓ when capability declared | ✗ `available: false` |
|
|
431
|
-
| `useRecordActivity` | ✓ the app's organization's members | ✗ `available: false
|
|
446
|
+
| `useRecordActivity` | ✓ the app's organization's members | ✗ `available: false`, nothing asked |
|
|
447
|
+
| `undoRecordChange` | ✓ a member holding the table's write, on a row they may write | ✗ rejects |
|
package/docs/runtime.md
CHANGED
|
@@ -210,6 +210,7 @@ surfaces:
|
|
|
210
210
|
|---|---|---|
|
|
211
211
|
| `query`, `field_options`, `workflow`, `members`, `context`, `upload`, `urlState.get/set`, `openExternal` | yes | yes |
|
|
212
212
|
| `comments.*` | yes | rejects — `"Comments are available only in embedded apps — a signed-in member is required."` |
|
|
213
|
+
| `activity.list`, `activity.undo` | yes, for a member of the app's organization; `activity.undo` for one who may write the record. A host that does not carry the op answers `"Unknown RPC op: activity.undo"` | rejects — `"A record's activity is available only in embedded apps — a signed-in member is required."` |
|
|
213
214
|
| `agentRun` (streaming, internal to `useAgentRun`) | yes | yes |
|
|
214
215
|
| `agentRun.get`, `agentRun.cancel` | yes | yes |
|
|
215
216
|
| `agentRuns` (session history) | **no** — the product host doesn't implement it either (`"Unknown RPC op: agentRuns"`) | rejects — a standalone caller is anonymous, and a history is its member's (`"App agent runs require an authenticated member …"`) |
|
package/docs/security.md
CHANGED
|
@@ -103,6 +103,7 @@ A publicly-shared app (its own origin, or its public link) is reachable by **any
|
|
|
103
103
|
| Agent runs (`useAgentRun`) | **No** — rejected: agent runs require an authenticated member, and each member's run history is private to them (a guessed session id cannot read another member's thread) |
|
|
104
104
|
| Comments (`useComments`) | **No** — members-only |
|
|
105
105
|
| Record activity (`useRecordActivity`) | **No** — the app's organization's members only |
|
|
106
|
+
| Undo of a record's change (`undoRecordChange`) | **No** — a member of the app's organization holding `write_data` on the record's table, on a row their own row rule covers |
|
|
106
107
|
| Member roster (`useMembers`) | **No** — same-org members only (below) |
|
|
107
108
|
|
|
108
109
|
**Warning — `is_current_member` on a public app resolves to the app owner.** An anonymous request has no viewer to bind, so both `is_current_member` and `current_member in_any_group` fall back to the owner's identity and the owner's groups. A "my items" query shows every anonymous visitor the *owner's* items, and a manager-only read template gated on a group the owner belongs to **passes for every anonymous visitor**. Never rely on viewer-bound predicates in a public app.
|
package/docs/workflows.md
CHANGED
|
@@ -392,7 +392,7 @@ body does not look like what you typed.
|
|
|
392
392
|
| `arr.at(i)` | `at(arr, i)` — negative counts from the end |
|
|
393
393
|
| `arr.filter(Boolean)` | `compact(arr)` — drops every falsy element |
|
|
394
394
|
| `[...a, b]` | `concat(a, [b])` |
|
|
395
|
-
| `{ ...a, b: 1 }` | `merge(a, { b: 1 })` — later sources win, as in JS |
|
|
395
|
+
| `{ ...a, b: 1 }` | `merge(merge({}, a), { b: 1 })` — later sources win, as in JS — and reads back as the spread, typed as you wrote it |
|
|
396
396
|
| `{ table_id }` | `{ table_id: table_id }` — in expressions **and** tool inputs |
|
|
397
397
|
| `undefined` | `null` |
|
|
398
398
|
| `xs.push(a, b);` | `xs = concat(xs, [a, b])` — statement form |
|
package/package.json
CHANGED