@tinkerlandai/indiana-iam 0.3.0 → 0.4.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.
@@ -0,0 +1,388 @@
1
+ import { uiRelation } from "./ui-structure.mjs";
2
+
3
+ /** Bounded source dataflow: current location -> query parameter -> Navigate query consumer.
4
+ * This records conditional UI relations only. It never runs guards, helpers or authentication. */
5
+ export function sourceRedirects({
6
+ ts,
7
+ modules,
8
+ binding,
9
+ functionFrom,
10
+ externalImport,
11
+ sourceLocation,
12
+ constant,
13
+ pages,
14
+ components,
15
+ documents,
16
+ }) {
17
+ const router = (ref) =>
18
+ ["react-router", "react-router-dom"].includes(ref?.specifier);
19
+ const unknown = Symbol("unresolved redirect");
20
+ const unwrap = (n) => {
21
+ while (
22
+ n &&
23
+ (ts.isParenthesizedExpression(n) ||
24
+ ts.isAsExpression(n) ||
25
+ ts.isNonNullExpression(n) ||
26
+ ts.isTypeAssertionExpression(n))
27
+ )
28
+ n = n.expression;
29
+ return n;
30
+ };
31
+ const score = (v) =>
32
+ v?.kind === "url"
33
+ ? 5
34
+ : v?.kind === "query"
35
+ ? 4
36
+ : v?.kind === "origin"
37
+ ? 3
38
+ : typeof v === "string"
39
+ ? 1
40
+ : 0;
41
+ const choose = (values) =>
42
+ values
43
+ .filter((v) => v !== unknown && v !== undefined && v !== null)
44
+ .sort((a, b) => score(b) - score(a))[0] ?? unknown;
45
+ const definitions = new Map();
46
+ let budget = 0;
47
+ function value(raw, module, env, refs, depth = 0) {
48
+ const node = unwrap(raw);
49
+ if (!node || depth > 16 || ++budget > 20000) return unknown;
50
+ const next = (n) => value(n, module, env, refs, depth + 1);
51
+ if (ts.isIdentifier(node)) {
52
+ if (env.has(node.text)) return env.get(node.text);
53
+ const c = constant(node, module);
54
+ return c === undefined ? unknown : c;
55
+ }
56
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
57
+ return node.text;
58
+ if (ts.isPropertyAccessExpression(node)) {
59
+ const parent = next(node.expression),
60
+ key = node.name.text;
61
+ if (
62
+ parent?.kind === "location" &&
63
+ ["pathname", "search", "hash"].includes(key)
64
+ )
65
+ return { kind: "origin", pathname: key === "pathname" };
66
+ if (parent && typeof parent === "object" && Object.hasOwn(parent, key))
67
+ return parent[key];
68
+ const c = constant(node, module);
69
+ return c === undefined ? unknown : c;
70
+ }
71
+ if (ts.isObjectLiteralExpression(node))
72
+ return Object.fromEntries(
73
+ node.properties.flatMap((p) =>
74
+ ts.isShorthandPropertyAssignment(p)
75
+ ? [[p.name.text, next(p.name)]]
76
+ : ts.isPropertyAssignment(p)
77
+ ? [[p.name.text, next(p.initializer)]]
78
+ : [],
79
+ ),
80
+ );
81
+ if (ts.isConditionalExpression(node))
82
+ return choose([next(node.whenTrue), next(node.whenFalse)]);
83
+ if (
84
+ ts.isBinaryExpression(node) &&
85
+ [ts.SyntaxKind.BarBarToken, ts.SyntaxKind.QuestionQuestionToken].includes(
86
+ node.operatorToken.kind,
87
+ )
88
+ )
89
+ return choose([next(node.left), next(node.right)]);
90
+ if (
91
+ ts.isNewExpression(node) &&
92
+ ts.isIdentifier(node.expression) &&
93
+ node.expression.text === "URLSearchParams" &&
94
+ !node.arguments?.length
95
+ )
96
+ return { kind: "params", entries: new Map() };
97
+ if (ts.isTemplateExpression(node)) {
98
+ const parts = [node.head.text];
99
+ for (const span of node.templateSpans)
100
+ parts.push(next(span.expression), span.literal.text);
101
+ const dynamic = parts.filter((p) => typeof p !== "string");
102
+ if (
103
+ dynamic.length &&
104
+ dynamic.every((p) => p?.kind === "origin") &&
105
+ dynamic.some((p) => p.pathname) &&
106
+ parts.filter((p) => typeof p === "string").every((p) => p === "")
107
+ )
108
+ return { kind: "origin", pathname: true };
109
+ if (dynamic.length === 1 && dynamic[0]?.kind === "querystring") {
110
+ const index = parts.indexOf(dynamic[0]),
111
+ prefix = parts.slice(0, index).join(""),
112
+ suffix = parts.slice(index + 1).join("");
113
+ if (prefix.endsWith("?") && suffix === "")
114
+ return {
115
+ kind: "url",
116
+ path: prefix.slice(0, -1),
117
+ entries: dynamic[0].entries,
118
+ };
119
+ }
120
+ return dynamic.length ? unknown : parts.join("");
121
+ }
122
+ if (ts.isCallExpression(node)) {
123
+ if (ts.isIdentifier(node.expression)) {
124
+ const primitive = externalImport(module, node.expression.text);
125
+ if (router(primitive) && primitive.name === "useLocation")
126
+ return { kind: "location" };
127
+ if (router(primitive) && primitive.name === "useSearchParams")
128
+ return { kind: "search-hook" };
129
+ const ref = binding(module, node.expression.text),
130
+ fn = ref && functionFrom(ref.node);
131
+ if (
132
+ fn &&
133
+ !fn.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) &&
134
+ fn.parameters.every((p) => ts.isIdentifier(p.name))
135
+ ) {
136
+ refs.set(
137
+ ref.module.file + "#" + ref.name,
138
+ sourceLocation(ref.module, fn, ref.name),
139
+ );
140
+ const local = new Map(
141
+ fn.parameters.map((p, i) => [p.name.text, next(node.arguments[i])]),
142
+ );
143
+ const returns = [];
144
+ scanBody(fn.body, ref.module, local, refs, returns, null, depth + 1);
145
+ return choose(returns);
146
+ }
147
+ }
148
+ if (ts.isPropertyAccessExpression(node.expression)) {
149
+ const parent = next(node.expression.expression),
150
+ method = node.expression.name.text;
151
+ if (
152
+ parent?.kind === "search" &&
153
+ method === "get" &&
154
+ node.arguments.length === 1
155
+ ) {
156
+ const key = next(node.arguments[0]);
157
+ return typeof key === "string"
158
+ ? { kind: "query", parameter: key }
159
+ : unknown;
160
+ }
161
+ if (
162
+ parent?.kind === "params" &&
163
+ method === "set" &&
164
+ node.arguments.length === 2
165
+ ) {
166
+ const key = next(node.arguments[0]),
167
+ v = next(node.arguments[1]);
168
+ if (typeof key === "string" && v !== unknown)
169
+ parent.entries.set(key, v);
170
+ return undefined;
171
+ }
172
+ if (parent?.kind === "params" && method === "toString")
173
+ return { kind: "querystring", entries: parent.entries };
174
+ }
175
+ }
176
+ const c = constant(node, module);
177
+ return c === undefined ? unknown : c;
178
+ }
179
+ function scanBody(
180
+ node,
181
+ module,
182
+ env,
183
+ refs,
184
+ returns,
185
+ onNavigate,
186
+ depth = 0,
187
+ condition = null,
188
+ ) {
189
+ if (!node || depth > 24) return;
190
+ if (ts.isBlock(node) || ts.isSourceFile(node))
191
+ for (const statement of node.statements)
192
+ scanBody(
193
+ statement,
194
+ module,
195
+ env,
196
+ refs,
197
+ returns,
198
+ onNavigate,
199
+ depth + 1,
200
+ condition,
201
+ );
202
+ else if (ts.isVariableStatement(node))
203
+ for (const decl of node.declarationList.declarations) {
204
+ const v = value(decl.initializer, module, env, refs, depth);
205
+ if (ts.isIdentifier(decl.name)) env.set(decl.name.text, v);
206
+ else if (ts.isObjectBindingPattern(decl.name)) {
207
+ for (const field of decl.name.elements)
208
+ if (ts.isIdentifier(field.name)) {
209
+ const key = field.propertyName?.text || field.name.text;
210
+ env.set(
211
+ field.name.text,
212
+ v?.kind === "location" &&
213
+ ["pathname", "search", "hash"].includes(key)
214
+ ? { kind: "origin", pathname: key === "pathname" }
215
+ : v && typeof v === "object"
216
+ ? (v[key] ?? unknown)
217
+ : unknown,
218
+ );
219
+ }
220
+ } else if (
221
+ ts.isArrayBindingPattern(decl.name) &&
222
+ v?.kind === "search-hook"
223
+ ) {
224
+ const name = decl.name.elements[0]?.name;
225
+ if (name && ts.isIdentifier(name))
226
+ env.set(name.text, { kind: "search" });
227
+ }
228
+ }
229
+ else if (ts.isIfStatement(node)) {
230
+ const label = node.expression.getText(module.source).slice(0, 160),
231
+ combine = (text) =>
232
+ condition ? condition + " && (" + text + ")" : text;
233
+ scanBody(
234
+ node.thenStatement,
235
+ module,
236
+ new Map(env),
237
+ refs,
238
+ returns,
239
+ onNavigate,
240
+ depth + 1,
241
+ combine(label),
242
+ );
243
+ if (node.elseStatement)
244
+ scanBody(
245
+ node.elseStatement,
246
+ module,
247
+ new Map(env),
248
+ refs,
249
+ returns,
250
+ onNavigate,
251
+ depth + 1,
252
+ combine("!(" + label + ")"),
253
+ );
254
+ } else if (ts.isExpressionStatement(node))
255
+ value(node.expression, module, env, refs, depth);
256
+ else if (ts.isReturnStatement(node)) {
257
+ if (onNavigate)
258
+ scanJsx(
259
+ node.expression,
260
+ module,
261
+ env,
262
+ refs,
263
+ onNavigate,
264
+ depth,
265
+ condition,
266
+ );
267
+ else returns.push(value(node.expression, module, env, refs, depth));
268
+ } else if (!ts.isFunctionLike(node) && onNavigate)
269
+ scanJsx(node, module, env, refs, onNavigate, depth, condition);
270
+ }
271
+ function scanJsx(node, module, env, refs, emit, depth, condition) {
272
+ if (!node || depth > 32) return;
273
+ if (ts.isConditionalExpression(node)) {
274
+ const label = node.condition.getText(module.source).slice(0, 160);
275
+ scanJsx(node.whenTrue, module, env, refs, emit, depth + 1, label);
276
+ scanJsx(
277
+ node.whenFalse,
278
+ module,
279
+ env,
280
+ refs,
281
+ emit,
282
+ depth + 1,
283
+ "!(" + label + ")",
284
+ );
285
+ return;
286
+ }
287
+ if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
288
+ const opening = ts.isJsxElement(node) ? node.openingElement : node,
289
+ primitive = externalImport(
290
+ module,
291
+ opening.tagName.getText(module.source),
292
+ );
293
+ if (router(primitive) && primitive.name === "Navigate") {
294
+ const to = opening.attributes.properties.find(
295
+ (p) => ts.isJsxAttribute(p) && p.name.text === "to",
296
+ );
297
+ const proof = new Map(refs),
298
+ target = value(
299
+ ts.isJsxExpression(to?.initializer)
300
+ ? to.initializer.expression
301
+ : to?.initializer,
302
+ module,
303
+ env,
304
+ proof,
305
+ );
306
+ emit({
307
+ target,
308
+ condition,
309
+ source: sourceLocation(module, node, "$source-navigation"),
310
+ refs: [...proof.values()],
311
+ });
312
+ return;
313
+ }
314
+ }
315
+ if (ts.isFunctionLike(node)) return;
316
+ ts.forEachChild(node, (child) =>
317
+ scanJsx(child, module, env, refs, emit, depth + 1, condition),
318
+ );
319
+ }
320
+ for (const component of components) {
321
+ const module = modules.get(component.file),
322
+ ref = module && binding(module, component.name),
323
+ fn = ref && functionFrom(ref.node);
324
+ if (!fn?.body) continue;
325
+ budget = 0;
326
+ const rows = [];
327
+ scanBody(fn.body, module, new Map(), new Map(), [], (row) =>
328
+ rows.push(row),
329
+ );
330
+ definitions.set(component.componentKey, rows);
331
+ }
332
+ const byRoute = new Map(
333
+ pages.map((p) => [
334
+ p.pageKey,
335
+ p.componentKeys.flatMap((key) => definitions.get(key) ?? []),
336
+ ]),
337
+ );
338
+ const result = [];
339
+ for (const page of pages)
340
+ for (const row of byRoute.get(page.pageKey)) {
341
+ const target = row.target;
342
+ if (target?.kind !== "url" || !target.path.startsWith("/")) continue;
343
+ const destination = pages.find((p) => p.route === target.path);
344
+ if (!destination) continue;
345
+ for (const [parameter, origin] of target.entries) {
346
+ if (origin?.kind !== "origin" || !origin.pathname) continue;
347
+ const consumers = byRoute
348
+ .get(destination.pageKey)
349
+ .filter(
350
+ (r) =>
351
+ r.target?.kind === "query" && r.target.parameter === parameter,
352
+ );
353
+ if (!consumers.length) continue;
354
+ const hasPassword = documents
355
+ .filter((d) => d.pageKey === destination.pageKey)
356
+ .some((d) =>
357
+ d.nodes.some(
358
+ (n) => n.tag === "input" && n.attributes.type === "password",
359
+ ),
360
+ );
361
+ const sourceRefs = [row.source, ...row.refs].slice(0, 16);
362
+ result.push(
363
+ uiRelation({
364
+ routeKey: page.pageKey,
365
+ targetRouteKey: destination.pageKey,
366
+ kind: hasPassword ? "login" : "redirect",
367
+ returnParameter: parameter,
368
+ condition: row.condition,
369
+ activation: "route-condition",
370
+ sourceRefs,
371
+ }),
372
+ );
373
+ for (const consumer of consumers)
374
+ result.push(
375
+ uiRelation({
376
+ routeKey: destination.pageKey,
377
+ targetRouteKey: page.pageKey,
378
+ kind: "return",
379
+ returnParameter: parameter,
380
+ condition: consumer.condition,
381
+ activation: "route-condition",
382
+ sourceRefs: [consumer.source, ...consumer.refs].slice(0, 16),
383
+ }),
384
+ );
385
+ }
386
+ }
387
+ return [...new Map(result.map((r) => [r.relationKey, r])).values()];
388
+ }
@@ -0,0 +1,48 @@
1
+ /** A finite source-only React Router matcher. No merchant modules or regular expressions run. */
2
+ export function sourceMatchPath(input, pathname) {
3
+ const options = typeof input === "string" ? { path: input } : input;
4
+ if (
5
+ !options ||
6
+ typeof options.path !== "string" ||
7
+ typeof pathname !== "string" ||
8
+ options.path.length > 1024 ||
9
+ pathname.length > 4000
10
+ )
11
+ throw Error("Unsupported source path");
12
+ const parts = options.path.split("/").filter(Boolean),
13
+ names = [];
14
+ let pattern = "^";
15
+ for (const [index, part] of parts.entries()) {
16
+ if (part === "*" && index === parts.length - 1) {
17
+ names.push("*");
18
+ pattern += "(?:/(.*))?";
19
+ } else if (/^:[A-Za-z_][\w-]*\??$/.test(part)) {
20
+ names.push(part.slice(1).replace(/\?$/, ""));
21
+ pattern += part.endsWith("?") ? "(?:/([^/]+))?" : "/([^/]+)";
22
+ } else if (/^[\w.~-]+$/.test(part))
23
+ pattern += "/" + part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24
+ else throw Error("Unsupported source pattern");
25
+ }
26
+ pattern += options.end === false ? "(?=/|$)" : "/*$";
27
+ const match = new RegExp(pattern, options.caseSensitive ? "" : "i").exec(
28
+ pathname,
29
+ );
30
+ if (!match) return null;
31
+ return {
32
+ params: Object.fromEntries(
33
+ names.map((name, index) => [
34
+ name,
35
+ match[index + 1] === undefined
36
+ ? undefined
37
+ : decodeURIComponent(match[index + 1]),
38
+ ]),
39
+ ),
40
+ pathname: match[0] || "/",
41
+ pathnameBase: (match[0] || "/").replace(/\/+$/, "") || "/",
42
+ pattern: {
43
+ path: options.path,
44
+ caseSensitive: options.caseSensitive ?? false,
45
+ end: options.end ?? true,
46
+ },
47
+ };
48
+ }
@@ -1,6 +1,7 @@
1
+ import {sourceMatchPath} from './static-primitives.mjs';
1
2
  // Abstract interpretation of bounded, synchronous, read-only source helpers.
2
3
  // No import(), eval, native callback invocation, await or object mutation.
3
- export function staticQuery({ ts, node, module, context, binding, unknown, redact, sensitive }) {
4
+ export function staticQuery({ ts, node, module, context, binding, unknown, redact, sensitive, reference, isReference, imported, project }) {
4
5
  let steps = 0;
5
6
  const closure = Symbol("source-function"), writable = Symbol("function-locals"), fail = () => { throw unknown; };
6
7
  const tick = () => { if (++steps > 6000) fail(); };
@@ -9,7 +10,9 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
9
10
  const scalar = value => value === null || ["string", "boolean", "number", "undefined"].includes(typeof value);
10
11
  function clean(value, depth = 0) {
11
12
  tick();
12
- if (depth > 20 || value === unknown || value?.[closure]) fail();
13
+ if (depth > 20) fail();
14
+ if(isReference?.(value))return value;
15
+ if(value===unknown||value?.[closure]){if(depth>0)return unknown;fail();}
13
16
  if (typeof value === "number" && !Number.isFinite(value)) fail();
14
17
  if (typeof value === "string") return redact(value);
15
18
  if (Array.isArray(value)) { if (value.length > 256) fail(); return value.map(v => clean(v, depth + 1)); }
@@ -20,6 +23,7 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
20
23
  function read(n, m, env, seen = new Set(), depth = 0) {
21
24
  tick(); n = unwrap(n); if (!n || depth > 24) fail();
22
25
  const ev = child => read(child, m, env, seen, depth + 1);
26
+ const opaque=!(ts.isIdentifier(n)&&Object.hasOwn(env,n.text))&&reference?.(n,m);if(opaque)return opaque;
23
27
  if (ts.isStringLiteralLike(n)) return redact(n.text);
24
28
  if (ts.isNumericLiteral(n)) return Number(n.text);
25
29
  if (n.kind === ts.SyntaxKind.TrueKeyword) return true;
@@ -38,11 +42,11 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
38
42
  if (ts.isObjectLiteralExpression(n)) {
39
43
  const value = Object.create(null);
40
44
  for (const p of n.properties) {
41
- if (ts.isShorthandPropertyAssignment(p)) { value[p.name.text] = sensitive(p.name.text) ? unknown : ev(p.name); continue; }
45
+ if (ts.isShorthandPropertyAssignment(p)) { try{value[p.name.text] = sensitive(p.name.text) ? unknown : ev(p.name);}catch{value[p.name.text]=unknown;}continue; }
42
46
  if (!ts.isPropertyAssignment(p) || !p.name || ts.isComputedPropertyName(p.name)) fail();
43
47
  const key = p.name.text; if (!key || ["__proto__", "constructor", "prototype"].includes(key)) fail();
44
48
  // Keep unrelated async/mutating helpers opaque, never evaluate their bodies.
45
- value[key] = sensitive(key) ? unknown : ev(p.initializer);
49
+ try{value[key] = sensitive(key) ? unknown : ev(p.initializer);}catch{value[key]=unknown;}
46
50
  }
47
51
  return value;
48
52
  }
@@ -50,8 +54,11 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
50
54
  const object = ev(n.expression), key = ts.isPropertyAccessExpression(n) ? n.name.text : ev(n.argumentExpression);
51
55
  if (object == null && n.questionDotToken) return undefined;
52
56
  if (object == null || ["__proto__", "constructor", "prototype"].includes(key) || sensitive(String(key))) fail();
53
- return Object.hasOwn(Object(object), key) || key === "length" ? object[key] : undefined;
57
+ const result=Object.hasOwn(Object(object), key) || key === "length" ? object[key] : undefined;
58
+ if(result===unknown)fail();return result;
54
59
  }
60
+ if(ts.isTemplateExpression(n)){let text=n.head.text;for(const span of n.templateSpans){const v=ev(span.expression);if(!scalar(v)||v===unknown)fail();text+=String(v)+span.literal.text;if(text.length>4000)fail();}return redact(text);}
61
+ if(ts.isRegularExpressionLiteral(n)){const raw=n.getText(m.source);if(raw==='/[?#]/')return {sourceRegex:'query-separator'};if(raw==='/\\/+$/')return {sourceRegex:'trailing-slashes'};if(raw==='/^./')return {sourceRegex:'first-character'};fail();}
55
62
  if (ts.isTypeOfExpression(n)) return typeof ev(n.expression);
56
63
  if (ts.isPrefixUnaryExpression(n)) { const value = ev(n.operand); if (n.operator === ts.SyntaxKind.ExclamationToken) return !value; if (n.operator === ts.SyntaxKind.MinusToken && typeof value === "number") return -value; fail(); }
57
64
  if (ts.isBinaryExpression(n)) {
@@ -67,15 +74,34 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
67
74
  }
68
75
  if (ts.isConditionalExpression(n)) return ev(ev(n.condition) ? n.whenTrue : n.whenFalse);
69
76
  if (ts.isCallExpression(n)) {
77
+ const projected=project?.(n,m,env);if(Array.isArray(projected))return projected[0];
70
78
  if (n.arguments.length > 8) fail();
79
+ const primitive=imported?.(m,n.expression.getText(m.source));
80
+ if(primitive?.specifier==='react'&&primitive.name==='useMemo'&&n.arguments.length===2){const fn=ev(n.arguments[0]);return call(fn,[],depth+1);}
71
81
  const args = n.arguments.map(ev);
82
+ if(ts.isPropertyAccessExpression(n.expression)&&ts.isIdentifier(n.expression.expression)&&n.expression.expression.text==='Object'&&!Object.hasOwn(env,'Object')&&!binding(m,'Object')){
83
+ const method=n.expression.name.text,target=args[0];
84
+ if(!plain(target)||isReference?.(target)||Object.keys(target).length>256)fail();
85
+ if(method==='keys'&&args.length===1)return Object.keys(target);
86
+ if(method==='hasOwn'&&args.length===2&&['string','number'].includes(typeof args[1]))return Object.hasOwn(target,args[1]);
87
+ fail();
88
+ }
89
+ if(['react-router','react-router-dom'].includes(primitive?.specifier)&&primitive.name==='matchPath'&&args.length===2)return sourceMatchPath(args[0],args[1]);
90
+ if(ts.isIdentifier(n.expression)&&n.expression.text==='String'&&args.length===1&&scalar(args[0]))return String(args[0]);
72
91
  if (ts.isPropertyAccessExpression(n.expression)) {
73
92
  const target = ev(n.expression.expression), method = n.expression.name.text;
74
- if (Array.isArray(target) && ["find", "filter", "map"].includes(method) && args.length === 1 && args[0]?.[closure]) {
93
+ if (Array.isArray(target) && ["find", "filter", "map", "some", "every", "flatMap"].includes(method) && args.length === 1 && args[0]?.[closure]) {
75
94
  if (target.length > 256) fail();
76
95
  const result = [];
77
- for (let i = 0; i < target.length; i++) { const value = call(args[0], [target[i], i], depth + 1); if (method === "find" && value) return target[i]; if (method === "filter" && value) result.push(target[i]); if (method === "map") result.push(value); }
78
- return method === "find" ? undefined : result;
96
+ for (let i = 0; i < target.length; i++) { const value = call(args[0], [target[i], i], depth + 1); if (method === "find" && value) return target[i]; if (method === "filter" && value) result.push(target[i]); if (method === "map") result.push(value); if(method==="flatMap"){if(!Array.isArray(value))fail();result.push(...value);} if(method==="some"&&value)return true;if(method==="every"&&!value)return false; }
97
+ return method === "find" ? undefined : method==="some"?false:method==="every"?true:result;
98
+ }
99
+ if(typeof target==='string'&&target.length<=4000){
100
+ if(['startsWith','endsWith','includes','toLowerCase','toUpperCase','trim','slice'].includes(method)&&args.every(v=>['string','number'].includes(typeof v)))return String.prototype[method].apply(target,args);
101
+ if(method==='split'&&args[0]?.sourceRegex==='query-separator'&&args[1]===1)return target.split(/[?#]/,1);
102
+ if(method==='replace'&&args[0]?.sourceRegex==='trailing-slashes'&&args[1]==='')return target.replace(/\/+$/,'');
103
+ if(method==='replaceAll'&&args.length===2&&typeof args[0]==='string'&&args[0].length&&typeof args[1]==='string'){const result=target.split(args[0]).join(args[1]);if(result.length>4000)fail();return result;}
104
+ if(method==='replace'&&args[0]?.sourceRegex==='first-character'&&args[1]?.[closure]){const first=call(args[1],[target.charAt(0)],depth+1);if(typeof first!=='string')fail();return first+target.slice(1);}
79
105
  }
80
106
  }
81
107
  return call(ev(n.expression), args, depth + 1);
@@ -86,9 +112,11 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
86
112
  tick(); if (!fn?.[closure] || depth > 24 || fn.node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword)) fail();
87
113
  const env = { ...fn.env, [writable]: new Set() };
88
114
  for (let i = 0; i < fn.node.parameters.length; i++) {
89
- const p = fn.node.parameters[i]; if (!ts.isIdentifier(p.name) || p.dotDotDotToken) fail();
90
- env[p.name.text] = args[i] === undefined && p.initializer ? read(p.initializer, fn.module, env, new Set(), depth + 1) : args[i];
91
- env[writable].add(p.name.text);
115
+ const p = fn.node.parameters[i]; if (p.dotDotDotToken) fail();
116
+ const value=args[i] === undefined && p.initializer ? read(p.initializer, fn.module, env, new Set(), depth + 1) : args[i];
117
+ if(ts.isIdentifier(p.name)){env[p.name.text]=value;env[writable].add(p.name.text);}
118
+ else if(ts.isArrayBindingPattern(p.name)&&Array.isArray(value)&&p.name.elements.length<=8){for(const [index,field] of p.name.elements.entries()){if(ts.isOmittedExpression(field))continue;if(!ts.isIdentifier(field.name)||field.dotDotDotToken)fail();env[field.name.text]=value[index]===undefined&&field.initializer?read(field.initializer,fn.module,env,new Set(),depth+1):value[index];env[writable].add(field.name.text);}}
119
+ else fail();
92
120
  }
93
121
  if (!ts.isBlock(fn.node.body)) return read(fn.node.body, fn.module, env, new Set(), depth + 1);
94
122
  const result = statements(fn.node.body.statements, fn.module, env, depth + 1); return result?.value;