@tinkerlandai/indiana-iam 0.2.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.
Files changed (55) hide show
  1. package/README.md +127 -5
  2. package/assets/document-controller.js +18 -1
  3. package/assets/document-runtime.js +189 -1
  4. package/assets/viewer.js +74 -7
  5. package/package.json +6 -13
  6. package/src/analyze.mjs +897 -123
  7. package/src/anchors.mjs +5 -0
  8. package/src/build-options.d.ts +6 -1
  9. package/src/build-runtime.mjs +4 -1
  10. package/src/build-styles.mjs +49 -6
  11. package/src/build.mjs +13 -7
  12. package/src/cli.mjs +10 -4
  13. package/src/component-registry.mjs +229 -0
  14. package/src/dependencies.mjs +2 -2
  15. package/src/dom-payload-browser.mjs +28 -0
  16. package/src/dom-payload-json.mjs +12 -0
  17. package/src/dom-payload.mjs +50 -0
  18. package/src/framework-routes.mjs +44 -89
  19. package/src/generate.mjs +20 -7
  20. package/src/graph-sources.mjs +54 -0
  21. package/src/index.d.ts +13 -1
  22. package/src/index.mjs +1 -1
  23. package/src/integrity.mjs +4 -2
  24. package/src/library-entry.mjs +5 -0
  25. package/src/lucide-source.mjs +18 -0
  26. package/src/next-loader.cjs +7 -1
  27. package/src/next.cjs +5 -0
  28. package/src/next.mjs +4 -3
  29. package/src/node-hashes.mjs +13 -9
  30. package/src/render-index.mjs +6 -4
  31. package/src/route-lazy.mjs +80 -0
  32. package/src/route-tree.mjs +15 -3
  33. package/src/routing.mjs +1 -1
  34. package/src/schema-definition.mjs +8 -1
  35. package/src/source-attributes.mjs +24 -0
  36. package/src/source-charts.mjs +38 -0
  37. package/src/source-collections.mjs +241 -0
  38. package/src/source-entry.mjs +50 -0
  39. package/src/source-environment.mjs +26 -0
  40. package/src/source-media.mjs +21 -0
  41. package/src/source-query-evidence.mjs +64 -0
  42. package/src/source-redirects.mjs +388 -0
  43. package/src/source-resolution.mjs +268 -0
  44. package/src/static-primitives.mjs +48 -0
  45. package/src/static-query.mjs +39 -11
  46. package/src/tab-scenes.mjs +214 -0
  47. package/src/tanstack-routes.mjs +143 -0
  48. package/src/transform.mjs +2 -2
  49. package/src/ui-preview.mjs +225 -0
  50. package/src/ui-state-machine.mjs +76 -0
  51. package/src/ui-structure.mjs +645 -0
  52. package/src/upload.mjs +28 -6
  53. package/src/validate.mjs +19 -4
  54. package/src/vite.d.ts +4 -1
  55. package/src/vite.mjs +25 -10
@@ -0,0 +1,64 @@
1
+ /** Trace returned TanStack query values through local source wrappers, without running a query or module. */
2
+ export function sourceQueryEvidence({
3
+ ts,
4
+ node,
5
+ module,
6
+ locals,
7
+ binding,
8
+ functionFrom,
9
+ imported,
10
+ }) {
11
+ let steps = 0;
12
+ const seen = new Set();
13
+ function visit(n, m, env, depth = 0) {
14
+ if (!n || depth > 20 || ++steps > 1800) return false;
15
+ while (
16
+ ts.isParenthesizedExpression(n) ||
17
+ ts.isAsExpression(n) ||
18
+ ts.isNonNullExpression(n)
19
+ )
20
+ n = n.expression;
21
+ const key = m.file + ":" + n.pos + ":" + n.end;
22
+ if (seen.has(key)) return false;
23
+ seen.add(key);
24
+ if (ts.isIdentifier(n)) {
25
+ if (env?.has(n.text)) return visit(env.get(n.text), m, env, depth + 1);
26
+ const ref = binding(m, n.text);
27
+ return Boolean(ref && visit(ref.node, ref.module, null, depth + 1));
28
+ }
29
+ if (ts.isCallExpression(n)) {
30
+ const primitive = imported(m, n.expression.getText(m.source));
31
+ if (
32
+ primitive?.specifier === "@tanstack/react-query" &&
33
+ ["useQuery", "useInfiniteQuery"].includes(primitive.name)
34
+ )
35
+ return true;
36
+ if (ts.isIdentifier(n.expression)) {
37
+ const ref = binding(m, n.expression.text),
38
+ fn = ref && functionFrom(ref.node);
39
+ if (fn) {
40
+ const next = new Map();
41
+ if (ts.isBlock(fn.body))
42
+ for (const s of fn.body.statements)
43
+ if (ts.isVariableStatement(s))
44
+ for (const d of s.declarationList.declarations)
45
+ if (ts.isIdentifier(d.name))
46
+ next.set(d.name.text, d.initializer);
47
+ const returns = ts.isBlock(fn.body)
48
+ ? fn.body.statements
49
+ .filter((s) => ts.isReturnStatement(s))
50
+ .map((s) => s.expression)
51
+ : [fn.body];
52
+ if (returns.some((r) => visit(r, ref.module, next, depth + 1)))
53
+ return true;
54
+ }
55
+ }
56
+ }
57
+ let found = false;
58
+ ts.forEachChild(n, (c) => {
59
+ if (!found) found = visit(c, m, env, depth + 1);
60
+ });
61
+ return found;
62
+ }
63
+ return visit(node, module, locals);
64
+ }
@@ -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
+ }