@akanjs/language 3.2.0-rc.0 → 3.2.0-rc.2

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/server.js CHANGED
@@ -5,6 +5,239 @@ import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "n
5
5
  import path from "node:path";
6
6
  import { fileURLToPath, pathToFileURL } from "node:url";
7
7
 
8
+ // pkgs/@akanjs/language/guardFacts.ts
9
+ var guardLabelOf = (guard, labels) => {
10
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
11
+ const label = labels[local];
12
+ return label === undefined ? guard : role ? `${role} ${label}` : label;
13
+ };
14
+ var maxLabelArgs = 32;
15
+ var guardRefReaderOf = (ts, ast, unwrap) => {
16
+ const pathOf = (expression) => {
17
+ if (ts.isIdentifier(expression))
18
+ return expression.text;
19
+ if (!ts.isPropertyAccessExpression(expression))
20
+ return null;
21
+ const head = pathOf(expression.expression);
22
+ return head && `${head}.${expression.name.text}`;
23
+ };
24
+ const calleeOf = (call) => pathOf(unwrap(call.expression)) ?? "?";
25
+ const labelOf = (source, call) => {
26
+ const args = call.arguments.map((arg) => arg.getText(source)).join(", ");
27
+ return `${calleeOf(call)}(${args.length <= maxLabelArgs && !args.includes(`
28
+ `) ? args : "…"})`;
29
+ };
30
+ const guardRefOf = (expression, made, member) => {
31
+ const value = unwrap(expression);
32
+ if (!ts.isCallExpression(value))
33
+ return pathOf(value) ?? ast.expressionName(value);
34
+ const prefix = `${member}:${calleeOf(value)}@`;
35
+ const name = `${prefix}${made.filter((other) => other.name.startsWith(prefix)).length + 1}`;
36
+ made.push({ call: value, name });
37
+ return name;
38
+ };
39
+ const guardsIn = (source, object, made, member) => {
40
+ const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
41
+ if (!guards)
42
+ return [];
43
+ const refOf = (expression) => guardRefOf(expression, made, member);
44
+ if (ts.isArrayLiteralExpression(guards))
45
+ return guards.elements.map(refOf).filter((name) => name !== null);
46
+ if (ts.isObjectLiteralExpression(guards))
47
+ return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
48
+ const value = unwrap(property.initializer);
49
+ const names = ts.isArrayLiteralExpression(value) ? value.elements.map(refOf) : [refOf(value)];
50
+ return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
51
+ });
52
+ const single = refOf(guards);
53
+ return single ? [single] : [];
54
+ };
55
+ return { pathOf, labelOf, guardsIn };
56
+ };
57
+ var guardFactoryReaderOf = (ts, ast, { spanOf, extentOf, unwrap, docOf }, pathOf) => {
58
+ const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
59
+ const scopeOf = (node) => {
60
+ const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
61
+ const value = scope && unwrap(scope);
62
+ return value && ts.isStringLiteralLike(value) ? value.text : null;
63
+ };
64
+ const functionsOf = (source) => {
65
+ const functions = new Map;
66
+ for (const statement of source.statements) {
67
+ if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
68
+ functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
69
+ if (!ts.isVariableStatement(statement))
70
+ continue;
71
+ for (const declaration of statement.declarationList.declarations) {
72
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
73
+ if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
74
+ functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
75
+ }
76
+ }
77
+ return functions;
78
+ };
79
+ const returnedOf = (body, guardNames) => {
80
+ const locals = new Map;
81
+ const pending = [];
82
+ const visit = (node) => {
83
+ if (ts.isClassDeclaration(node) && node.name)
84
+ locals.set(node.name.text, node);
85
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
86
+ locals.set(node.name.text, node.initializer);
87
+ if (ts.isReturnStatement(node) && node.expression)
88
+ pending.push(node.expression);
89
+ if (!ts.isFunctionLike(node) && !ts.isClassLike(node))
90
+ ts.forEachChild(node, visit);
91
+ };
92
+ if (ts.isBlock(body))
93
+ ts.forEachChild(body, visit);
94
+ else
95
+ pending.push(body);
96
+ let guard = null;
97
+ const calls = [];
98
+ const seen = new Set;
99
+ const passed = new Set;
100
+ for (let node = pending.pop();node; node = pending.pop()) {
101
+ if (seen.has(node))
102
+ continue;
103
+ seen.add(node);
104
+ const value = ts.isExpression(node) ? unwrap(node) : node;
105
+ if ((ts.isClassExpression(value) || ts.isClassDeclaration(value)) && implementsGuard(value, guardNames))
106
+ guard ??= value;
107
+ else if (ts.isConditionalExpression(value))
108
+ pending.push(value.whenTrue, value.whenFalse);
109
+ else if (ts.isBinaryExpression(value))
110
+ pending.push(value.left, value.right);
111
+ else if (ts.isIdentifier(value)) {
112
+ const local = locals.get(value.text);
113
+ if (local)
114
+ pending.push(local);
115
+ } else if (ts.isCallExpression(value)) {
116
+ const callee = pathOf(unwrap(value.expression));
117
+ if (callee && !passed.has(node) && !calls.includes(callee))
118
+ calls.push(callee);
119
+ for (const arg of value.arguments)
120
+ passed.add(arg);
121
+ pending.push(...value.arguments);
122
+ }
123
+ }
124
+ return { guard, calls };
125
+ };
126
+ const guardFactoriesOf = (source, guardNames, isImported) => {
127
+ const functions = functionsOf(source);
128
+ const returned = new Map([...functions].map(([name, { body }]) => [name, returnedOf(body, guardNames)]));
129
+ const members = new Set;
130
+ const isFactoryCall = (callee) => members.has(callee) || !functions.has(callee) && isImported(callee);
131
+ for (let isGrowing = true;isGrowing; ) {
132
+ isGrowing = false;
133
+ for (const [name, { guard, calls }] of returned) {
134
+ if (members.has(name) || !guard && !calls.some(isFactoryCall))
135
+ continue;
136
+ members.add(name);
137
+ isGrowing = true;
138
+ }
139
+ }
140
+ const factories = new Map;
141
+ for (const name of members) {
142
+ const found = functions.get(name);
143
+ const made = returned.get(name);
144
+ if (!found || !made)
145
+ continue;
146
+ factories.set(name, {
147
+ name,
148
+ span: spanOf(source, found.name),
149
+ extent: extentOf(source, found.statement),
150
+ scope: made.guard ? scopeOf(made.guard) : null,
151
+ doc: docOf(source, found.statement),
152
+ direct: !!made.guard,
153
+ via: made.calls.filter(isFactoryCall)
154
+ });
155
+ }
156
+ return factories;
157
+ };
158
+ return { implementsGuard, scopeOf, guardFactoriesOf };
159
+ };
160
+ var createGuardFactReader = (ts, ast, base) => {
161
+ const { spanOf, extentOf, unwrap, docOf } = base;
162
+ const { pathOf, labelOf, guardsIn } = guardRefReaderOf(ts, ast, unwrap);
163
+ const { implementsGuard, scopeOf, guardFactoriesOf } = guardFactoryReaderOf(ts, ast, base, pathOf);
164
+ const guardsOf = (source, imports, madeInPlace) => {
165
+ const guardNames = new Set([
166
+ "Guard",
167
+ ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
168
+ ]);
169
+ const imported = new Set(imports.map(({ name }) => name));
170
+ const isImported = (callee) => imported.has(callee.split(".")[0] ?? callee);
171
+ const factories = guardFactoriesOf(source, guardNames, isImported);
172
+ const guards = [];
173
+ const calls = [];
174
+ const makeOf = (made, value, isInPlace = false) => {
175
+ if (ts.isClassExpression(value) && implementsGuard(value, guardNames)) {
176
+ guards.push({ ...made, scope: scopeOf(value), factory: null });
177
+ return;
178
+ }
179
+ const callee = ts.isCallExpression(value) ? pathOf(unwrap(value.expression)) : null;
180
+ const factory = callee && factories.get(callee);
181
+ if (factory)
182
+ guards.push({ ...made, scope: factory.scope, factory: factory.name });
183
+ else if (callee && (isImported(callee) || isInPlace))
184
+ calls.push({ ...made, callee, kind: "call", scope: null });
185
+ };
186
+ for (const statement of source.statements) {
187
+ if (ts.isClassDeclaration(statement) && statement.name) {
188
+ const declared = {
189
+ name: statement.name.text,
190
+ span: spanOf(source, statement.name),
191
+ extent: extentOf(source, statement),
192
+ scope: scopeOf(statement),
193
+ doc: docOf(source, statement)
194
+ };
195
+ const base = statement.heritageClauses?.find(({ token }) => token === ts.SyntaxKind.ExtendsKeyword)?.types[0];
196
+ const callee = base && pathOf(base.expression);
197
+ if (implementsGuard(statement, guardNames))
198
+ guards.push({ ...declared, factory: null });
199
+ else if (callee)
200
+ calls.push({ ...declared, callee, kind: "extends" });
201
+ }
202
+ if (!ts.isVariableStatement(statement))
203
+ continue;
204
+ for (const declaration of statement.declarationList.declarations) {
205
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
206
+ if (!initializer || !ts.isIdentifier(declaration.name))
207
+ continue;
208
+ const holder = declaration.name.text;
209
+ makeOf({
210
+ name: holder,
211
+ span: spanOf(source, declaration.name),
212
+ extent: extentOf(source, statement),
213
+ doc: docOf(source, statement)
214
+ }, initializer);
215
+ if (!ts.isObjectLiteralExpression(initializer))
216
+ continue;
217
+ for (const property of initializer.properties) {
218
+ const key = ts.isPropertyAssignment(property) ? ast.nodeName(property.name) : null;
219
+ if (key && ts.isPropertyAssignment(property))
220
+ makeOf({
221
+ name: `${holder}.${key}`,
222
+ span: spanOf(source, property.name),
223
+ extent: extentOf(source, property),
224
+ doc: docOf(source, property)
225
+ }, unwrap(property.initializer));
226
+ }
227
+ }
228
+ }
229
+ for (const { call, name } of madeInPlace)
230
+ makeOf({ name, span: spanOf(source, call), extent: extentOf(source, call), doc: null }, call, true);
231
+ const guardLabels = Object.fromEntries(madeInPlace.map(({ call, name }) => [name, labelOf(source, call)]));
232
+ return { guards, guardFactories: [...factories.values()], guardCalls: calls, guardLabels };
233
+ };
234
+ return { guardsIn, guardsOf };
235
+ };
236
+
237
+ // pkgs/@akanjs/language/escapeRegExp.ts
238
+ var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
239
+ var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
240
+
8
241
  // pkgs/@akanjs/language/moduleFacts.ts
9
242
  var testFilePattern = /\.(?:test|spec)\.tsx?$/;
10
243
  var moduleCallees = new Set([
@@ -73,21 +306,6 @@ var createFactReader = (ts, ast) => {
73
306
  const value = unwrap(expression);
74
307
  return ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) ? `${value.expression.text}.${value.name.text}` : ast.expressionName(value);
75
308
  };
76
- const guardsIn = (object) => {
77
- const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
78
- if (!guards)
79
- return [];
80
- if (ts.isArrayLiteralExpression(guards))
81
- return guards.elements.map(guardNameOf).filter((name) => name !== null);
82
- if (ts.isObjectLiteralExpression(guards))
83
- return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
84
- const value = unwrap(property.initializer);
85
- const names = ts.isArrayLiteralExpression(value) ? value.elements.map(guardNameOf) : [guardNameOf(value)];
86
- return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
87
- });
88
- const single = guardNameOf(guards);
89
- return single ? [single] : [];
90
- };
91
309
  const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
92
310
  const chainOf = (initializer) => {
93
311
  let root = unwrap(initializer);
@@ -104,7 +322,7 @@ var createFactReader = (ts, ast) => {
104
322
  const node = keyName(property);
105
323
  return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
106
324
  });
107
- const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
325
+ const sliceKeysOf = (source, builder, made, model) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
108
326
  if (!ts.isPropertyAssignment(property))
109
327
  return [];
110
328
  const name = ast.nodeName(property.name);
@@ -116,7 +334,7 @@ var createFactReader = (ts, ast) => {
116
334
  name,
117
335
  span: spanOf(source, property.name),
118
336
  extent: extentOf(source, property),
119
- guards: root ? guardsIn(optionObjectOf(root)) : [],
337
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
120
338
  live: methods.includes("live"),
121
339
  argCount: methods.filter((method) => argMethods.has(method)).length
122
340
  }
@@ -222,7 +440,7 @@ var createFactReader = (ts, ast) => {
222
440
  return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
223
441
  });
224
442
  };
225
- const endpointKeysOf = (source, builder) => {
443
+ const endpointKeysOf = (source, builder, made, model) => {
226
444
  const verbOf = verbReaderOf(builder?.parameters[0]);
227
445
  return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
228
446
  if (!ts.isPropertyAssignment(property))
@@ -237,7 +455,7 @@ var createFactReader = (ts, ast) => {
237
455
  span: spanOf(source, property.name),
238
456
  extent: extentOf(source, property),
239
457
  verb: root ? verbOf(root.expression) : null,
240
- guards: root ? guardsIn(optionObjectOf(root)) : [],
458
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
241
459
  argCount: methods.filter((method) => argMethods.has(method)).length
242
460
  }
243
461
  ];
@@ -304,22 +522,6 @@ var createFactReader = (ts, ast) => {
304
522
  })) : []
305
523
  ];
306
524
  };
307
- const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
308
- const guardClassIn = (node, guardNames) => {
309
- if ((ts.isClassExpression(node) || ts.isClassDeclaration(node)) && implementsGuard(node, guardNames))
310
- return node;
311
- return ts.forEachChild(node, (child) => guardClassIn(child, guardNames)) ?? null;
312
- };
313
- const callsIn = (node, names) => {
314
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && names.has(node.expression.text))
315
- return node.expression.text;
316
- return ts.forEachChild(node, (child) => callsIn(child, names)) ?? null;
317
- };
318
- const scopeOf = (node) => {
319
- const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
320
- const value = scope && unwrap(scope);
321
- return value && ts.isStringLiteralLike(value) ? value.text : null;
322
- };
323
525
  const docOf = (source, node) => {
324
526
  const { text } = source;
325
527
  const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
@@ -335,108 +537,20 @@ var createFactReader = (ts, ast) => {
335
537
  `)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
336
538
  return lines.length ? lines.join(" ") : null;
337
539
  };
338
- const functionsOf = (source) => {
339
- const functions = new Map;
340
- for (const statement of source.statements) {
341
- if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
342
- functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
343
- if (!ts.isVariableStatement(statement))
344
- continue;
345
- for (const declaration of statement.declarationList.declarations) {
346
- const initializer = declaration.initializer && unwrap(declaration.initializer);
347
- if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
348
- functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
349
- }
350
- }
351
- return functions;
352
- };
353
- const guardFactoriesOf = (source, guardNames) => {
354
- const functions = functionsOf(source);
355
- const factories = new Map;
356
- const factoryOf = (name, scope, via) => {
357
- const found = functions.get(name);
358
- if (!found)
359
- return;
360
- factories.set(name, {
361
- name,
362
- span: spanOf(source, found.name),
363
- extent: extentOf(source, found.statement),
364
- scope,
365
- doc: docOf(source, found.statement),
366
- via
367
- });
368
- };
369
- for (const [name, { body }] of functions) {
370
- const guard = guardClassIn(body, guardNames);
371
- if (guard)
372
- factoryOf(name, scopeOf(guard), null);
373
- }
374
- for (let isGrowing = true;isGrowing; ) {
375
- isGrowing = false;
376
- for (const [name, { body }] of functions) {
377
- const called = factories.has(name) ? null : callsIn(body, new Set(factories.keys()));
378
- if (!called)
379
- continue;
380
- factoryOf(name, factories.get(called)?.scope ?? null, called);
381
- isGrowing = true;
382
- }
383
- }
384
- return factories;
385
- };
386
- const guardsOf = (source, imports) => {
387
- const guardNames = new Set([
388
- "Guard",
389
- ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
390
- ]);
391
- const imported = new Set(imports.map(({ name }) => name));
392
- const factories = guardFactoriesOf(source, guardNames);
393
- const guards = [];
394
- const calls = [];
395
- for (const statement of source.statements) {
396
- if (ts.isClassDeclaration(statement) && statement.name && implementsGuard(statement, guardNames))
397
- guards.push({
398
- name: statement.name.text,
399
- span: spanOf(source, statement.name),
400
- extent: extentOf(source, statement),
401
- scope: scopeOf(statement),
402
- doc: docOf(source, statement),
403
- factory: null
404
- });
405
- if (!ts.isVariableStatement(statement))
406
- continue;
407
- for (const declaration of statement.declarationList.declarations) {
408
- const initializer = declaration.initializer && unwrap(declaration.initializer);
409
- if (!initializer || !ts.isIdentifier(declaration.name))
410
- continue;
411
- const made = {
412
- name: declaration.name.text,
413
- span: spanOf(source, declaration.name),
414
- extent: extentOf(source, statement),
415
- doc: docOf(source, statement)
416
- };
417
- if (ts.isClassExpression(initializer) && implementsGuard(initializer, guardNames))
418
- guards.push({ ...made, scope: scopeOf(initializer), factory: null });
419
- if (!ts.isCallExpression(initializer) || !ts.isIdentifier(initializer.expression))
420
- continue;
421
- const callee = initializer.expression.text;
422
- const factory = factories.get(callee);
423
- if (factory)
424
- guards.push({ ...made, scope: factory.scope, factory: callee });
425
- else if (imported.has(callee))
426
- calls.push({ ...made, callee });
427
- }
428
- }
429
- return { guards, guardFactories: [...factories.values()], guardCalls: calls };
430
- };
540
+ const { guardsIn, guardsOf } = createGuardFactReader(ts, ast, { spanOf, extentOf, unwrap, docOf });
431
541
  const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
432
542
  const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
433
543
  const read = (source, owner) => {
434
544
  const imports = importsIn(source);
545
+ const madeInPlace = [];
435
546
  const facts = {
436
547
  fileName: source.fileName,
437
548
  owner,
438
549
  imports,
439
- ...guardsOf(source, imports),
550
+ guards: [],
551
+ guardFactories: [],
552
+ guardCalls: [],
553
+ guardLabels: {},
440
554
  constants: [],
441
555
  enums: [],
442
556
  filters: [],
@@ -454,64 +568,496 @@ var createFactReader = (ts, ast) => {
454
568
  const heritage = ast.heritageCallNamed(statement, moduleCallees);
455
569
  if (!heritage)
456
570
  continue;
457
- const { callee, call } = heritage;
458
- const name = { name: statement.name.text, span: spanOf(source, statement.name) };
459
- const builder = builderOf(call);
460
- if (callee === "via")
461
- facts.constants.push(constantOf(source, name, call));
462
- else if (callee === "enumOf")
463
- facts.enums.push({ ...name, values: enumValuesOf(call) });
464
- else if (callee === "from")
465
- facts.filters.push({ ...name, queries: filterQueriesOf(source, builder) });
466
- else if (callee === "into")
467
- facts.models.push({ ...name, model: name.name.endsWith("Model") ? lowerFirst(name.name.slice(0, -5)) : null });
468
- else if (callee === "serve")
469
- facts.services.push({
470
- ...name,
471
- model: moduleRefOf(call.arguments[0], "db"),
472
- methods: methodsOf(source, statement),
473
- plugs: plugsOf(source, builder)
474
- });
475
- else if (callee === "slice")
476
- facts.slices.push({
477
- ...name,
478
- model: moduleRefOf(call.arguments[0], "srv"),
479
- guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
480
- keys: sliceKeysOf(source, builder)
481
- });
482
- else if (callee === "internal")
483
- facts.internals.push({
484
- ...name,
485
- model: moduleRefOf(call.arguments[0], "srv"),
486
- keys: internalKeysOf(source, builder)
487
- });
488
- else if (callee === "adapt" || callee === "dangerouslyAdapt") {
489
- const refName = call.arguments[0] && unwrap(call.arguments[0]);
490
- facts.adaptors.push({
491
- ...name,
492
- extent: extentOf(source, statement),
493
- refName: refName && ts.isStringLiteralLike(refName) ? refName.text : null,
494
- plugs: plugsOf(source, builder),
495
- doc: docOf(source, statement)
496
- });
497
- } else if (callee === "endpoint")
498
- facts.endpoints.push({
499
- ...name,
500
- model: moduleRefOf(call.arguments[0], "srv"),
501
- keys: endpointKeysOf(source, builder)
502
- });
503
- else if (callee === "store")
504
- facts.stores.push({
505
- ...name,
506
- model: moduleRefOf(call.arguments[0], "sig"),
507
- stateKeys: objectKeys(source, returnedObject(builder)),
508
- actions: methodsOf(source, statement)
509
- });
571
+ const { callee, call } = heritage;
572
+ const name = { name: statement.name.text, span: spanOf(source, statement.name) };
573
+ const builder = builderOf(call);
574
+ if (callee === "via")
575
+ facts.constants.push(constantOf(source, name, call));
576
+ else if (callee === "enumOf")
577
+ facts.enums.push({ ...name, values: enumValuesOf(call) });
578
+ else if (callee === "from")
579
+ facts.filters.push({ ...name, queries: filterQueriesOf(source, builder) });
580
+ else if (callee === "into")
581
+ facts.models.push({ ...name, model: name.name.endsWith("Model") ? lowerFirst(name.name.slice(0, -5)) : null });
582
+ else if (callee === "serve")
583
+ facts.services.push({
584
+ ...name,
585
+ model: moduleRefOf(call.arguments[0], "db"),
586
+ methods: methodsOf(source, statement),
587
+ plugs: plugsOf(source, builder)
588
+ });
589
+ else if (callee === "slice") {
590
+ const model = moduleRefOf(call.arguments[0], "srv");
591
+ const member = model ?? name.name;
592
+ facts.slices.push({
593
+ ...name,
594
+ model,
595
+ guards: guardsIn(source, call.arguments.find(ts.isObjectLiteralExpression), madeInPlace, member),
596
+ keys: sliceKeysOf(source, builder, madeInPlace, member)
597
+ });
598
+ } else if (callee === "internal")
599
+ facts.internals.push({
600
+ ...name,
601
+ model: moduleRefOf(call.arguments[0], "srv"),
602
+ keys: internalKeysOf(source, builder)
603
+ });
604
+ else if (callee === "adapt" || callee === "dangerouslyAdapt") {
605
+ const refName = call.arguments[0] && unwrap(call.arguments[0]);
606
+ facts.adaptors.push({
607
+ ...name,
608
+ extent: extentOf(source, statement),
609
+ refName: refName && ts.isStringLiteralLike(refName) ? refName.text : null,
610
+ plugs: plugsOf(source, builder),
611
+ doc: docOf(source, statement)
612
+ });
613
+ } else if (callee === "endpoint") {
614
+ const model = moduleRefOf(call.arguments[0], "srv");
615
+ facts.endpoints.push({
616
+ ...name,
617
+ model,
618
+ keys: endpointKeysOf(source, builder, madeInPlace, model ?? name.name)
619
+ });
620
+ } else if (callee === "store")
621
+ facts.stores.push({
622
+ ...name,
623
+ model: moduleRefOf(call.arguments[0], "sig"),
624
+ stateKeys: objectKeys(source, returnedObject(builder)),
625
+ actions: methodsOf(source, statement)
626
+ });
627
+ }
628
+ return { ...facts, ...guardsOf(source, imports, madeInPlace) };
629
+ };
630
+ return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
631
+ };
632
+
633
+ // pkgs/@akanjs/language/guardResolver.ts
634
+ var frameworkOwner = "akanjs";
635
+ var frameworkPattern = /^akanjs(?:\/|$)/;
636
+ var aliasPattern = /^@(apps|libs)\/([^/]+)/;
637
+ var maxMapDepth = 4;
638
+ var starExportPattern = /export\s*\*\s*from\s*["']([^"']+)["']/g;
639
+ var declaringPatternOf = (name, isAnyBinding) => {
640
+ const escaped = escapeRegExp(name);
641
+ const braced = "\\{(?:[^}]*?[\\s,])?";
642
+ const leads = [
643
+ "function\\s*\\*?\\s*",
644
+ "(?:const|let|var)\\s+",
645
+ `export\\s*${braced}`,
646
+ ...isAnyBinding ? ["class\\s+", `(?:const|let|var)\\s*${braced}`] : []
647
+ ];
648
+ return new RegExp(`(?:${leads.join("|")})${escaped}(?![\\w$])|,\\s*${escaped}\\s*(?::[^=;,]+)?=(?![=>])`);
649
+ };
650
+
651
+ class GuardResolver {
652
+ #source;
653
+ #routes;
654
+ #guards = new Map;
655
+ #madeInPlace = new Map;
656
+ #mapEntries = new Map;
657
+ #reexports = new Map;
658
+ #declarations = new Map;
659
+ #starExports = new Map;
660
+ #ownerFiles = null;
661
+ #modules = null;
662
+ constructor(source, routes) {
663
+ this.#source = source;
664
+ this.#routes = routes;
665
+ }
666
+ find(local, site) {
667
+ return this.#findGuard(local, site, 0);
668
+ }
669
+ madeInPlace(fileName, name) {
670
+ return this.#madeInPlace.get(`${fileName}
671
+ ${name}`);
672
+ }
673
+ declare(facts) {
674
+ const factories = new Map;
675
+ const declaredFactories = [];
676
+ for (const file of facts)
677
+ for (const fact of file.guardFactories) {
678
+ const factory = {
679
+ fact,
680
+ owner: file.owner,
681
+ site: file,
682
+ made: [],
683
+ isReal: fact.direct,
684
+ scope: fact.scope,
685
+ targets: []
686
+ };
687
+ factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
688
+ declaredFactories.push(factory);
689
+ }
690
+ for (const factory of declaredFactories)
691
+ factory.targets = factory.fact.via.map((callee) => this.#factoryAt(factories, callee, factory.site)).filter((target) => target !== undefined);
692
+ for (let isGrowing = true;isGrowing; ) {
693
+ isGrowing = false;
694
+ for (const factory of declaredFactories) {
695
+ const target = factory.targets.find((other) => other.isReal);
696
+ if (factory.isReal || !target)
697
+ continue;
698
+ factory.isReal = true;
699
+ factory.scope ??= target.scope;
700
+ isGrowing = true;
701
+ }
702
+ }
703
+ const partsOf = (factory, seen = new Set) => {
704
+ if (seen.has(factory))
705
+ return [];
706
+ seen.add(factory);
707
+ return [
708
+ { fileName: factory.site.fileName, extent: factory.fact.extent },
709
+ ...factory.targets.filter((target) => target.isReal).flatMap((target) => partsOf(target, seen))
710
+ ];
711
+ };
712
+ const declare = (file, made, scope, parts) => {
713
+ const { name, span, extent, doc } = made;
714
+ const label = file.guardLabels[name];
715
+ const own = { fileName: file.fileName, extent };
716
+ const guard = { name, owner: file.owner, span, scope, doc, parts: [own, ...parts], file: null, label };
717
+ if (name.includes("@"))
718
+ this.#madeInPlace.set(`${file.fileName}
719
+ ${name}`, guard);
720
+ else
721
+ this.#guards.set(name, [...this.#guards.get(name) ?? [], guard]);
722
+ return guard;
723
+ };
724
+ const extending = [];
725
+ for (const file of facts) {
726
+ for (const made of file.guards) {
727
+ const factory = made.factory ? factories.get(made.factory)?.find((other) => other.site.fileName === file.fileName) : undefined;
728
+ if (!made.factory)
729
+ declare(file, made, made.scope, []);
730
+ else if (factory?.isReal)
731
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
732
+ }
733
+ for (const made of file.guardCalls) {
734
+ if (made.kind === "extends") {
735
+ extending.push([file, made]);
736
+ continue;
737
+ }
738
+ const factory = this.#factoryAt(factories, made.callee, file);
739
+ if (factory?.isReal)
740
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
741
+ }
742
+ }
743
+ for (let isGrowing = true;isGrowing; ) {
744
+ isGrowing = false;
745
+ for (const [idx, entry] of extending.entries()) {
746
+ const [file, made] = entry;
747
+ const found = this.#findGuard(made.callee, file, 0, true);
748
+ if (!("guard" in found))
749
+ continue;
750
+ declare(file, made, made.scope ?? found.guard.scope, found.guard.parts);
751
+ extending.splice(idx, 1);
752
+ isGrowing = true;
753
+ break;
754
+ }
755
+ }
756
+ for (const { fact, made, isReal } of declaredFactories)
757
+ if (isReal && made.every((guard) => !guard.doc))
758
+ for (const guard of made)
759
+ guard.doc = fact.doc;
760
+ for (const list of this.#guards.values())
761
+ for (const guard of list)
762
+ if (list.some((other) => other !== guard && other.owner === guard.owner))
763
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
764
+ for (const guard of this.#madeInPlace.values())
765
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
766
+ return [...[...this.#guards.values()].flat(), ...this.#madeInPlace.values()];
767
+ }
768
+ #factoryAt(factories, callee, site, depth = 0) {
769
+ const [head = callee, member] = callee.split(".");
770
+ const imported = site.imports.find((item) => item.name === head);
771
+ if (!imported)
772
+ return member ? undefined : factories.get(callee)?.find((other) => other.site.fileName === site.fileName);
773
+ if (member && imported.imported !== "*")
774
+ return;
775
+ const from = this.ownerOfSpecifier(site.fileName, imported.from);
776
+ if (from === undefined || from === frameworkOwner)
777
+ return;
778
+ const name = member ?? (imported.imported === "default" || imported.imported === "*" ? head : imported.imported);
779
+ const candidates = factories.get(name) ?? [];
780
+ const near = this.pathOfSpecifier(site.fileName, imported.from);
781
+ const own = this.#nearest(candidates.filter((other) => other.owner === from), ({ site }) => site.fileName, near);
782
+ if (own)
783
+ return own;
784
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
785
+ if (reexport)
786
+ return this.#factoryAt(factories, reexport.local, reexport.site, depth + 1);
787
+ return from && near ? this.#throughStar(candidates, from, name, near) : undefined;
788
+ }
789
+ #findGuard(local, site, depth, isBase = false) {
790
+ const segments = local.split(".");
791
+ const head = segments[0] ?? local;
792
+ const imported = site.imports.find((item) => item.name === head);
793
+ const from = imported ? this.ownerOfSpecifier(site.fileName, imported.from) : undefined;
794
+ if (imported && from === undefined)
795
+ return { external: true };
796
+ const near = imported ? this.pathOfSpecifier(site.fileName, imported.from) : this.#source.relative(site.fileName).replace(/\.tsx?$/, "");
797
+ const named = imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : null;
798
+ const path = imported?.imported === "*" ? segments.slice(1) : [named ?? head, ...segments.slice(1)];
799
+ const holder = imported ? from ?? null : site.owner;
800
+ const [objectName, member] = path;
801
+ if (path.length === 2 && objectName && member && holder !== frameworkOwner && depth < maxMapDepth) {
802
+ const made = this.#nearest((this.#guards.get(`${objectName}.${member}`) ?? []).filter((guard) => guard.owner === holder), ({ span }) => span.fileName, near);
803
+ if (made)
804
+ return { guard: made };
805
+ for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
806
+ const found = this.#findGuard(entry.name, entry.site, depth + 1, isBase);
807
+ if ("guard" in found)
808
+ return found;
809
+ }
810
+ }
811
+ const name = path[path.length - 1] ?? head;
812
+ if (from === frameworkOwner)
813
+ return { name, from };
814
+ const declared = this.#guards.get(name) ?? [];
815
+ const own = this.#nearest(declared.filter((other) => other.owner === (imported ? from : site.owner)), ({ span }) => span.fileName, near);
816
+ if (own)
817
+ return { guard: own };
818
+ if (!imported) {
819
+ const visible = isBase ? undefined : declared.find((other) => this.visible(other.owner, site.owner));
820
+ return visible ? { guard: visible } : { name, from };
821
+ }
822
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
823
+ if (reexport)
824
+ return this.#findGuard(reexport.local, reexport.site, depth + 1, isBase);
825
+ const reached = from && near ? this.#throughStar(declared, from, name, near) : undefined;
826
+ return reached ? { guard: reached } : { name, from };
827
+ }
828
+ reexportOf(owner, name, near) {
829
+ const key = `${owner}
830
+ ${name}
831
+ ${near ?? ""}`;
832
+ const cached = this.#reexports.get(key);
833
+ if (cached !== undefined)
834
+ return cached;
835
+ let found = null;
836
+ const exporting = new RegExp(`export\\s*\\{(?:[^}]*?[\\s,])?${escapeRegExp(name)}(?![\\w$])`);
837
+ for (const fileName of this.#filesOf(owner, name, near)) {
838
+ const text = this.#source.readText(fileName);
839
+ if (text === undefined || !exporting.test(text))
840
+ continue;
841
+ const reexport = this.#reexportIn(this.#source.parse(fileName, text), name);
842
+ if (!reexport)
843
+ continue;
844
+ const site = { fileName, owner, imports: [] };
845
+ found = reexport.from ? {
846
+ local: reexport.name,
847
+ site: { ...site, imports: [{ ...reexport, imported: reexport.name, from: reexport.from }] }
848
+ } : { local: reexport.name, site };
849
+ break;
850
+ }
851
+ this.#reexports.set(key, found);
852
+ return found;
853
+ }
854
+ #reexportIn(source, name) {
855
+ const { ts, reader } = this.#source;
856
+ for (const statement of source.statements) {
857
+ if (!ts.isExportDeclaration(statement) || statement.isTypeOnly)
858
+ continue;
859
+ const { exportClause, moduleSpecifier } = statement;
860
+ if (!exportClause || !ts.isNamedExports(exportClause))
861
+ continue;
862
+ const element = exportClause.elements.find((item) => !item.isTypeOnly && item.name.text === name);
863
+ if (!element)
864
+ continue;
865
+ const original = (element.propertyName ?? element.name).text;
866
+ if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier))
867
+ return { name: original, from: moduleSpecifier.text };
868
+ const imported = reader.importsIn(source).find((item) => item.name === original);
869
+ if (!imported)
870
+ return { name: original, from: null };
871
+ return imported.imported === "default" || imported.imported === "*" ? null : { name: imported.imported, from: imported.from };
872
+ }
873
+ return null;
874
+ }
875
+ #filesOf(owner, name, near) {
876
+ const isNear = (fileName) => {
877
+ const relative = this.#source.relative(fileName);
878
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
879
+ };
880
+ const candidates = this.filesIn(owner).filter(([, words]) => words.includes(`
881
+ ${name}
882
+ `)).map(([fileName]) => fileName);
883
+ return [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))];
884
+ }
885
+ #mapEntriesOf(objectName, member, fileName, owner) {
886
+ const key = `${objectName}
887
+ ${member}
888
+ ${fileName ?? ""}
889
+ ${owner ?? ""}`;
890
+ const cached = this.#mapEntries.get(key);
891
+ if (cached)
892
+ return cached;
893
+ const { ts, reader } = this.#source;
894
+ const entries = [];
895
+ const fileNames = [
896
+ ...fileName ? [fileName] : [],
897
+ ...(owner === null ? [] : this.filesIn(owner)).filter(([other, words]) => other !== fileName && words.includes(`
898
+ ${objectName}
899
+ `) && words.includes(`
900
+ ${member}
901
+ `)).map(([other]) => other)
902
+ ];
903
+ for (const holder of fileNames) {
904
+ const text = this.#source.readText(holder);
905
+ if (text === undefined)
906
+ continue;
907
+ const source = this.#source.parse(holder, text);
908
+ const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
909
+ for (const statement of source.statements) {
910
+ if (!ts.isVariableStatement(statement))
911
+ continue;
912
+ const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
913
+ const object = map && reader.unwrap(map);
914
+ if (!object || !ts.isObjectLiteralExpression(object))
915
+ continue;
916
+ for (const property of object.properties) {
917
+ if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
918
+ entries.push({ name: member, site });
919
+ if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
920
+ continue;
921
+ const value = reader.unwrap(property.initializer);
922
+ if (ts.isIdentifier(value))
923
+ entries.push({ name: value.text, site });
924
+ else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
925
+ entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
926
+ }
927
+ }
928
+ }
929
+ this.#mapEntries.set(key, entries);
930
+ return entries;
931
+ }
932
+ declarationOf(owner, name, near, depth = 0) {
933
+ const key = `${owner}
934
+ ${name}
935
+ ${near ?? ""}`;
936
+ const cached = this.#declarations.get(key);
937
+ if (cached !== undefined)
938
+ return cached;
939
+ this.#declarations.set(key, null);
940
+ const declaring = declaringPatternOf(name, false);
941
+ let found = null;
942
+ for (const fileName of this.#filesOf(owner, name, near)) {
943
+ const text = this.#source.readText(fileName);
944
+ if (text === undefined || !declaring.test(text))
945
+ continue;
946
+ const source = this.#source.parse(fileName, text);
947
+ const declared = this.#routes.declarationIn(source, name);
948
+ if (declared) {
949
+ found = { source, ...declared, name };
950
+ break;
951
+ }
952
+ const reexport = this.#reexportIn(source, name);
953
+ if (!reexport)
954
+ continue;
955
+ if (!reexport.from) {
956
+ const local = this.#routes.declarationIn(source, reexport.name);
957
+ found = local ? { source, ...local, name: reexport.name } : null;
958
+ break;
959
+ }
960
+ const next = this.ownerOfSpecifier(fileName, reexport.from);
961
+ if (next && next !== frameworkOwner && depth < maxMapDepth)
962
+ found = this.declarationOf(next, reexport.name, this.pathOfSpecifier(fileName, reexport.from), depth + 1);
963
+ break;
510
964
  }
511
- return facts;
512
- };
513
- return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
514
- };
965
+ this.#declarations.set(key, found);
966
+ return found;
967
+ }
968
+ #throughStar(declared, owner, name, near) {
969
+ if (!declared.length)
970
+ return;
971
+ const { owners, isOpaque } = this.#starExportsOf(owner, near);
972
+ const reached = declared.filter((other) => other.owner !== null && owners.includes(other.owner));
973
+ return reached.length === 1 && !isOpaque && !this.declares(owner, name) ? reached[0] : undefined;
974
+ }
975
+ #starExportsOf(owner, near) {
976
+ const cached = this.#starExports.get(near);
977
+ if (cached)
978
+ return cached;
979
+ const found = { owners: [], isOpaque: false };
980
+ this.#starExports.set(near, found);
981
+ const paths = [near];
982
+ for (let idx = 0;idx < paths.length && !found.isOpaque; idx++)
983
+ for (const fileName of this.#modulesAt(paths[idx] ?? "")) {
984
+ for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? []) {
985
+ const target = this.ownerOfSpecifier(fileName, specifier);
986
+ const path = this.pathOfSpecifier(fileName, specifier);
987
+ if (target === undefined || target === frameworkOwner || path === null)
988
+ found.isOpaque = true;
989
+ else if (!paths.includes(path))
990
+ paths.push(path);
991
+ if (target && target !== frameworkOwner && target !== owner && !found.owners.includes(target))
992
+ found.owners.push(target);
993
+ }
994
+ }
995
+ return found;
996
+ }
997
+ #modulesAt(path) {
998
+ if (!this.#modules) {
999
+ this.#modules = new Map;
1000
+ for (const [fileName] of this.#source.files) {
1001
+ const module = this.#source.relative(fileName).replace(/\.tsx?$/, "");
1002
+ for (const key of module.endsWith("/index") ? [module, module.slice(0, -6)] : [module])
1003
+ this.#modules.set(key, [...this.#modules.get(key) ?? [], fileName]);
1004
+ }
1005
+ }
1006
+ return this.#modules.get(path) ?? [];
1007
+ }
1008
+ declares(owner, name) {
1009
+ const declaring = declaringPatternOf(name, true);
1010
+ return this.#filesOf(owner, name, null).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
1011
+ }
1012
+ filesIn(owner) {
1013
+ if (!this.#ownerFiles) {
1014
+ this.#ownerFiles = new Map;
1015
+ for (const entry of this.#source.files) {
1016
+ const fileOwner = testFilePattern.test(entry[0]) ? null : this.#source.ownerOf(entry[0]);
1017
+ const files = fileOwner === null ? null : this.#ownerFiles.get(fileOwner);
1018
+ if (files)
1019
+ files.push(entry);
1020
+ else if (fileOwner !== null)
1021
+ this.#ownerFiles.set(fileOwner, [entry]);
1022
+ }
1023
+ }
1024
+ return this.#ownerFiles.get(owner) ?? [];
1025
+ }
1026
+ #nearest(items, fileOf, near) {
1027
+ const isNear = (fileName) => {
1028
+ const relative = this.#source.relative(fileName);
1029
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1030
+ };
1031
+ return items.find((item) => isNear(fileOf(item))) ?? items[0];
1032
+ }
1033
+ inOwner(fileName, owner) {
1034
+ const relative = this.#source.relative(fileName);
1035
+ return owner && relative.startsWith(`${owner}/`) ? relative.slice(owner.length + 1) : relative;
1036
+ }
1037
+ ownerOfSpecifier(fileName, specifier) {
1038
+ if (frameworkPattern.test(specifier))
1039
+ return frameworkOwner;
1040
+ const path = this.pathOfSpecifier(fileName, specifier);
1041
+ return path === null ? undefined : ownerOf(path) ?? undefined;
1042
+ }
1043
+ pathOfSpecifier(fileName, specifier) {
1044
+ const alias = specifier.match(aliasPattern);
1045
+ if (alias)
1046
+ return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1047
+ if (!specifier.startsWith("."))
1048
+ return null;
1049
+ const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1050
+ for (const segment of specifier.split("/"))
1051
+ if (segment === "..")
1052
+ segments.pop();
1053
+ else if (segment !== ".")
1054
+ segments.push(segment);
1055
+ return segments.join("/");
1056
+ }
1057
+ visible(target, from) {
1058
+ return !from || !target || target === from || target.startsWith("libs/");
1059
+ }
1060
+ }
515
1061
 
516
1062
  // pkgs/akanjs/common/trimSlashes.ts
517
1063
  var trimTrailingSlashes = (value) => {
@@ -660,24 +1206,23 @@ rootLayout
660
1206
  }
661
1207
  declarationIn(source, name) {
662
1208
  const ts = this.#ts;
1209
+ const overloads = source.statements.filter((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === name);
1210
+ const [first] = overloads;
1211
+ const last = overloads.at(-1);
1212
+ if (first?.name && last)
1213
+ return { statement: first, declared: first.name, extent: { start: first.getStart(source), end: last.getEnd() } };
663
1214
  for (const statement of source.statements) {
664
- if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
665
- return { statement, declared: statement.name, initializer: null };
666
1215
  if (!ts.isVariableStatement(statement))
667
1216
  continue;
668
1217
  const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
669
1218
  if (declaration)
670
- return { statement, declared: declaration.name, initializer: declaration.initializer ?? null };
1219
+ return { statement, declared: declaration.name, extent: this.#reader.extentOf(source, statement) };
671
1220
  }
672
1221
  return null;
673
1222
  }
674
1223
  #guardOf(source, exported) {
675
1224
  const ts = this.#ts;
676
1225
  let current = ts.isExportAssignment(exported) ? this.#reader.unwrap(exported.expression) : null;
677
- if (current && ts.isIdentifier(current)) {
678
- const initializer = this.declarationIn(source, current.text)?.initializer;
679
- current = initializer ? this.#reader.unwrap(initializer) : null;
680
- }
681
1226
  while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
682
1227
  const { name, expression } = current.expression;
683
1228
  const [stage] = current.arguments;
@@ -689,9 +1234,20 @@ rootLayout
689
1234
  }
690
1235
  #callsIn(source, stage) {
691
1236
  const ts = this.#ts;
692
- const imported = new Set(this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from)).map(({ name }) => name));
1237
+ const imports = this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from));
1238
+ const imported = new Set(imports.filter(({ imported }) => imported !== "*").map(({ name }) => name));
1239
+ const namespaces = new Set(imports.filter(({ imported }) => imported === "*").map(({ name }) => name));
693
1240
  const calls = new Map;
694
- const callOf = (name) => {
1241
+ const callOf = (callee, shadowed) => {
1242
+ if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text) && !shadowed.has(callee.expression.text)) {
1243
+ const name = `${callee.expression.text}.${callee.name.text}`;
1244
+ if (!calls.has(name))
1245
+ calls.set(name, { name, local: null });
1246
+ return true;
1247
+ }
1248
+ if (!ts.isIdentifier(callee) || shadowed.has(callee.text))
1249
+ return false;
1250
+ const name = callee.text;
695
1251
  if (calls.has(name))
696
1252
  return true;
697
1253
  const declared = this.declarationIn(source, name);
@@ -701,23 +1257,71 @@ rootLayout
701
1257
  name,
702
1258
  local: declared && {
703
1259
  span: this.#reader.spanOf(source, declared.declared),
704
- extent: this.#reader.extentOf(source, declared.statement),
1260
+ extent: declared.extent,
705
1261
  doc: this.#reader.docOf(source, declared.statement)
706
1262
  }
707
1263
  });
708
1264
  return true;
709
1265
  };
710
- const visit = (node) => {
711
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && callOf(node.expression.text))
1266
+ const visit = (node, outer) => {
1267
+ const shadowed = this.#scopeOf(node, outer);
1268
+ if (ts.isCallExpression(node) && callOf(node.expression, shadowed))
712
1269
  return;
713
- ts.forEachChild(node, visit);
1270
+ ts.forEachChild(node, (child) => visit(child, shadowed));
714
1271
  };
715
1272
  if (ts.isIdentifier(stage))
716
- callOf(stage.text);
1273
+ callOf(stage, new Set);
717
1274
  else
718
- visit(stage);
1275
+ visit(stage, new Set);
719
1276
  return [...calls.values()];
720
1277
  }
1278
+ #scopeOf(node, outer) {
1279
+ const ts = this.#ts;
1280
+ const names = [];
1281
+ const bind = (name) => {
1282
+ if (ts.isIdentifier(name))
1283
+ names.push(name.text);
1284
+ else
1285
+ for (const element of name.elements)
1286
+ if (!ts.isOmittedExpression(element))
1287
+ bind(element.name);
1288
+ };
1289
+ const declare = (statement) => {
1290
+ if (ts.isVariableStatement(statement))
1291
+ for (const declaration of statement.declarationList.declarations)
1292
+ bind(declaration.name);
1293
+ else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
1294
+ names.push(statement.name.text);
1295
+ };
1296
+ const hoist = (child) => {
1297
+ if (ts.isFunctionLike(child) || ts.isClassLike(child))
1298
+ return;
1299
+ if (ts.isVariableDeclarationList(child) && !(child.flags & ts.NodeFlags.BlockScoped))
1300
+ for (const declaration of child.declarations)
1301
+ bind(declaration.name);
1302
+ ts.forEachChild(child, hoist);
1303
+ };
1304
+ if (ts.isFunctionLike(node)) {
1305
+ for (const parameter of node.parameters)
1306
+ bind(parameter.name);
1307
+ if (ts.isFunctionExpression(node) && node.name)
1308
+ names.push(node.name.text);
1309
+ if ("body" in node && node.body)
1310
+ ts.forEachChild(node.body, hoist);
1311
+ } else if (ts.isClassExpression(node) && node.name)
1312
+ names.push(node.name.text);
1313
+ else if (ts.isBlock(node))
1314
+ node.statements.forEach(declare);
1315
+ else if (ts.isCaseBlock(node))
1316
+ for (const clause of node.clauses)
1317
+ clause.statements.forEach(declare);
1318
+ else if (ts.isCatchClause(node) && node.variableDeclaration)
1319
+ bind(node.variableDeclaration.name);
1320
+ else if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer))
1321
+ for (const declaration of node.initializer.declarations)
1322
+ bind(declaration.name);
1323
+ return names.length ? new Set([...outer, ...names]) : outer;
1324
+ }
721
1325
  }
722
1326
 
723
1327
  // pkgs/@akanjs/language/akanGraph.ts
@@ -733,10 +1337,6 @@ var builtinGuards = {
733
1337
  Public: { scope: "account", doc: "Passes every caller, signed in or not." },
734
1338
  None: { scope: "account", doc: "Refuses every caller." }
735
1339
  };
736
- var frameworkOwner = "akanjs";
737
- var frameworkPattern = /^akanjs(?:\/|$)/;
738
- var aliasPattern = /^@(apps|libs)\/([^/]+)/;
739
- var maxMapDepth = 4;
740
1340
 
741
1341
  class GraphBuilder {
742
1342
  #source;
@@ -744,16 +1344,15 @@ class GraphBuilder {
744
1344
  #edges = new Map;
745
1345
  #contexts = new Map;
746
1346
  #members = new Map;
747
- #guards = new Map;
748
- #mapEntries = new Map;
749
1347
  #routeFiles = new Map;
750
- #declarations = new Map;
751
1348
  #routes;
1349
+ #resolver;
752
1350
  #adaptors = new Map;
753
1351
  #plugs = new Map;
754
1352
  constructor(source) {
755
1353
  this.#source = source;
756
1354
  this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
1355
+ this.#resolver = new GuardResolver(source, this.#routes);
757
1356
  }
758
1357
  build() {
759
1358
  const { facts } = this.#source;
@@ -775,7 +1374,8 @@ class GraphBuilder {
775
1374
  for (const fact of file.adaptors)
776
1375
  for (const plug of fact.plugs)
777
1376
  this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
778
- this.#declareGuards(facts);
1377
+ for (const guard of this.#resolver.declare(facts))
1378
+ this.#guardNodeOf(guard);
779
1379
  for (const file of facts) {
780
1380
  const { owner } = file;
781
1381
  const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
@@ -819,31 +1419,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
819
1419
  for (const query of filter.queries)
820
1420
  this.#digest(this.#addNode(this.#key("query", owner, refName, query.name), "query", owner, refName, query.name, query.span), this.#source.readText(file.fileName), query.extent);
821
1421
  }
822
- for (const slice of file.slices) {
823
- const refName = slice.model;
824
- if (!refName)
825
- continue;
826
- const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards);
827
- for (const guard of slice.guards)
828
- this.#guard(fallback, guard, file);
829
- for (const key of slice.keys) {
830
- const detail = [...key.guards, ...key.live ? ["live"] : []];
831
- const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
832
- for (const guard of key.guards)
833
- this.#guard(node, guard, file);
834
- }
835
- }
836
- for (const endpoint of file.endpoints) {
837
- const refName = endpoint.model;
838
- if (!refName)
839
- continue;
840
- for (const key of endpoint.keys) {
841
- const detail = [...key.verb ? [key.verb] : [], ...key.guards];
842
- const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
843
- for (const guard of key.guards)
844
- this.#guard(node, guard, file);
845
- }
846
- }
1422
+ this.#addGuarded(file);
847
1423
  for (const internal of file.internals)
848
1424
  for (const key of internal.keys)
849
1425
  if (internal.model && key.type !== "resolveField")
@@ -859,7 +1435,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
859
1435
  types.set(node.label, [...types.get(node.label) ?? [], node]);
860
1436
  for (const node of typed)
861
1437
  for (const field of node.fields ?? []) {
862
- const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#visible(type.owner, node.owner));
1438
+ const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#resolver.visible(type.owner, node.owner));
863
1439
  field.ref = (candidates.find((type) => type.owner === node.owner) ?? candidates[0])?.id ?? null;
864
1440
  if (field.ref)
865
1441
  this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
@@ -941,61 +1517,52 @@ ${label ?? ""}`;
941
1517
  else
942
1518
  this.#edges.set(key, { from, to, kind, label, count: 1, at });
943
1519
  }
944
- #guard(node, guard, file) {
945
- const [role, local] = guard.includes(" ") ? guard.split(" ") : [null, guard];
946
- if (!local || local === "?")
947
- return;
948
- this.#addEdge(node.id, this.#guardIdOf(local, file), "guard", role, node.at);
949
- }
950
- #declareGuards(facts) {
951
- const factories = new Map;
952
- for (const file of facts)
953
- for (const fact of file.guardFactories) {
954
- const factory = { fact, owner: file.owner, fileName: file.fileName, made: [] };
955
- factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
956
- }
957
- const sameFile = (factory, name) => name ? factories.get(name)?.find((other) => other.fileName === factory.fileName) : undefined;
958
- const declare = (guard, factory) => {
959
- this.#guards.set(guard.name, [...this.#guards.get(guard.name) ?? [], guard]);
960
- factory?.made.push(guard);
961
- };
962
- const madeBy = (factory, fileName, extent) => {
963
- const parts = [{ fileName, extent }];
964
- const seen = new Set;
965
- for (let current = factory;current && !seen.has(current); current = sameFile(current, current.fact.via)) {
966
- seen.add(current);
967
- parts.push({ fileName: current.fileName, extent: current.fact.extent });
968
- }
969
- return parts;
970
- };
971
- for (const file of facts) {
972
- const { owner, fileName } = file;
973
- for (const { name, span, extent, scope, doc, factory: callee } of file.guards) {
974
- const factory = callee ? factories.get(callee)?.find((other) => other.fileName === fileName) : undefined;
975
- declare({ name, owner, span, scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
1520
+ #addGuarded(file) {
1521
+ const { owner } = file;
1522
+ for (const slice of file.slices) {
1523
+ const refName = slice.model;
1524
+ if (!refName)
1525
+ continue;
1526
+ const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards.map((guard) => guardLabelOf(guard, file.guardLabels)));
1527
+ for (const guard of slice.guards)
1528
+ this.#guard(fallback, guard, file);
1529
+ for (const key of slice.keys) {
1530
+ const detail = [
1531
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels)),
1532
+ ...key.live ? ["live"] : []
1533
+ ];
1534
+ const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1535
+ for (const guard of key.guards)
1536
+ this.#guard(node, guard, file);
976
1537
  }
977
- for (const { name, span, extent, doc, callee } of file.guardCalls) {
978
- const imported = file.imports.find((item) => item.name === callee);
979
- if (!imported)
980
- continue;
981
- const from = this.#ownerOfSpecifier(fileName, imported.from);
982
- const candidates = factories.get(imported.imported === "default" || imported.imported === "*" ? callee : imported.imported) ?? [];
983
- const factory = candidates.find((other) => other.owner === from) ?? this.#soleVisible(candidates, from ?? owner);
984
- if (factory)
985
- declare({ name, owner, span, scope: factory.fact.scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
1538
+ }
1539
+ for (const endpoint of file.endpoints) {
1540
+ const refName = endpoint.model;
1541
+ if (!refName)
1542
+ continue;
1543
+ for (const key of endpoint.keys) {
1544
+ const detail = [
1545
+ ...key.verb ? [key.verb] : [],
1546
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels))
1547
+ ];
1548
+ const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1549
+ for (const guard of key.guards)
1550
+ this.#guard(node, guard, file);
986
1551
  }
987
1552
  }
988
- for (const list of factories.values())
989
- for (const { fact, made } of list)
990
- if (made.every((guard) => !guard.doc))
991
- for (const guard of made)
992
- guard.doc = fact.doc;
993
- for (const list of this.#guards.values())
994
- for (const guard of list)
995
- this.#guardNodeOf(guard);
1553
+ }
1554
+ #guard(node, guard, file) {
1555
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
1556
+ if (!local || local === "?")
1557
+ return;
1558
+ const id = local.includes("@") ? this.#madeInPlaceIdOf(local, file) : this.#guardIdOf(local, file);
1559
+ if (id)
1560
+ this.#addEdge(node.id, id, "guard", role, node.at);
996
1561
  }
997
1562
  #guardIdOf(local, site) {
998
- const found = this.#findGuard(local, site, 0);
1563
+ const found = this.#resolver.find(local, site);
1564
+ if ("external" in found)
1565
+ return null;
999
1566
  if ("guard" in found)
1000
1567
  return this.#guardNodeOf(found.guard);
1001
1568
  const { name, from } = found;
@@ -1008,99 +1575,18 @@ ${label ?? ""}`;
1008
1575
  node.doc = builtin.doc;
1009
1576
  return node.id;
1010
1577
  }
1011
- #findGuard(local, site, depth) {
1012
- const [head = local, member] = local.split(".");
1013
- const imported = site.imports.find((item) => item.name === head);
1014
- const from = imported ? this.#ownerOfSpecifier(site.fileName, imported.from) : undefined;
1015
- const holder = imported ? from : site.owner;
1016
- if (member && imported?.imported !== "*" && holder !== undefined && holder !== frameworkOwner && depth < maxMapDepth) {
1017
- const objectName = imported && imported.imported !== "default" ? imported.imported : head;
1018
- for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
1019
- const found = this.#findGuard(entry.name, entry.site, depth + 1);
1020
- if ("guard" in found)
1021
- return found;
1022
- }
1023
- }
1024
- const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1025
- const declared = this.#guards.get(name) ?? [];
1026
- const guard = from === undefined ? declared.find((other) => other.owner === site.owner) ?? declared.find((other) => this.#visible(other.owner, site.owner)) : declared.find((other) => other.owner === from) ?? (from === frameworkOwner ? undefined : this.#soleVisible(declared, from));
1027
- return guard ? { guard } : { name, from };
1028
- }
1029
- #mapEntriesOf(objectName, member, fileName, owner) {
1030
- const key = `${objectName}
1031
- ${member}
1032
- ${fileName ?? ""}
1033
- ${owner ?? ""}`;
1034
- const cached = this.#mapEntries.get(key);
1035
- if (cached)
1036
- return cached;
1037
- const { ts, reader } = this.#source;
1038
- const entries = [];
1039
- const fileNames = [
1040
- ...fileName ? [fileName] : [],
1041
- ...this.#source.files.filter(([other, words]) => other !== fileName && owner !== null && !testFilePattern.test(other) && words.includes(`
1042
- ${objectName}
1043
- `) && words.includes(`
1044
- ${member}
1045
- `) && this.#source.ownerOf(other) === owner).map(([other]) => other)
1046
- ];
1047
- for (const holder of fileNames) {
1048
- const text = this.#source.readText(holder);
1049
- if (text === undefined)
1050
- continue;
1051
- const source = this.#source.parse(holder, text);
1052
- const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
1053
- for (const statement of source.statements) {
1054
- if (!ts.isVariableStatement(statement))
1055
- continue;
1056
- const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
1057
- const object = map && reader.unwrap(map);
1058
- if (!object || !ts.isObjectLiteralExpression(object))
1059
- continue;
1060
- for (const property of object.properties) {
1061
- if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
1062
- entries.push({ name: member, site });
1063
- if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
1064
- continue;
1065
- const value = reader.unwrap(property.initializer);
1066
- if (ts.isIdentifier(value))
1067
- entries.push({ name: value.text, site });
1068
- else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
1069
- entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
1070
- }
1071
- }
1072
- }
1073
- this.#mapEntries.set(key, entries);
1074
- return entries;
1075
- }
1076
- #declarationOf(owner, name, near) {
1077
- const key = `${owner}
1078
- ${name}
1079
- ${near ?? ""}`;
1080
- const cached = this.#declarations.get(key);
1081
- if (cached !== undefined)
1082
- return cached;
1083
- const isNear = (fileName) => {
1084
- const relative = this.#source.relative(fileName);
1085
- return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1086
- };
1087
- const candidates = this.#source.files.filter(([fileName, words]) => !testFilePattern.test(fileName) && words.includes(`
1088
- ${name}
1089
- `) && this.#source.ownerOf(fileName) === owner).map(([fileName]) => fileName);
1090
- let found = null;
1091
- for (const fileName of [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))]) {
1092
- const text = this.#source.readText(fileName);
1093
- if (text === undefined)
1094
- continue;
1095
- const source = this.#source.parse(fileName, text);
1096
- const declared = this.#routes.declarationIn(source, name);
1097
- if (!declared)
1098
- continue;
1099
- found = { source, statement: declared.statement, declared: declared.declared };
1100
- break;
1101
- }
1102
- this.#declarations.set(key, found);
1103
- return found;
1578
+ #madeInPlaceIdOf(name, file) {
1579
+ const made = this.#resolver.madeInPlace(file.fileName, name);
1580
+ if (made)
1581
+ return this.#guardNodeOf(made);
1582
+ const head = name.slice(name.indexOf(":") + 1, name.indexOf("@")).split(".")[0] ?? name;
1583
+ const imported = file.imports.find((item) => item.name === head);
1584
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : file.owner;
1585
+ if (from === undefined || from === frameworkOwner)
1586
+ return null;
1587
+ const scope = `${this.#resolver.inOwner(file.fileName, file.owner)}#${name}`;
1588
+ const label = guardLabelOf(name, file.guardLabels);
1589
+ return this.#addNode(this.#key("guard", file.owner, scope), "guard", file.owner, null, name, null, [], label).id;
1104
1590
  }
1105
1591
  #addRoutes() {
1106
1592
  const routes = [];
@@ -1153,22 +1639,23 @@ ${dir}`, node);
1153
1639
  }
1154
1640
  #routeGuardIdOf({ name: local, local: declared }, fact, text) {
1155
1641
  if (declared) {
1156
- const file = this.#source.relative(fact.fileName).slice(fact.owner.length + 1);
1157
- const id = this.#key("guard", fact.owner, `${file}#${local}`);
1642
+ const id = this.#key("guard", fact.owner, `${this.#resolver.inOwner(fact.fileName, fact.owner)}#${local}`);
1158
1643
  return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
1159
1644
  }
1160
- const imported = fact.imports.find((item) => item.name === local);
1161
- if (!imported)
1645
+ const [head = local, member] = local.split(".");
1646
+ const imported = fact.imports.find((item) => item.name === head);
1647
+ if (!imported || member && imported.imported !== "*")
1162
1648
  return null;
1163
- const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
1164
- const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
1649
+ const name = member ?? (imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local);
1650
+ const from = this.#resolver.ownerOfSpecifier(fact.fileName, imported.from);
1165
1651
  if (!from || from === frameworkOwner)
1166
1652
  return null;
1167
- const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
1653
+ const found = this.#resolver.declarationOf(from, name, this.#resolver.pathOfSpecifier(fact.fileName, imported.from));
1168
1654
  if (!found)
1169
1655
  return null;
1170
1656
  const { reader } = this.#source;
1171
- return this.#routeGuardNodeOf(this.#key("guard", from, name), from, name, reader.spanOf(found.source, found.declared), reader.docOf(found.source, found.statement), found.source.text, reader.extentOf(found.source, found.statement));
1657
+ const owner = this.#source.ownerOf(found.source.fileName) ?? from;
1658
+ return this.#routeGuardNodeOf(this.#key("guard", owner, `${this.#resolver.inOwner(found.source.fileName, owner)}#${found.name}`), owner, found.name, reader.spanOf(found.source, found.declared), reader.docOf(found.source, found.statement), found.source.text, found.extent);
1172
1659
  }
1173
1660
  #routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
1174
1661
  const isNew = !this.#nodes.has(id);
@@ -1181,10 +1668,10 @@ ${dir}`, node);
1181
1668
  #adaptorIdOf(local, file) {
1182
1669
  const [head, member] = local.split(".");
1183
1670
  const imported = file.imports.find((item) => item.name === head);
1184
- const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
1671
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
1185
1672
  const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1186
1673
  const declared = this.#adaptors.get(name) ?? [];
1187
- const found = from === undefined ? declared.find((adaptor) => adaptor.owner === file.owner) ?? declared.find((adaptor) => this.#visible(adaptor.owner, file.owner)) : declared.find((adaptor) => adaptor.owner === from) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#visible(adaptor.owner, from)));
1674
+ const found = from === undefined ? declared.find((adaptor) => adaptor.owner === file.owner) ?? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, file.owner)) : declared.find((adaptor) => adaptor.owner === from) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#resolver.visible(adaptor.owner, from)));
1188
1675
  if (found)
1189
1676
  return this.#adaptorNodeOf(found);
1190
1677
  const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
@@ -1200,38 +1687,22 @@ ${dir}`, node);
1200
1687
  this.#digest(node, this.#source.readText(fileName), fact.extent);
1201
1688
  return node.id;
1202
1689
  }
1203
- #guardNodeOf({ name, owner, span, scope, doc, parts }) {
1204
- const node = this.#addNode(this.#key("guard", owner, name), "guard", owner, null, name, span, scope ? [scope] : []);
1690
+ #guardNodeOf({ name, owner, span, scope, doc, parts, file, label }) {
1691
+ const id = this.#key("guard", owner, file ? `${file}#${name}` : name);
1692
+ const node = this.#addNode(id, "guard", owner, null, name, span, scope ? [scope] : [], label ?? name);
1205
1693
  if (doc && !node.doc)
1206
1694
  node.doc = doc;
1207
- const digest = node.digest ? null : this.#hashOf(...parts.map(({ fileName, extent }) => [
1208
- this.#source.readText(fileName),
1209
- extent
1210
- ]));
1211
- if (digest)
1212
- node.digest = digest;
1695
+ if (node.digest)
1696
+ return node.id;
1697
+ const digest = this.#hashOf(...parts.map(({ fileName, extent }) => [this.#source.readText(fileName), extent]));
1698
+ if (!digest)
1699
+ return node.id;
1700
+ node.digest = digest;
1701
+ const sources = [...new Set(parts.map(({ fileName }) => this.#source.relative(fileName)))].filter((source) => source !== node.at?.file);
1702
+ if (sources.length)
1703
+ node.sources = sources;
1213
1704
  return node.id;
1214
1705
  }
1215
- #ownerOfSpecifier(fileName, specifier) {
1216
- if (frameworkPattern.test(specifier))
1217
- return frameworkOwner;
1218
- const path = this.#pathOfSpecifier(fileName, specifier);
1219
- return path === null ? undefined : ownerOf(path) ?? undefined;
1220
- }
1221
- #pathOfSpecifier(fileName, specifier) {
1222
- const alias = specifier.match(aliasPattern);
1223
- if (alias)
1224
- return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1225
- if (!specifier.startsWith("."))
1226
- return null;
1227
- const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1228
- for (const segment of specifier.split("/"))
1229
- if (segment === "..")
1230
- segments.pop();
1231
- else if (segment !== ".")
1232
- segments.push(segment);
1233
- return segments.join("/");
1234
- }
1235
1706
  #moduleDirOf(fileName) {
1236
1707
  const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
1237
1708
  return dir?.startsWith("_") && !dir.startsWith("__") ? dir.slice(1) : dir;
@@ -1267,20 +1738,13 @@ ${dir}`, node);
1267
1738
  visit(constant);
1268
1739
  return [...fields.values()];
1269
1740
  }
1270
- #visible(target, from) {
1271
- return !from || !target || target === from || target.startsWith("libs/");
1272
- }
1273
- #soleVisible(declared, from) {
1274
- const visible = declared.filter((other) => this.#visible(other.owner, from));
1275
- return visible.length === 1 ? visible[0] : undefined;
1276
- }
1277
1741
  #targetNodeOf({ family, owner, refName, sourceKey }) {
1278
1742
  const isDefault = /^[A-Z]/.test(sourceKey);
1279
1743
  const id = family === "slice" || family === "query" ? this.#key(family, owner, refName, isDefault ? "" : sourceKey) : family === "model" || family === "formSetter" ? this.#key("model", owner, refName) : family === "endpoint" ? this.#key("endpoint", owner, refName, sourceKey) : null;
1280
1744
  return id && this.#nodes.has(id) ? id : null;
1281
1745
  }
1282
1746
  #membersNamed(kind, name, owner, refName) {
1283
- const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#visible(node.owner, owner));
1747
+ const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#resolver.visible(node.owner, owner));
1284
1748
  const own = nodes.filter((node) => node.owner === owner);
1285
1749
  return (own.length ? own : nodes).map((node) => node.id);
1286
1750
  }
@@ -1634,6 +2098,10 @@ var endpointNames = (key) => {
1634
2098
  };
1635
2099
 
1636
2100
  // pkgs/@akanjs/language/nameTable.ts
2101
+ var guardDetailOf = (facts, guards) => {
2102
+ const labels = Object.assign({}, ...facts.map((file) => file.guardLabels));
2103
+ return guards.length ? [`guards: ${guards.map((guard) => guardLabelOf(guard, labels)).join(", ")}`] : [];
2104
+ };
1637
2105
  var capitalize3 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
1638
2106
 
1639
2107
  class NameTable {
@@ -1733,7 +2201,7 @@ class NameTable {
1733
2201
  role: "sig",
1734
2202
  gate: "sig",
1735
2203
  label: sliceClass.name,
1736
- detail: sliceClass.guards.length ? [`guards: ${sliceClass.guards.join(", ")}`] : []
2204
+ detail: guardDetailOf(facts, sliceClass.guards)
1737
2205
  });
1738
2206
  if (modelDoc)
1739
2207
  this.#add({
@@ -1798,7 +2266,7 @@ class NameTable {
1798
2266
  sourceKey: slice.name,
1799
2267
  span: slice.span,
1800
2268
  label: `slice ${refName} (default)`,
1801
- detail: slice.guards.length ? [`guards: ${slice.guards.join(", ")}`] : []
2269
+ detail: guardDetailOf(facts, slice.guards)
1802
2270
  };
1803
2271
  this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
1804
2272
  for (const key of slice.keys)
@@ -1808,7 +2276,7 @@ class NameTable {
1808
2276
  sourceKey: key.name,
1809
2277
  span: key.span,
1810
2278
  label: `slice ${key.name}`,
1811
- detail: [...key.guards.length ? [`guards: ${key.guards.join(", ")}`] : [], ...key.live ? ["live"] : []]
2279
+ detail: [...guardDetailOf(facts, key.guards), ...key.live ? ["live"] : []]
1812
2280
  }, refName, key.name, modelNameSet, slice.name);
1813
2281
  }
1814
2282
  if (filterClass) {
@@ -1865,7 +2333,7 @@ class NameTable {
1865
2333
  role: key.verb === "pubsub" ? "subscribe" : "listen",
1866
2334
  gate: null,
1867
2335
  label: `${key.verb} ${key.name}`,
1868
- detail: key.guards.length ? [`guards: ${key.guards.join(", ")}`] : []
2336
+ detail: guardDetailOf(facts, key.guards)
1869
2337
  });
1870
2338
  }
1871
2339
  }
@@ -1923,14 +2391,12 @@ var dictionaryPattern = /\.dictionary\.ts$/;
1923
2391
  var srvkitPattern = /\/srvkit\//;
1924
2392
  var wordPattern = /[A-Za-z_$][\w$]*/g;
1925
2393
  var identifierPattern = /^[A-Za-z_$][\w$]*$/;
1926
- var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
1927
2394
  var stateReceivers = new Set(["use", "sel", "get", "do"]);
1928
2395
  var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
1929
2396
  var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
1930
2397
  var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
1931
2398
  var parseCacheSize = 64;
1932
2399
  var pathOf = (fileName) => fileName.replace(/\\/g, "/");
1933
- var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
1934
2400
  var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
1935
2401
 
1936
2402
  class AkanIndex {