-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathresolvePageVarTokens.ts
More file actions
77 lines (72 loc) · 2.93 KB
/
Copy pathresolvePageVarTokens.ts
File metadata and controls
77 lines (72 loc) · 2.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* resolvePageVarTokens — resolve `{{page.<path>}}` tokens against a page-variable
* snapshot. The data-entry bridge for SDUI forms: an interactive input
* (`element:text_input`, `element:record_picker`) writes a page variable; a
* submit action references it in its params/body as `{{page.<var>}}`; this
* resolves those tokens against the live snapshot — published into the action
* context by `PageVariableActionBridge` — just before the request body is built.
*
* - A WHOLE-VALUE token (`"{{page.amount}}"`) is replaced by the variable's RAW
* value, preserving its type — a number stays a number, an object stays an
* object — so numeric/boolean/array form fields submit with the right JSON
* type rather than being stringified.
* - An EMBEDDED token (`"/orgs/{{page.slug}}/setup"`) is string-interpolated.
* - Resolution walks nested objects/arrays. A whole-value miss resolves to ''
* (kept as a present-but-empty field); an embedded miss drops to ''.
*
* Distinct from the single-brace `{field}` row-record interpolation used in API
* target URLs (different brace count, different source) so the two never collide.
*/
const WHOLE_RE = /^\s*\{\{\s*page\.([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*\}\}\s*$/;
function lookup(path: string, vars: Record<string, any>): unknown {
let node: any = vars;
for (const seg of path.split('.')) {
if (node == null) return undefined;
node = node[seg];
}
return node;
}
function resolveString(str: string, vars: Record<string, any>): unknown {
if (!str.includes('{{')) return str;
const whole = str.match(WHOLE_RE);
if (whole) {
const v = lookup(whole[1], vars);
return v === undefined ? '' : v;
}
// Fresh global regex per call — avoids shared `lastIndex` state.
return str.replace(
/\{\{\s*page\.([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*\}\}/g,
(_m, path) => {
const v = lookup(path, vars);
return v == null ? '' : String(v);
},
);
}
function walk(value: any, vars: Record<string, any>): any {
if (typeof value === 'string') return resolveString(value, vars);
if (Array.isArray(value)) return value.map((v) => walk(v, vars));
if (value && typeof value === 'object') {
const out: Record<string, any> = {};
for (const [k, v] of Object.entries(value)) out[k] = walk(v, vars);
return out;
}
return value;
}
/**
* Deep-resolve every `{{page.<var>}}` token in `value` against `pageVariables`.
* Returns `value` unchanged when there is no snapshot. Non-string leaves pass
* through untouched; the input is never mutated (objects/arrays are copied).
*/
export function resolvePageVarTokens<T>(
value: T,
pageVariables?: Record<string, any> | null,
): T {
if (!pageVariables) return value;
return walk(value, pageVariables) as T;
}