@akanjs/language 3.2.0-rc.1 → 3.2.0-rc.11

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,261 @@ 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/escapeRegExp.ts
9
+ var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
10
+ var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
11
+
12
+ // pkgs/@akanjs/language/guardFacts.ts
13
+ var guardLabelOf = (guard, labels) => {
14
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
15
+ const label = labels[local];
16
+ return label === undefined ? guard : role ? `${role} ${label}` : label;
17
+ };
18
+ var maxLabelArgs = 32;
19
+ var maxLayers = 4;
20
+ var guardRefReaderOf = (ts, ast, unwrap) => {
21
+ const pathOf = (expression) => {
22
+ if (ts.isIdentifier(expression))
23
+ return expression.text;
24
+ if (!ts.isPropertyAccessExpression(expression))
25
+ return null;
26
+ const head = pathOf(expression.expression);
27
+ return head && `${head}.${expression.name.text}`;
28
+ };
29
+ const layersOf = (call) => {
30
+ const inner = unwrap(call.expression);
31
+ if (!ts.isCallExpression(inner))
32
+ return { root: call, layers: 0 };
33
+ const { root, layers } = layersOf(inner);
34
+ return { root, layers: layers + 1 };
35
+ };
36
+ const calleeOf = (call) => pathOf(unwrap(layersOf(call).root.expression)) ?? "?";
37
+ const labelOf = (source, call) => {
38
+ const args = call.arguments.map((arg) => arg.getText(source)).join(", ");
39
+ const inner = unwrap(call.expression);
40
+ const head = ts.isCallExpression(inner) ? labelOf(source, inner) : pathOf(inner) ?? "?";
41
+ return `${head}(${args.length <= maxLabelArgs && !args.includes(`
42
+ `) ? args : "…"})`;
43
+ };
44
+ const guardRefOf = (expression, made, member) => {
45
+ const value = unwrap(expression);
46
+ if (!ts.isCallExpression(value))
47
+ return pathOf(value) ?? ast.expressionName(value);
48
+ const prefix = `${member}:${calleeOf(value)}@`;
49
+ const name = `${prefix}${made.filter((other) => other.name.startsWith(prefix)).length + 1}`;
50
+ made.push({ call: value, name });
51
+ return name;
52
+ };
53
+ const guardsIn = (source, object, made, member) => {
54
+ const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
55
+ if (!guards)
56
+ return [];
57
+ const refOf = (expression) => guardRefOf(expression, made, member);
58
+ if (ts.isArrayLiteralExpression(guards))
59
+ return guards.elements.map(refOf).filter((name) => name !== null);
60
+ if (ts.isObjectLiteralExpression(guards))
61
+ return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
62
+ const value = unwrap(property.initializer);
63
+ const names = ts.isArrayLiteralExpression(value) ? value.elements.map(refOf) : [refOf(value)];
64
+ return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
65
+ });
66
+ const single = refOf(guards);
67
+ return single ? [single] : [];
68
+ };
69
+ return { pathOf, layersOf, labelOf, guardsIn };
70
+ };
71
+ var guardFactoryReaderOf = (ts, ast, { spanOf, extentOf, unwrap, docOf }, pathOf, layersOf) => {
72
+ const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
73
+ const scopeOf = (node) => {
74
+ const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
75
+ const value = scope && unwrap(scope);
76
+ return value && ts.isStringLiteralLike(value) ? value.text : null;
77
+ };
78
+ const functionsOf = (source) => {
79
+ const functions = new Map;
80
+ for (const statement of source.statements) {
81
+ if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
82
+ functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
83
+ if (!ts.isVariableStatement(statement))
84
+ continue;
85
+ for (const declaration of statement.declarationList.declarations) {
86
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
87
+ if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
88
+ functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
89
+ }
90
+ }
91
+ return functions;
92
+ };
93
+ const returnedOf = (body, guardNames, depth = 0) => {
94
+ const locals = new Map;
95
+ const pending = [];
96
+ const visit = (node) => {
97
+ if (ts.isClassDeclaration(node) && node.name)
98
+ locals.set(node.name.text, node);
99
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
100
+ locals.set(node.name.text, node.initializer);
101
+ if (ts.isReturnStatement(node) && node.expression)
102
+ pending.push(node.expression);
103
+ if (!ts.isFunctionLike(node) && !ts.isClassLike(node))
104
+ ts.forEachChild(node, visit);
105
+ };
106
+ if (ts.isBlock(body))
107
+ ts.forEachChild(body, visit);
108
+ else
109
+ pending.push(body);
110
+ let guard = null;
111
+ const calls = [];
112
+ const seen = new Set;
113
+ const passed = new Set;
114
+ const returned = [];
115
+ for (let node = pending.pop();node; node = pending.pop()) {
116
+ if (seen.has(node))
117
+ continue;
118
+ seen.add(node);
119
+ const value = ts.isExpression(node) ? unwrap(node) : node;
120
+ if ((ts.isClassExpression(value) || ts.isClassDeclaration(value)) && implementsGuard(value, guardNames))
121
+ guard ??= value;
122
+ else if ((ts.isArrowFunction(value) || ts.isFunctionExpression(value)) && !passed.has(node) && depth < maxLayers)
123
+ returned.push(value.body);
124
+ else if (ts.isConditionalExpression(value))
125
+ pending.push(value.whenTrue, value.whenFalse);
126
+ else if (ts.isBinaryExpression(value))
127
+ pending.push(value.left, value.right);
128
+ else if (ts.isIdentifier(value)) {
129
+ const local = locals.get(value.text);
130
+ if (local)
131
+ pending.push(local);
132
+ } else if (ts.isCallExpression(value)) {
133
+ const { root, layers } = layersOf(value);
134
+ const callee = pathOf(unwrap(root.expression));
135
+ if (callee && !passed.has(node) && !calls.some((call) => call.callee === callee))
136
+ calls.push({ callee, layers });
137
+ for (let call = value;ts.isCallExpression(call); call = unwrap(call.expression)) {
138
+ for (const arg of call.arguments)
139
+ passed.add(arg);
140
+ pending.push(...call.arguments);
141
+ }
142
+ }
143
+ }
144
+ const [inner] = returned;
145
+ if (guard || calls.length || !inner)
146
+ return { guard, calls, layers: 0 };
147
+ const nested = returnedOf(inner, guardNames, depth + 1);
148
+ return { ...nested, layers: nested.layers + 1 };
149
+ };
150
+ const guardFactoriesOf = (source, guardNames, isImported) => {
151
+ const functions = functionsOf(source);
152
+ const returned = new Map([...functions].map(([name, { body }]) => [name, returnedOf(body, guardNames)]));
153
+ const members = new Set;
154
+ const isFactoryCall = (callee) => members.has(callee) || !functions.has(callee) && isImported(callee);
155
+ for (let isGrowing = true;isGrowing; ) {
156
+ isGrowing = false;
157
+ for (const [name, { guard, calls }] of returned) {
158
+ if (members.has(name) || !guard && !calls.some(({ callee }) => isFactoryCall(callee)))
159
+ continue;
160
+ members.add(name);
161
+ isGrowing = true;
162
+ }
163
+ }
164
+ const factories = new Map;
165
+ for (const name of members) {
166
+ const found = functions.get(name);
167
+ const made = returned.get(name);
168
+ if (!found || !made)
169
+ continue;
170
+ factories.set(name, {
171
+ name,
172
+ span: spanOf(source, found.name),
173
+ extent: extentOf(source, found.statement),
174
+ scope: made.guard ? scopeOf(made.guard) : null,
175
+ doc: docOf(source, found.statement),
176
+ direct: !!made.guard,
177
+ via: made.calls.filter(({ callee }) => isFactoryCall(callee)),
178
+ layers: made.layers
179
+ });
180
+ }
181
+ return factories;
182
+ };
183
+ return { implementsGuard, scopeOf, guardFactoriesOf };
184
+ };
185
+ var createGuardFactReader = (ts, ast, base) => {
186
+ const { spanOf, extentOf, unwrap, docOf } = base;
187
+ const { pathOf, layersOf, labelOf, guardsIn } = guardRefReaderOf(ts, ast, unwrap);
188
+ const { implementsGuard, scopeOf, guardFactoriesOf } = guardFactoryReaderOf(ts, ast, base, pathOf, layersOf);
189
+ const guardsOf = (source, imports, madeInPlace) => {
190
+ const guardNames = new Set([
191
+ "Guard",
192
+ ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
193
+ ]);
194
+ const imported = new Set(imports.map(({ name }) => name));
195
+ const isImported = (callee) => imported.has(callee.split(".")[0] ?? callee);
196
+ const factories = guardFactoriesOf(source, guardNames, isImported);
197
+ const guards = [];
198
+ const calls = [];
199
+ const makeOf = (made, value, isInPlace = false) => {
200
+ if (ts.isClassExpression(value) && implementsGuard(value, guardNames)) {
201
+ guards.push({ ...made, scope: scopeOf(value), factory: null, layers: 0 });
202
+ return;
203
+ }
204
+ const { root, layers } = ts.isCallExpression(value) ? layersOf(value) : { root: null, layers: 0 };
205
+ const callee = root && pathOf(unwrap(root.expression));
206
+ const factory = callee && factories.get(callee);
207
+ if (factory)
208
+ guards.push({ ...made, scope: factory.scope, factory: factory.name, layers });
209
+ else if (callee && (isImported(callee) || isInPlace))
210
+ calls.push({ ...made, callee, kind: "call", scope: null, layers });
211
+ };
212
+ for (const statement of source.statements) {
213
+ if (ts.isClassDeclaration(statement) && statement.name) {
214
+ const declared = {
215
+ name: statement.name.text,
216
+ span: spanOf(source, statement.name),
217
+ extent: extentOf(source, statement),
218
+ scope: scopeOf(statement),
219
+ doc: docOf(source, statement)
220
+ };
221
+ const base = statement.heritageClauses?.find(({ token }) => token === ts.SyntaxKind.ExtendsKeyword)?.types[0];
222
+ const callee = base && pathOf(base.expression);
223
+ if (implementsGuard(statement, guardNames))
224
+ guards.push({ ...declared, factory: null, layers: 0 });
225
+ else if (callee)
226
+ calls.push({ ...declared, callee, kind: "extends", layers: 0 });
227
+ }
228
+ if (!ts.isVariableStatement(statement))
229
+ continue;
230
+ for (const declaration of statement.declarationList.declarations) {
231
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
232
+ if (!initializer || !ts.isIdentifier(declaration.name))
233
+ continue;
234
+ const holder = declaration.name.text;
235
+ makeOf({
236
+ name: holder,
237
+ span: spanOf(source, declaration.name),
238
+ extent: extentOf(source, statement),
239
+ doc: docOf(source, statement)
240
+ }, initializer);
241
+ if (!ts.isObjectLiteralExpression(initializer))
242
+ continue;
243
+ for (const property of initializer.properties) {
244
+ const key = ts.isPropertyAssignment(property) ? ast.nodeName(property.name) : null;
245
+ if (key && ts.isPropertyAssignment(property))
246
+ makeOf({
247
+ name: `${holder}.${key}`,
248
+ span: spanOf(source, property.name),
249
+ extent: extentOf(source, property),
250
+ doc: docOf(source, property)
251
+ }, unwrap(property.initializer));
252
+ }
253
+ }
254
+ }
255
+ for (const { call, name } of madeInPlace)
256
+ makeOf({ name, span: spanOf(source, call), extent: extentOf(source, call), doc: null }, call, true);
257
+ const guardLabels = Object.fromEntries(madeInPlace.map(({ call, name }) => [name, labelOf(source, call)]));
258
+ return { guards, guardFactories: [...factories.values()], guardCalls: calls, guardLabels };
259
+ };
260
+ return { guardsIn, guardsOf };
261
+ };
262
+
8
263
  // pkgs/@akanjs/language/moduleFacts.ts
9
264
  var testFilePattern = /\.(?:test|spec)\.tsx?$/;
10
265
  var moduleCallees = new Set([
@@ -73,21 +328,6 @@ var createFactReader = (ts, ast) => {
73
328
  const value = unwrap(expression);
74
329
  return ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) ? `${value.expression.text}.${value.name.text}` : ast.expressionName(value);
75
330
  };
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
331
  const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
92
332
  const chainOf = (initializer) => {
93
333
  let root = unwrap(initializer);
@@ -104,7 +344,7 @@ var createFactReader = (ts, ast) => {
104
344
  const node = keyName(property);
105
345
  return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
106
346
  });
107
- const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
347
+ const sliceKeysOf = (source, builder, made, model) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
108
348
  if (!ts.isPropertyAssignment(property))
109
349
  return [];
110
350
  const name = ast.nodeName(property.name);
@@ -116,7 +356,7 @@ var createFactReader = (ts, ast) => {
116
356
  name,
117
357
  span: spanOf(source, property.name),
118
358
  extent: extentOf(source, property),
119
- guards: root ? guardsIn(optionObjectOf(root)) : [],
359
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
120
360
  live: methods.includes("live"),
121
361
  argCount: methods.filter((method) => argMethods.has(method)).length
122
362
  }
@@ -166,7 +406,7 @@ var createFactReader = (ts, ast) => {
166
406
  const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
167
407
  return `${ms / size}${unit}`;
168
408
  };
169
- const modesOf = (option) => {
409
+ const modesOf = (option, type) => {
170
410
  const object = option && unwrap(option);
171
411
  if (!object || !ts.isObjectLiteralExpression(object))
172
412
  return [];
@@ -176,9 +416,9 @@ var createFactReader = (ts, ast) => {
176
416
  if (name === "enabled")
177
417
  return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
178
418
  if (name === "once")
179
- return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
419
+ return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
180
420
  if (name === "lock")
181
- return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
421
+ return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
182
422
  if (name !== "serverMode" && name !== "operationMode")
183
423
  return [];
184
424
  const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
@@ -196,7 +436,8 @@ var createFactReader = (ts, ast) => {
196
436
  const { root } = chainOf(property.initializer);
197
437
  const type = root ? verbOf(root.expression) : null;
198
438
  const [first, second] = root?.arguments ?? [];
199
- const schedule = type === "cron" && first ? ts.isStringLiteralLike(first) ? first.text : first.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
439
+ const cron = type === "cron" && first ? unwrap(first) : null;
440
+ const schedule = cron ? ts.isStringLiteralLike(cron) ? cron.text : cron.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
200
441
  const option = type === "initialize" || type === "destroy" ? first : second;
201
442
  return [
202
443
  {
@@ -205,7 +446,7 @@ var createFactReader = (ts, ast) => {
205
446
  extent: extentOf(source, property),
206
447
  type,
207
448
  schedule,
208
- modes: modesOf(option)
449
+ modes: modesOf(option, type)
209
450
  }
210
451
  ];
211
452
  });
@@ -222,7 +463,20 @@ var createFactReader = (ts, ast) => {
222
463
  return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
223
464
  });
224
465
  };
225
- const endpointKeysOf = (source, builder) => {
466
+ const adaptorBindingsIn = (source) => {
467
+ const bindings = [];
468
+ const visit = (node) => {
469
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "applyAdaptor") {
470
+ const [role, adaptor] = node.arguments.map(guardNameOf);
471
+ if (role && adaptor)
472
+ bindings.push({ role, adaptor, span: spanOf(source, node.expression.name) });
473
+ }
474
+ ts.forEachChild(node, visit);
475
+ };
476
+ visit(source);
477
+ return bindings;
478
+ };
479
+ const endpointKeysOf = (source, builder, made, model) => {
226
480
  const verbOf = verbReaderOf(builder?.parameters[0]);
227
481
  return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
228
482
  if (!ts.isPropertyAssignment(property))
@@ -237,7 +491,7 @@ var createFactReader = (ts, ast) => {
237
491
  span: spanOf(source, property.name),
238
492
  extent: extentOf(source, property),
239
493
  verb: root ? verbOf(root.expression) : null,
240
- guards: root ? guardsIn(optionObjectOf(root)) : [],
494
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
241
495
  argCount: methods.filter((method) => argMethods.has(method)).length
242
496
  }
243
497
  ];
@@ -304,22 +558,6 @@ var createFactReader = (ts, ast) => {
304
558
  })) : []
305
559
  ];
306
560
  };
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
561
  const docOf = (source, node) => {
324
562
  const { text } = source;
325
563
  const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
@@ -335,108 +573,20 @@ var createFactReader = (ts, ast) => {
335
573
  `)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
336
574
  return lines.length ? lines.join(" ") : null;
337
575
  };
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
- };
576
+ const { guardsIn, guardsOf } = createGuardFactReader(ts, ast, { spanOf, extentOf, unwrap, docOf });
431
577
  const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
432
578
  const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
433
579
  const read = (source, owner) => {
434
580
  const imports = importsIn(source);
581
+ const madeInPlace = [];
435
582
  const facts = {
436
583
  fileName: source.fileName,
437
584
  owner,
438
585
  imports,
439
- ...guardsOf(source, imports),
586
+ guards: [],
587
+ guardFactories: [],
588
+ guardCalls: [],
589
+ guardLabels: {},
440
590
  constants: [],
441
591
  enums: [],
442
592
  filters: [],
@@ -446,6 +596,7 @@ var createFactReader = (ts, ast) => {
446
596
  endpoints: [],
447
597
  internals: [],
448
598
  adaptors: [],
599
+ adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
449
600
  stores: []
450
601
  };
451
602
  for (const statement of source.statements) {
@@ -470,16 +621,19 @@ var createFactReader = (ts, ast) => {
470
621
  ...name,
471
622
  model: moduleRefOf(call.arguments[0], "db"),
472
623
  methods: methodsOf(source, statement),
473
- plugs: plugsOf(source, builder)
624
+ plugs: plugsOf(source, builder),
625
+ fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
474
626
  });
475
- else if (callee === "slice")
627
+ else if (callee === "slice") {
628
+ const model = moduleRefOf(call.arguments[0], "srv");
629
+ const member = model ?? name.name;
476
630
  facts.slices.push({
477
631
  ...name,
478
- model: moduleRefOf(call.arguments[0], "srv"),
479
- guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
480
- keys: sliceKeysOf(source, builder)
632
+ model,
633
+ guards: guardsIn(source, call.arguments.find(ts.isObjectLiteralExpression), madeInPlace, member),
634
+ keys: sliceKeysOf(source, builder, madeInPlace, member)
481
635
  });
482
- else if (callee === "internal")
636
+ } else if (callee === "internal")
483
637
  facts.internals.push({
484
638
  ...name,
485
639
  model: moduleRefOf(call.arguments[0], "srv"),
@@ -494,13 +648,14 @@ var createFactReader = (ts, ast) => {
494
648
  plugs: plugsOf(source, builder),
495
649
  doc: docOf(source, statement)
496
650
  });
497
- } else if (callee === "endpoint")
651
+ } else if (callee === "endpoint") {
652
+ const model = moduleRefOf(call.arguments[0], "srv");
498
653
  facts.endpoints.push({
499
654
  ...name,
500
- model: moduleRefOf(call.arguments[0], "srv"),
501
- keys: endpointKeysOf(source, builder)
655
+ model,
656
+ keys: endpointKeysOf(source, builder, madeInPlace, model ?? name.name)
502
657
  });
503
- else if (callee === "store")
658
+ } else if (callee === "store")
504
659
  facts.stores.push({
505
660
  ...name,
506
661
  model: moduleRefOf(call.arguments[0], "sig"),
@@ -508,10 +663,647 @@ var createFactReader = (ts, ast) => {
508
663
  actions: methodsOf(source, statement)
509
664
  });
510
665
  }
511
- return facts;
512
- };
513
- return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
514
- };
666
+ return { ...facts, ...guardsOf(source, imports, madeInPlace) };
667
+ };
668
+ return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
669
+ };
670
+
671
+ // pkgs/@akanjs/language/guardResolver.ts
672
+ var frameworkOwner = "akanjs";
673
+ var frameworkPattern = /^akanjs(?:\/|$)/;
674
+ var aliasPattern = /^@(apps|libs)\/([^/]+)/;
675
+ var maxMapDepth = 4;
676
+ var identifierPattern = /^[\p{ID_Start}$_][\p{ID_Continue}$\u200c\u200d]*$/u;
677
+ var starExportPattern = /export\s*\*\s*from\s*["']([^"']+)["']/g;
678
+ var exportingPatternOf = (name) => new RegExp(`export\\s*\\{(?:[^}]*?[\\s,])?${escapeRegExp(name)}(?![\\w$])`);
679
+ var declaringPatternOf = (name, isAnyBinding) => {
680
+ const escaped = escapeRegExp(name);
681
+ const braced = "\\{(?:[^}]*?[\\s,])?";
682
+ const leads = [
683
+ "function\\s*\\*?\\s*",
684
+ "(?:const|let|var)\\s+",
685
+ `export\\s*${braced}`,
686
+ ...isAnyBinding ? ["class\\s+", `(?:const|let|var)\\s*${braced}`] : []
687
+ ];
688
+ return new RegExp(`(?:${leads.join("|")})${escaped}(?![\\w$])|,\\s*${escaped}\\s*(?::[^=;,]+)?=(?![=>])`);
689
+ };
690
+
691
+ class GuardResolver {
692
+ #source;
693
+ #routes;
694
+ #guards = new Map;
695
+ #madeInPlace = new Map;
696
+ #mapEntries = new Map;
697
+ #reexports = new Map;
698
+ #declarations = new Map;
699
+ #starExports = new Map;
700
+ #moduleExports = new Map;
701
+ #ownerFiles = null;
702
+ #modules = null;
703
+ constructor(source, routes) {
704
+ this.#source = source;
705
+ this.#routes = routes;
706
+ }
707
+ find(local, site) {
708
+ return this.#findGuard(local, site, 0);
709
+ }
710
+ madeInPlace(fileName, name) {
711
+ return this.#madeInPlace.get(`${fileName}
712
+ ${name}`);
713
+ }
714
+ declare(facts) {
715
+ const factories = new Map;
716
+ const declaredFactories = [];
717
+ for (const file of facts)
718
+ for (const fact of file.guardFactories) {
719
+ const factory = {
720
+ fact,
721
+ owner: file.owner,
722
+ site: file,
723
+ made: [],
724
+ isReal: fact.direct,
725
+ scope: fact.scope,
726
+ layers: fact.layers,
727
+ targets: []
728
+ };
729
+ factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
730
+ declaredFactories.push(factory);
731
+ }
732
+ for (const factory of declaredFactories)
733
+ factory.targets = factory.fact.via.flatMap(({ callee, layers }) => {
734
+ const target = this.#factoryAt(factories, callee, factory.site);
735
+ return target ? [{ target, layers }] : [];
736
+ });
737
+ for (let isGrowing = true;isGrowing; ) {
738
+ isGrowing = false;
739
+ for (const factory of declaredFactories) {
740
+ const via = factory.targets.find(({ target, layers }) => target.isReal && target.layers >= layers);
741
+ if (factory.isReal || !via)
742
+ continue;
743
+ factory.isReal = true;
744
+ factory.layers = factory.fact.layers + via.target.layers - via.layers;
745
+ factory.scope ??= via.target.scope;
746
+ isGrowing = true;
747
+ }
748
+ }
749
+ const partsOf = (factory, seen = new Set) => {
750
+ if (seen.has(factory))
751
+ return [];
752
+ seen.add(factory);
753
+ return [
754
+ { fileName: factory.site.fileName, extent: factory.fact.extent },
755
+ ...factory.targets.filter(({ target }) => target.isReal).flatMap(({ target }) => partsOf(target, seen))
756
+ ];
757
+ };
758
+ const declare = (file, made, scope, parts) => {
759
+ const { name, span, extent, doc } = made;
760
+ const label = file.guardLabels[name];
761
+ const own = { fileName: file.fileName, extent };
762
+ const guard = { name, owner: file.owner, span, scope, doc, parts: [own, ...parts], file: null, label };
763
+ if (name.includes("@"))
764
+ this.#madeInPlace.set(`${file.fileName}
765
+ ${name}`, guard);
766
+ else
767
+ this.#guards.set(name, [...this.#guards.get(name) ?? [], guard]);
768
+ return guard;
769
+ };
770
+ const extending = [];
771
+ for (const file of facts) {
772
+ for (const made of file.guards) {
773
+ const factory = made.factory ? factories.get(made.factory)?.find((other) => other.site.fileName === file.fileName) : undefined;
774
+ if (!made.factory)
775
+ declare(file, made, made.scope, []);
776
+ else if (factory?.isReal && factory.layers === made.layers)
777
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
778
+ }
779
+ for (const made of file.guardCalls) {
780
+ if (made.kind === "extends") {
781
+ extending.push([file, made]);
782
+ continue;
783
+ }
784
+ const factory = this.#factoryAt(factories, made.callee, file);
785
+ if (factory?.isReal && factory.layers === made.layers)
786
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
787
+ }
788
+ }
789
+ for (let isGrowing = true;isGrowing; ) {
790
+ isGrowing = false;
791
+ for (const [idx, entry] of extending.entries()) {
792
+ const [file, made] = entry;
793
+ const found = this.#findGuard(made.callee, file, 0, true);
794
+ if (!("guard" in found))
795
+ continue;
796
+ declare(file, made, made.scope ?? found.guard.scope, found.guard.parts);
797
+ extending.splice(idx, 1);
798
+ isGrowing = true;
799
+ break;
800
+ }
801
+ }
802
+ for (const { fact, made, isReal } of declaredFactories)
803
+ if (isReal && made.every((guard) => !guard.doc))
804
+ for (const guard of made)
805
+ guard.doc = fact.doc;
806
+ for (const list of this.#guards.values())
807
+ for (const guard of list)
808
+ if (list.some((other) => other !== guard && other.owner === guard.owner))
809
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
810
+ for (const guard of this.#madeInPlace.values())
811
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
812
+ return [...[...this.#guards.values()].flat(), ...this.#madeInPlace.values()];
813
+ }
814
+ #factoryAt(factories, callee, site, depth = 0) {
815
+ const [head = callee, member] = callee.split(".");
816
+ const imported = site.imports.find((item) => item.name === head);
817
+ if (!imported)
818
+ return member ? undefined : factories.get(callee)?.find((other) => other.site.fileName === site.fileName);
819
+ if (member && imported.imported !== "*")
820
+ return;
821
+ const from = this.ownerOfSpecifier(site.fileName, imported.from);
822
+ if (from === undefined || from === frameworkOwner)
823
+ return;
824
+ const name = member ?? (imported.imported === "default" || imported.imported === "*" ? head : imported.imported);
825
+ const candidates = factories.get(name) ?? [];
826
+ const near = this.pathOfSpecifier(site.fileName, imported.from);
827
+ const own = this.#nearest(candidates.filter((other) => other.owner === from), ({ site }) => site.fileName, near);
828
+ if (own)
829
+ return own;
830
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
831
+ if (reexport)
832
+ return this.#factoryAt(factories, reexport.local, reexport.site, depth + 1);
833
+ return from && near ? this.#throughStar(candidates, from, name, near) : undefined;
834
+ }
835
+ #findGuard(local, site, depth, isBase = false) {
836
+ const segments = local.split(".");
837
+ const head = segments[0] ?? local;
838
+ const imported = site.imports.find((item) => item.name === head);
839
+ const from = imported ? this.ownerOfSpecifier(site.fileName, imported.from) : undefined;
840
+ if (imported && from === undefined)
841
+ return { external: true };
842
+ const near = imported ? this.pathOfSpecifier(site.fileName, imported.from) : this.#source.relative(site.fileName).replace(/\.tsx?$/, "");
843
+ const named = imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : null;
844
+ const path = imported?.imported === "*" ? segments.slice(1) : [named ?? head, ...segments.slice(1)];
845
+ const holder = imported ? from ?? null : site.owner;
846
+ const [objectName, member] = path;
847
+ if (path.length === 2 && objectName && member && holder !== frameworkOwner && depth < maxMapDepth) {
848
+ const made = this.#nearest((this.#guards.get(`${objectName}.${member}`) ?? []).filter((guard) => guard.owner === holder), ({ span }) => span.fileName, near);
849
+ if (made)
850
+ return { guard: made };
851
+ for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
852
+ const found = this.#findGuard(entry.name, entry.site, depth + 1, isBase);
853
+ if ("guard" in found)
854
+ return found;
855
+ }
856
+ }
857
+ const name = path[path.length - 1] ?? head;
858
+ if (from === frameworkOwner)
859
+ return { name, from };
860
+ const declared = this.#guards.get(name) ?? [];
861
+ const own = this.#nearest(declared.filter((other) => other.owner === (imported ? from : site.owner)), ({ span }) => span.fileName, near);
862
+ if (own)
863
+ return { guard: own };
864
+ if (!imported) {
865
+ const visible = isBase ? undefined : declared.find((other) => this.visible(other.owner, site.owner));
866
+ return visible ? { guard: visible } : { name, from };
867
+ }
868
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
869
+ if (reexport)
870
+ return this.#findGuard(reexport.local, reexport.site, depth + 1, isBase);
871
+ const reached = from && near ? this.#throughStar(declared, from, name, near) : undefined;
872
+ return reached ? { guard: reached } : { name, from };
873
+ }
874
+ reexportOf(owner, name, near) {
875
+ const key = `${owner}
876
+ ${name}
877
+ ${near ?? ""}`;
878
+ const cached = this.#reexports.get(key);
879
+ if (cached !== undefined)
880
+ return cached;
881
+ let found = null;
882
+ const exporting = exportingPatternOf(name);
883
+ for (const fileName of this.#filesOf(owner, name, near)) {
884
+ const text = this.#source.readText(fileName);
885
+ if (text === undefined || !exporting.test(text))
886
+ continue;
887
+ const reexport = this.#reexportIn(this.#source.parse(fileName, text), name);
888
+ if (!reexport)
889
+ continue;
890
+ const site = { fileName, owner, imports: [] };
891
+ found = reexport.from ? {
892
+ local: reexport.name,
893
+ site: { ...site, imports: [{ ...reexport, imported: reexport.name, from: reexport.from }] }
894
+ } : { local: reexport.name, site };
895
+ break;
896
+ }
897
+ this.#reexports.set(key, found);
898
+ return found;
899
+ }
900
+ #reexportIn(source, name) {
901
+ const { ts, reader } = this.#source;
902
+ for (const statement of source.statements) {
903
+ if (!ts.isExportDeclaration(statement) || statement.isTypeOnly)
904
+ continue;
905
+ const { exportClause, moduleSpecifier } = statement;
906
+ if (!exportClause || !ts.isNamedExports(exportClause))
907
+ continue;
908
+ const element = exportClause.elements.find((item) => !item.isTypeOnly && item.name.text === name);
909
+ if (!element)
910
+ continue;
911
+ const original = (element.propertyName ?? element.name).text;
912
+ if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier))
913
+ return { name: original, from: moduleSpecifier.text };
914
+ const imported = reader.importsIn(source).find((item) => item.name === original);
915
+ if (!imported)
916
+ return { name: original, from: null };
917
+ return imported.imported === "default" || imported.imported === "*" ? null : { name: imported.imported, from: imported.from };
918
+ }
919
+ return null;
920
+ }
921
+ #filesOf(owner, name, near) {
922
+ const isNear = (fileName) => {
923
+ const relative = this.#source.relative(fileName);
924
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
925
+ };
926
+ if (!identifierPattern.test(name))
927
+ return [];
928
+ const candidates = this.#filesIn(owner).filter(([, words]) => words.includes(`
929
+ ${name}
930
+ `)).map(([fileName]) => fileName);
931
+ return [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))];
932
+ }
933
+ #mapEntriesOf(objectName, member, fileName, owner) {
934
+ const key = `${objectName}
935
+ ${member}
936
+ ${fileName ?? ""}
937
+ ${owner ?? ""}`;
938
+ const cached = this.#mapEntries.get(key);
939
+ if (cached)
940
+ return cached;
941
+ const { ts, reader } = this.#source;
942
+ const entries = [];
943
+ const fileNames = [
944
+ ...fileName ? [fileName] : [],
945
+ ...(owner === null ? [] : this.#filesIn(owner)).filter(([other, words]) => other !== fileName && words.includes(`
946
+ ${objectName}
947
+ `) && words.includes(`
948
+ ${member}
949
+ `)).map(([other]) => other)
950
+ ];
951
+ for (const holder of fileNames) {
952
+ const text = this.#source.readText(holder);
953
+ if (text === undefined)
954
+ continue;
955
+ const source = this.#source.parse(holder, text);
956
+ const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
957
+ for (const statement of source.statements) {
958
+ if (!ts.isVariableStatement(statement))
959
+ continue;
960
+ const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
961
+ const object = map && reader.unwrap(map);
962
+ if (!object || !ts.isObjectLiteralExpression(object))
963
+ continue;
964
+ for (const property of object.properties) {
965
+ if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
966
+ entries.push({ name: member, site });
967
+ if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
968
+ continue;
969
+ const value = reader.unwrap(property.initializer);
970
+ if (ts.isIdentifier(value))
971
+ entries.push({ name: value.text, site });
972
+ else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
973
+ entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
974
+ }
975
+ }
976
+ }
977
+ this.#mapEntries.set(key, entries);
978
+ return entries;
979
+ }
980
+ declarationOf(owner, name, near, depth = 0) {
981
+ const key = `${owner}
982
+ ${name}
983
+ ${near ?? ""}`;
984
+ const cached = this.#declarations.get(key);
985
+ if (cached !== undefined)
986
+ return cached;
987
+ this.#declarations.set(key, null);
988
+ const declaring = declaringPatternOf(name, false);
989
+ let found = null;
990
+ for (const fileName of this.#filesOf(owner, name, near)) {
991
+ const text = this.#source.readText(fileName);
992
+ if (text === undefined || !declaring.test(text))
993
+ continue;
994
+ const source = this.#source.parse(fileName, text);
995
+ const declared = this.#routes.declarationIn(source, name);
996
+ if (declared) {
997
+ found = { source, ...declared, name };
998
+ break;
999
+ }
1000
+ const reexport = this.#reexportIn(source, name);
1001
+ if (!reexport)
1002
+ continue;
1003
+ if (!reexport.from) {
1004
+ const local = this.#routes.declarationIn(source, reexport.name);
1005
+ found = local ? { source, ...local, name: reexport.name } : null;
1006
+ break;
1007
+ }
1008
+ const next = this.ownerOfSpecifier(fileName, reexport.from);
1009
+ if (next && next !== frameworkOwner && depth < maxMapDepth)
1010
+ found = this.declarationOf(next, reexport.name, this.pathOfSpecifier(fileName, reexport.from), depth + 1);
1011
+ break;
1012
+ }
1013
+ this.#declarations.set(key, found);
1014
+ return found;
1015
+ }
1016
+ #throughStar(declared, owner, name, near) {
1017
+ if (!declared.length)
1018
+ return;
1019
+ const { owners, isOpaque } = this.starExportsOf(owner, near);
1020
+ const reached = declared.filter((other) => other.owner !== null && owners.includes(other.owner));
1021
+ return reached.length === 1 && !isOpaque && !this.declares(owner, name) ? reached[0] : undefined;
1022
+ }
1023
+ starExportsOf(owner, near) {
1024
+ const key = `${owner}
1025
+ ${near}`;
1026
+ const cached = this.#starExports.get(key);
1027
+ if (cached)
1028
+ return cached;
1029
+ const found = { owners: [], packages: [], paths: [near], isOpaque: false };
1030
+ this.#starExports.set(key, found);
1031
+ for (let idx = 0;idx < found.paths.length; idx++)
1032
+ for (const fileName of this.#modulesAt(found.paths[idx] ?? ""))
1033
+ for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? []) {
1034
+ const target = this.ownerOfSpecifier(fileName, specifier);
1035
+ const path = this.pathOfSpecifier(fileName, specifier);
1036
+ if (path === null && !found.packages.includes(specifier))
1037
+ found.packages.push(specifier);
1038
+ if (target === undefined || target === frameworkOwner || path === null)
1039
+ found.isOpaque = true;
1040
+ else if (!found.paths.includes(path))
1041
+ found.paths.push(path);
1042
+ if (target && target !== frameworkOwner && target !== owner && !found.owners.includes(target))
1043
+ found.owners.push(target);
1044
+ }
1045
+ return found;
1046
+ }
1047
+ exportsAt(path, name) {
1048
+ const key = `${path}
1049
+ ${name}`;
1050
+ const cached = this.#moduleExports.get(key);
1051
+ if (cached)
1052
+ return cached;
1053
+ const found = { named: null, stars: [] };
1054
+ const exporting = exportingPatternOf(name);
1055
+ for (const fileName of this.#modulesAt(path)) {
1056
+ const text = this.#source.readText(fileName);
1057
+ if (text === undefined)
1058
+ continue;
1059
+ const named = identifierPattern.test(name) && exporting.test(text) ? this.#reexportIn(this.#source.parse(fileName, text), name) : null;
1060
+ if (named) {
1061
+ found.named = { ...named, fileName };
1062
+ found.stars = [];
1063
+ break;
1064
+ }
1065
+ for (const [, specifier = ""] of text.matchAll(starExportPattern)) {
1066
+ const target = this.pathOfSpecifier(fileName, specifier);
1067
+ if (target !== null && !found.stars.includes(target))
1068
+ found.stars.push(target);
1069
+ }
1070
+ }
1071
+ this.#moduleExports.set(key, found);
1072
+ return found;
1073
+ }
1074
+ #modulesAt(path) {
1075
+ if (!this.#modules) {
1076
+ this.#modules = new Map;
1077
+ for (const [fileName] of this.#source.files) {
1078
+ const module = this.#source.relative(fileName).replace(/\.tsx?$/, "");
1079
+ for (const key of module.endsWith("/index") ? [module, module.slice(0, -6)] : [module])
1080
+ this.#modules.set(key, [...this.#modules.get(key) ?? [], fileName]);
1081
+ }
1082
+ }
1083
+ return this.#modules.get(path) ?? [];
1084
+ }
1085
+ hasModule(path) {
1086
+ return this.#modulesAt(path).length > 0;
1087
+ }
1088
+ packagesUnder(owner, folder) {
1089
+ const packages = new Set;
1090
+ for (const [fileName, words] of this.#filesIn(owner)) {
1091
+ if (!this.#source.relative(fileName).startsWith(`${folder}/`) || !words.includes(`
1092
+ export
1093
+ `))
1094
+ continue;
1095
+ for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? [])
1096
+ if (this.pathOfSpecifier(fileName, specifier) === null)
1097
+ packages.add(specifier);
1098
+ }
1099
+ return [...packages];
1100
+ }
1101
+ declares(owner, name) {
1102
+ const declaring = declaringPatternOf(name, true);
1103
+ return this.#filesOf(owner, name, null).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
1104
+ }
1105
+ declaresAt(path, name) {
1106
+ const declaring = declaringPatternOf(name, true);
1107
+ return this.#modulesAt(path).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
1108
+ }
1109
+ #filesIn(owner) {
1110
+ if (!this.#ownerFiles) {
1111
+ this.#ownerFiles = new Map;
1112
+ for (const entry of this.#source.files) {
1113
+ const fileOwner = testFilePattern.test(entry[0]) ? null : this.#source.ownerOf(entry[0]);
1114
+ const files = fileOwner === null ? null : this.#ownerFiles.get(fileOwner);
1115
+ if (files)
1116
+ files.push(entry);
1117
+ else if (fileOwner !== null)
1118
+ this.#ownerFiles.set(fileOwner, [entry]);
1119
+ }
1120
+ }
1121
+ return this.#ownerFiles.get(owner) ?? [];
1122
+ }
1123
+ #nearest(items, fileOf, near) {
1124
+ const isNear = (fileName) => {
1125
+ const relative = this.#source.relative(fileName);
1126
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1127
+ };
1128
+ return items.find((item) => isNear(fileOf(item))) ?? items[0];
1129
+ }
1130
+ inOwner(fileName, owner) {
1131
+ const relative = this.#source.relative(fileName);
1132
+ return owner && relative.startsWith(`${owner}/`) ? relative.slice(owner.length + 1) : relative;
1133
+ }
1134
+ ownerOfSpecifier(fileName, specifier) {
1135
+ if (frameworkPattern.test(specifier))
1136
+ return frameworkOwner;
1137
+ const path = this.pathOfSpecifier(fileName, specifier);
1138
+ return path === null ? undefined : ownerOf(path) ?? undefined;
1139
+ }
1140
+ pathOfSpecifier(fileName, specifier) {
1141
+ const alias = specifier.match(aliasPattern);
1142
+ if (alias)
1143
+ return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1144
+ if (!specifier.startsWith("."))
1145
+ return null;
1146
+ const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1147
+ for (const segment of specifier.split("/"))
1148
+ if (segment === "..")
1149
+ segments.pop();
1150
+ else if (segment !== ".")
1151
+ segments.push(segment);
1152
+ return segments.join("/");
1153
+ }
1154
+ visible(target, from) {
1155
+ return !from || !target || target === from || target.startsWith("libs/");
1156
+ }
1157
+ }
1158
+
1159
+ // pkgs/@akanjs/language/adaptorResolver.ts
1160
+ var maxModules = 64;
1161
+
1162
+ class AdaptorResolver {
1163
+ #source;
1164
+ #resolver;
1165
+ #sink;
1166
+ #adaptors = new Map;
1167
+ #plugs = new Map;
1168
+ #serviceFields = new Set;
1169
+ constructor(source, resolver, sink) {
1170
+ this.#source = source;
1171
+ this.#resolver = resolver;
1172
+ this.#sink = sink;
1173
+ }
1174
+ declare(facts) {
1175
+ const counts = new Map;
1176
+ for (const file of facts)
1177
+ for (const fact of file.adaptors) {
1178
+ const key = this.#sink.key("adaptor", file.owner, fact.name);
1179
+ counts.set(key, (counts.get(key) ?? 0) + 1);
1180
+ }
1181
+ const declared = facts.flatMap((file) => file.adaptors.map((fact) => {
1182
+ const key = this.#sink.key("adaptor", file.owner, fact.name);
1183
+ const id = (counts.get(key) ?? 0) > 1 ? this.#sink.key("adaptor", file.owner, `${this.#resolver.inOwner(file.fileName, file.owner)}#${fact.name}`) : key;
1184
+ const adaptor = { fact, owner: file.owner, fileName: file.fileName, id };
1185
+ this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], adaptor]);
1186
+ this.#nodeOf(adaptor);
1187
+ return { adaptor, file };
1188
+ }));
1189
+ for (const { adaptor, file } of declared)
1190
+ for (const plug of adaptor.fact.plugs)
1191
+ this.#sink.addEdge(adaptor.id, this.#idOf(plug.target, file), "plugs", plug.name, this.#sink.location(plug.span));
1192
+ for (const file of facts)
1193
+ for (const binding of file.adaptorBindings)
1194
+ this.#sink.addEdge(this.#idOf(binding.role, file), this.#idOf(binding.adaptor, file), "binds", file.owner, this.#sink.location(binding.span));
1195
+ }
1196
+ addService(refName, service, file) {
1197
+ for (const plug of service.plugs) {
1198
+ const key = `${refName}
1199
+ ${plug.name}`;
1200
+ this.#plugs.set(key, [
1201
+ ...this.#plugs.get(key) ?? [],
1202
+ { owner: file.owner, target: this.#idOf(plug.target, file) }
1203
+ ]);
1204
+ }
1205
+ for (const field of service.fields)
1206
+ this.#serviceFields.add(`${file.owner ?? ""}
1207
+ ${refName}
1208
+ ${field}`);
1209
+ }
1210
+ pluggedOf(owner, module, field) {
1211
+ const plugs = this.#plugs.get(`${module}
1212
+ ${field}`) ?? [];
1213
+ const own = plugs.filter((plug) => plug.owner === owner);
1214
+ if (!own.length && this.#serviceFields.has(`${owner ?? ""}
1215
+ ${module}
1216
+ ${field}`))
1217
+ return [];
1218
+ const inherited = own.length ? own : plugs.filter((plug) => this.#resolver.visible(plug.owner, owner));
1219
+ return [...new Set(inherited.map(({ target }) => target))];
1220
+ }
1221
+ #idOf(local, file) {
1222
+ const [head, member] = local.split(".");
1223
+ const imported = file.imports.find((item) => item.name === head);
1224
+ const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1225
+ const path = imported ? this.#resolver.pathOfSpecifier(file.fileName, imported.from) : null;
1226
+ if (imported && path === null && !frameworkPattern.test(imported.from))
1227
+ return this.#packageAdaptorOf(name, imported.from);
1228
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
1229
+ const declared = this.#adaptors.get(name) ?? [];
1230
+ const holder = from === undefined ? file.owner : from;
1231
+ const own = this.#adaptorIn(name, declared, holder, path, file.fileName);
1232
+ if (own && "from" in own)
1233
+ return this.#packageAdaptorOf(own.name, own.from);
1234
+ const found = own === undefined && from !== frameworkOwner ? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, holder)) : own;
1235
+ if (found)
1236
+ return found.id;
1237
+ const reexported = own === undefined && from && from !== frameworkOwner ? this.#packageShimOf(from, name, path) : null;
1238
+ if (reexported)
1239
+ return this.#packageAdaptorOf(reexported.name, reexported.from);
1240
+ const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
1241
+ const detail = from === frameworkOwner ? [frameworkOwner] : [];
1242
+ return this.#sink.addNode(this.#sink.key("adaptor", owner, name), "adaptor", owner, name, null, detail).id;
1243
+ }
1244
+ #adaptorIn(name, declared, owner, path, fileName) {
1245
+ const own = declared.filter((adaptor) => adaptor.owner === owner);
1246
+ const [first] = own;
1247
+ if (path === null)
1248
+ return own.find((adaptor) => adaptor.fileName === fileName) ?? first;
1249
+ if (own.length === 1)
1250
+ return first;
1251
+ if (!this.#resolver.hasModule(path))
1252
+ return own.find((adaptor) => this.#source.relative(adaptor.fileName).startsWith(`${path}/`)) ?? first;
1253
+ const exported = this.#exportedAt(name, path, new Set).filter((item) => first || ("fact" in item));
1254
+ const [only] = exported;
1255
+ if (only)
1256
+ return exported.length === 1 ? only : null;
1257
+ return first ? null : undefined;
1258
+ }
1259
+ #exportedAt(name, path, seen) {
1260
+ const key = `${path}
1261
+ ${name}`;
1262
+ if (seen.has(key) || seen.size >= maxModules)
1263
+ return [];
1264
+ seen.add(key);
1265
+ const here = (this.#adaptors.get(name) ?? []).filter((adaptor) => {
1266
+ const module = this.#source.relative(adaptor.fileName).replace(/\.tsx?$/, "");
1267
+ return module === path || module === `${path}/index`;
1268
+ });
1269
+ if (here.length)
1270
+ return here;
1271
+ const { named, stars } = this.#resolver.exportsAt(path, name);
1272
+ if (named?.from && this.#resolver.pathOfSpecifier(named.fileName, named.from) === null)
1273
+ return [{ name: named.name === "default" ? name : named.name, from: named.from }];
1274
+ if (named) {
1275
+ const target = named.from === null ? path : this.#resolver.pathOfSpecifier(named.fileName, named.from);
1276
+ return target === null ? [] : this.#exportedAt(named.name, target, seen);
1277
+ }
1278
+ return [...new Set(stars.flatMap((star) => this.#exportedAt(name, star, seen)))];
1279
+ }
1280
+ #packageAdaptorOf(name, specifier) {
1281
+ if (frameworkPattern.test(specifier))
1282
+ return this.#sink.addNode(this.#sink.key("adaptor", null, name), "adaptor", null, name, null, [frameworkOwner]).id;
1283
+ const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
1284
+ return this.#sink.addNode(this.#sink.key("adaptor", pkg, name), "adaptor", null, name, null, [pkg]).id;
1285
+ }
1286
+ #packageShimOf(owner, name, near) {
1287
+ const reexport = this.#resolver.reexportOf(owner, name, near);
1288
+ const [shim] = reexport?.site.imports ?? [];
1289
+ if (reexport && shim && this.#resolver.pathOfSpecifier(reexport.site.fileName, shim.from) === null)
1290
+ return { name: reexport.local === "default" ? name : reexport.local, from: shim.from };
1291
+ if (reexport || !near)
1292
+ return null;
1293
+ const { packages, paths } = this.#resolver.hasModule(near) ? this.#resolver.starExportsOf(owner, near) : { packages: this.#resolver.packagesUnder(owner, near), paths: [] };
1294
+ const [only] = packages;
1295
+ const isDeclared = this.#resolver.declares(owner, name) || paths.some((path) => this.#resolver.declaresAt(path, name));
1296
+ return packages.length === 1 && only && !isDeclared ? { name, from: only } : null;
1297
+ }
1298
+ #nodeOf({ fact, owner, fileName, id }) {
1299
+ const detail = fact.refName ? [fact.refName] : [];
1300
+ const node = this.#sink.addNode(id, "adaptor", owner, fact.name, fact.span, detail);
1301
+ if (fact.doc && !node.doc)
1302
+ node.doc = fact.doc;
1303
+ this.#sink.digest(node, this.#source.readText(fileName), fact.extent);
1304
+ return node.id;
1305
+ }
1306
+ }
515
1307
 
516
1308
  // pkgs/akanjs/common/trimSlashes.ts
517
1309
  var trimTrailingSlashes = (value) => {
@@ -545,7 +1337,8 @@ var ROOT_LAYOUT_ROUTE_EXPORTS = new Set([
545
1337
  "wsConnect",
546
1338
  "layoutStyle",
547
1339
  "viewport",
548
- "McpAppRoot"
1340
+ "McpAppRoot",
1341
+ "LevelBoundary"
549
1342
  ]);
550
1343
  var RESERVED_ROUTE_CONFIG_EXPORTS = new Set([...ROOT_LAYOUT_ROUTE_EXPORTS].filter((name) => name !== "default" && !/^[A-Z]/.test(name)));
551
1344
  function parseRouteModuleKey(key) {
@@ -660,24 +1453,23 @@ rootLayout
660
1453
  }
661
1454
  declarationIn(source, name) {
662
1455
  const ts = this.#ts;
1456
+ const overloads = source.statements.filter((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === name);
1457
+ const [first] = overloads;
1458
+ const last = overloads.at(-1);
1459
+ if (first?.name && last)
1460
+ return { statement: first, declared: first.name, extent: { start: first.getStart(source), end: last.getEnd() } };
663
1461
  for (const statement of source.statements) {
664
- if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
665
- return { statement, declared: statement.name, initializer: null };
666
1462
  if (!ts.isVariableStatement(statement))
667
1463
  continue;
668
1464
  const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
669
1465
  if (declaration)
670
- return { statement, declared: declaration.name, initializer: declaration.initializer ?? null };
1466
+ return { statement, declared: declaration.name, extent: this.#reader.extentOf(source, statement) };
671
1467
  }
672
1468
  return null;
673
1469
  }
674
1470
  #guardOf(source, exported) {
675
1471
  const ts = this.#ts;
676
1472
  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
1473
  while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
682
1474
  const { name, expression } = current.expression;
683
1475
  const [stage] = current.arguments;
@@ -689,9 +1481,20 @@ rootLayout
689
1481
  }
690
1482
  #callsIn(source, stage) {
691
1483
  const ts = this.#ts;
692
- const imported = new Set(this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from)).map(({ name }) => name));
1484
+ const imports = this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from));
1485
+ const imported = new Set(imports.filter(({ imported }) => imported !== "*").map(({ name }) => name));
1486
+ const namespaces = new Set(imports.filter(({ imported }) => imported === "*").map(({ name }) => name));
693
1487
  const calls = new Map;
694
- const callOf = (name) => {
1488
+ const callOf = (callee, shadowed) => {
1489
+ if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text) && !shadowed.has(callee.expression.text)) {
1490
+ const name = `${callee.expression.text}.${callee.name.text}`;
1491
+ if (!calls.has(name))
1492
+ calls.set(name, { name, local: null });
1493
+ return true;
1494
+ }
1495
+ if (!ts.isIdentifier(callee) || shadowed.has(callee.text))
1496
+ return false;
1497
+ const name = callee.text;
695
1498
  if (calls.has(name))
696
1499
  return true;
697
1500
  const declared = this.declarationIn(source, name);
@@ -701,23 +1504,71 @@ rootLayout
701
1504
  name,
702
1505
  local: declared && {
703
1506
  span: this.#reader.spanOf(source, declared.declared),
704
- extent: this.#reader.extentOf(source, declared.statement),
1507
+ extent: declared.extent,
705
1508
  doc: this.#reader.docOf(source, declared.statement)
706
1509
  }
707
1510
  });
708
1511
  return true;
709
1512
  };
710
- const visit = (node) => {
711
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && callOf(node.expression.text))
1513
+ const visit = (node, outer) => {
1514
+ const shadowed = this.#scopeOf(node, outer);
1515
+ if (ts.isCallExpression(node) && callOf(node.expression, shadowed))
712
1516
  return;
713
- ts.forEachChild(node, visit);
1517
+ ts.forEachChild(node, (child) => visit(child, shadowed));
714
1518
  };
715
1519
  if (ts.isIdentifier(stage))
716
- callOf(stage.text);
1520
+ callOf(stage, new Set);
717
1521
  else
718
- visit(stage);
1522
+ visit(stage, new Set);
719
1523
  return [...calls.values()];
720
1524
  }
1525
+ #scopeOf(node, outer) {
1526
+ const ts = this.#ts;
1527
+ const names = [];
1528
+ const bind = (name) => {
1529
+ if (ts.isIdentifier(name))
1530
+ names.push(name.text);
1531
+ else
1532
+ for (const element of name.elements)
1533
+ if (!ts.isOmittedExpression(element))
1534
+ bind(element.name);
1535
+ };
1536
+ const declare = (statement) => {
1537
+ if (ts.isVariableStatement(statement))
1538
+ for (const declaration of statement.declarationList.declarations)
1539
+ bind(declaration.name);
1540
+ else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
1541
+ names.push(statement.name.text);
1542
+ };
1543
+ const hoist = (child) => {
1544
+ if (ts.isFunctionLike(child) || ts.isClassLike(child))
1545
+ return;
1546
+ if (ts.isVariableDeclarationList(child) && !(child.flags & ts.NodeFlags.BlockScoped))
1547
+ for (const declaration of child.declarations)
1548
+ bind(declaration.name);
1549
+ ts.forEachChild(child, hoist);
1550
+ };
1551
+ if (ts.isFunctionLike(node)) {
1552
+ for (const parameter of node.parameters)
1553
+ bind(parameter.name);
1554
+ if (ts.isFunctionExpression(node) && node.name)
1555
+ names.push(node.name.text);
1556
+ if ("body" in node && node.body)
1557
+ ts.forEachChild(node.body, hoist);
1558
+ } else if (ts.isClassExpression(node) && node.name)
1559
+ names.push(node.name.text);
1560
+ else if (ts.isBlock(node))
1561
+ node.statements.forEach(declare);
1562
+ else if (ts.isCaseBlock(node))
1563
+ for (const clause of node.clauses)
1564
+ clause.statements.forEach(declare);
1565
+ else if (ts.isCatchClause(node) && node.variableDeclaration)
1566
+ bind(node.variableDeclaration.name);
1567
+ else if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer))
1568
+ for (const declaration of node.initializer.declarations)
1569
+ bind(declaration.name);
1570
+ return names.length ? new Set([...outer, ...names]) : outer;
1571
+ }
721
1572
  }
722
1573
 
723
1574
  // pkgs/@akanjs/language/akanGraph.ts
@@ -733,10 +1584,6 @@ var builtinGuards = {
733
1584
  Public: { scope: "account", doc: "Passes every caller, signed in or not." },
734
1585
  None: { scope: "account", doc: "Refuses every caller." }
735
1586
  };
736
- var frameworkOwner = "akanjs";
737
- var frameworkPattern = /^akanjs(?:\/|$)/;
738
- var aliasPattern = /^@(apps|libs)\/([^/]+)/;
739
- var maxMapDepth = 4;
740
1587
 
741
1588
  class GraphBuilder {
742
1589
  #source;
@@ -744,16 +1591,21 @@ class GraphBuilder {
744
1591
  #edges = new Map;
745
1592
  #contexts = new Map;
746
1593
  #members = new Map;
747
- #guards = new Map;
748
- #mapEntries = new Map;
749
1594
  #routeFiles = new Map;
750
- #declarations = new Map;
751
1595
  #routes;
752
- #adaptors = new Map;
753
- #plugs = new Map;
1596
+ #resolver;
1597
+ #adaptors;
754
1598
  constructor(source) {
755
1599
  this.#source = source;
756
1600
  this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
1601
+ this.#resolver = new GuardResolver(source, this.#routes);
1602
+ this.#adaptors = new AdaptorResolver(source, this.#resolver, {
1603
+ key: (kind, owner, scope) => this.#key(kind, owner, scope),
1604
+ addNode: (id, kind, owner, name, span, detail) => this.#addNode(id, kind, owner, null, name, span, detail),
1605
+ addEdge: (from, to, kind, label, at) => this.#addEdge(from, to, kind, label, at),
1606
+ digest: (node, text, extent) => this.#digest(node, text, extent),
1607
+ location: (span) => this.#location(span)
1608
+ });
757
1609
  }
758
1610
  build() {
759
1611
  const { facts } = this.#source;
@@ -765,17 +1617,9 @@ class GraphBuilder {
765
1617
  ...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
766
1618
  ];
767
1619
  const typed = [];
768
- for (const file of facts)
769
- for (const fact of file.adaptors) {
770
- const declared = { fact, owner: file.owner, fileName: file.fileName };
771
- this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
772
- this.#adaptorNodeOf(declared);
773
- }
774
- for (const file of facts)
775
- for (const fact of file.adaptors)
776
- for (const plug of fact.plugs)
777
- 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);
1620
+ this.#adaptors.declare(facts);
1621
+ for (const guard of this.#resolver.declare(facts))
1622
+ this.#guardNodeOf(guard);
779
1623
  for (const file of facts) {
780
1624
  const { owner } = file;
781
1625
  const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
@@ -806,10 +1650,7 @@ class GraphBuilder {
806
1650
  }
807
1651
  for (const method of service.methods)
808
1652
  this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
809
- for (const plug of service.plugs)
810
- this.#plugs.set(`${owner ?? ""}
811
- ${refName}
812
- ${plug.name}`, this.#adaptorIdOf(plug.target, file));
1653
+ this.#adaptors.addService(refName, service, file);
813
1654
  }
814
1655
  for (const filter of file.filters) {
815
1656
  if (!filter.name.endsWith("Filter"))
@@ -819,31 +1660,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
819
1660
  for (const query of filter.queries)
820
1661
  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
1662
  }
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
- }
1663
+ this.#addGuarded(file);
847
1664
  for (const internal of file.internals)
848
1665
  for (const key of internal.keys)
849
1666
  if (internal.model && key.type !== "resolveField")
@@ -859,7 +1676,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
859
1676
  types.set(node.label, [...types.get(node.label) ?? [], node]);
860
1677
  for (const node of typed)
861
1678
  for (const field of node.fields ?? []) {
862
- const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#visible(type.owner, node.owner));
1679
+ const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#resolver.visible(type.owner, node.owner));
863
1680
  field.ref = (candidates.find((type) => type.owner === node.owner) ?? candidates[0])?.id ?? null;
864
1681
  if (field.ref)
865
1682
  this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
@@ -941,61 +1758,52 @@ ${label ?? ""}`;
941
1758
  else
942
1759
  this.#edges.set(key, { from, to, kind, label, count: 1, at });
943
1760
  }
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);
1761
+ #addGuarded(file) {
1762
+ const { owner } = file;
1763
+ for (const slice of file.slices) {
1764
+ const refName = slice.model;
1765
+ if (!refName)
1766
+ continue;
1767
+ const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards.map((guard) => guardLabelOf(guard, file.guardLabels)));
1768
+ for (const guard of slice.guards)
1769
+ this.#guard(fallback, guard, file);
1770
+ for (const key of slice.keys) {
1771
+ const detail = [
1772
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels)),
1773
+ ...key.live ? ["live"] : []
1774
+ ];
1775
+ const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1776
+ for (const guard of key.guards)
1777
+ this.#guard(node, guard, file);
976
1778
  }
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);
1779
+ }
1780
+ for (const endpoint of file.endpoints) {
1781
+ const refName = endpoint.model;
1782
+ if (!refName)
1783
+ continue;
1784
+ for (const key of endpoint.keys) {
1785
+ const detail = [
1786
+ ...key.verb ? [key.verb] : [],
1787
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels))
1788
+ ];
1789
+ const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1790
+ for (const guard of key.guards)
1791
+ this.#guard(node, guard, file);
986
1792
  }
987
1793
  }
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);
1794
+ }
1795
+ #guard(node, guard, file) {
1796
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
1797
+ if (!local || local === "?")
1798
+ return;
1799
+ const id = local.includes("@") ? this.#madeInPlaceIdOf(local, file) : this.#guardIdOf(local, file);
1800
+ if (id)
1801
+ this.#addEdge(node.id, id, "guard", role, node.at);
996
1802
  }
997
1803
  #guardIdOf(local, site) {
998
- const found = this.#findGuard(local, site, 0);
1804
+ const found = this.#resolver.find(local, site);
1805
+ if ("external" in found)
1806
+ return null;
999
1807
  if ("guard" in found)
1000
1808
  return this.#guardNodeOf(found.guard);
1001
1809
  const { name, from } = found;
@@ -1008,99 +1816,18 @@ ${label ?? ""}`;
1008
1816
  node.doc = builtin.doc;
1009
1817
  return node.id;
1010
1818
  }
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;
1819
+ #madeInPlaceIdOf(name, file) {
1820
+ const made = this.#resolver.madeInPlace(file.fileName, name);
1821
+ if (made)
1822
+ return this.#guardNodeOf(made);
1823
+ const head = name.slice(name.indexOf(":") + 1, name.indexOf("@")).split(".")[0] ?? name;
1824
+ const imported = file.imports.find((item) => item.name === head);
1825
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : file.owner;
1826
+ if (from === undefined || from === frameworkOwner)
1827
+ return null;
1828
+ const scope = `${this.#resolver.inOwner(file.fileName, file.owner)}#${name}`;
1829
+ const label = guardLabelOf(name, file.guardLabels);
1830
+ return this.#addNode(this.#key("guard", file.owner, scope), "guard", file.owner, null, name, null, [], label).id;
1104
1831
  }
1105
1832
  #addRoutes() {
1106
1833
  const routes = [];
@@ -1153,22 +1880,23 @@ ${dir}`, node);
1153
1880
  }
1154
1881
  #routeGuardIdOf({ name: local, local: declared }, fact, text) {
1155
1882
  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}`);
1883
+ const id = this.#key("guard", fact.owner, `${this.#resolver.inOwner(fact.fileName, fact.owner)}#${local}`);
1158
1884
  return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
1159
1885
  }
1160
- const imported = fact.imports.find((item) => item.name === local);
1161
- if (!imported)
1886
+ const [head = local, member] = local.split(".");
1887
+ const imported = fact.imports.find((item) => item.name === head);
1888
+ if (!imported || member && imported.imported !== "*")
1162
1889
  return null;
1163
- const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
1164
- const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
1890
+ const name = member ?? (imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local);
1891
+ const from = this.#resolver.ownerOfSpecifier(fact.fileName, imported.from);
1165
1892
  if (!from || from === frameworkOwner)
1166
1893
  return null;
1167
- const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
1894
+ const found = this.#resolver.declarationOf(from, name, this.#resolver.pathOfSpecifier(fact.fileName, imported.from));
1168
1895
  if (!found)
1169
1896
  return null;
1170
1897
  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));
1898
+ const owner = this.#source.ownerOf(found.source.fileName) ?? from;
1899
+ 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
1900
  }
1173
1901
  #routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
1174
1902
  const isNew = !this.#nodes.has(id);
@@ -1178,60 +1906,22 @@ ${dir}`, node);
1178
1906
  this.#digest(node, text, extent);
1179
1907
  return node.id;
1180
1908
  }
1181
- #adaptorIdOf(local, file) {
1182
- const [head, member] = local.split(".");
1183
- const imported = file.imports.find((item) => item.name === head);
1184
- const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
1185
- const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1186
- 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)));
1188
- if (found)
1189
- return this.#adaptorNodeOf(found);
1190
- const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
1191
- const detail = from === frameworkOwner ? [frameworkOwner] : [];
1192
- return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
1193
- }
1194
- #adaptorNodeOf({ fact, owner, fileName }) {
1195
- const id = this.#key("adaptor", owner, fact.name);
1196
- const detail = fact.refName ? [fact.refName] : [];
1197
- const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
1198
- if (fact.doc && !node.doc)
1199
- node.doc = fact.doc;
1200
- this.#digest(node, this.#source.readText(fileName), fact.extent);
1201
- return node.id;
1202
- }
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] : []);
1909
+ #guardNodeOf({ name, owner, span, scope, doc, parts, file, label }) {
1910
+ const id = this.#key("guard", owner, file ? `${file}#${name}` : name);
1911
+ const node = this.#addNode(id, "guard", owner, null, name, span, scope ? [scope] : [], label ?? name);
1205
1912
  if (doc && !node.doc)
1206
1913
  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;
1914
+ if (node.digest)
1915
+ return node.id;
1916
+ const digest = this.#hashOf(...parts.map(({ fileName, extent }) => [this.#source.readText(fileName), extent]));
1917
+ if (!digest)
1918
+ return node.id;
1919
+ node.digest = digest;
1920
+ const sources = [...new Set(parts.map(({ fileName }) => this.#source.relative(fileName)))].filter((source) => source !== node.at?.file);
1921
+ if (sources.length)
1922
+ node.sources = sources;
1213
1923
  return node.id;
1214
1924
  }
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
1925
  #moduleDirOf(fileName) {
1236
1926
  const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
1237
1927
  return dir?.startsWith("_") && !dir.startsWith("__") ? dir.slice(1) : dir;
@@ -1267,20 +1957,13 @@ ${dir}`, node);
1267
1957
  visit(constant);
1268
1958
  return [...fields.values()];
1269
1959
  }
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
1960
  #targetNodeOf({ family, owner, refName, sourceKey }) {
1278
1961
  const isDefault = /^[A-Z]/.test(sourceKey);
1279
1962
  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
1963
  return id && this.#nodes.has(id) ? id : null;
1281
1964
  }
1282
1965
  #membersNamed(kind, name, owner, refName) {
1283
- const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#visible(node.owner, owner));
1966
+ const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#resolver.visible(node.owner, owner));
1284
1967
  const own = nodes.filter((node) => node.owner === owner);
1285
1968
  return (own.length ? own : nodes).map((node) => node.id);
1286
1969
  }
@@ -1325,10 +2008,8 @@ st
1325
2008
  visit(source);
1326
2009
  }
1327
2010
  #link(fileName, source, identifier, chain, context, owner) {
1328
- const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#plugs.get(`${context.owner ?? ""}
1329
- ${context.module}
1330
- ${chain[1]}`) : undefined;
1331
- const resolved = plugged ? [plugged] : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
2011
+ const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#adaptors.pluggedOf(context.owner, context.module, chain[1]) : [];
2012
+ const resolved = plugged.length ? plugged : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
1332
2013
  const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
1333
2014
  if (!targets.length)
1334
2015
  return;
@@ -1634,6 +2315,10 @@ var endpointNames = (key) => {
1634
2315
  };
1635
2316
 
1636
2317
  // pkgs/@akanjs/language/nameTable.ts
2318
+ var guardDetailOf = (facts, guards) => {
2319
+ const labels = Object.assign({}, ...facts.map((file) => file.guardLabels));
2320
+ return guards.length ? [`guards: ${guards.map((guard) => guardLabelOf(guard, labels)).join(", ")}`] : [];
2321
+ };
1637
2322
  var capitalize3 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
1638
2323
 
1639
2324
  class NameTable {
@@ -1733,7 +2418,7 @@ class NameTable {
1733
2418
  role: "sig",
1734
2419
  gate: "sig",
1735
2420
  label: sliceClass.name,
1736
- detail: sliceClass.guards.length ? [`guards: ${sliceClass.guards.join(", ")}`] : []
2421
+ detail: guardDetailOf(facts, sliceClass.guards)
1737
2422
  });
1738
2423
  if (modelDoc)
1739
2424
  this.#add({
@@ -1798,7 +2483,7 @@ class NameTable {
1798
2483
  sourceKey: slice.name,
1799
2484
  span: slice.span,
1800
2485
  label: `slice ${refName} (default)`,
1801
- detail: slice.guards.length ? [`guards: ${slice.guards.join(", ")}`] : []
2486
+ detail: guardDetailOf(facts, slice.guards)
1802
2487
  };
1803
2488
  this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
1804
2489
  for (const key of slice.keys)
@@ -1808,7 +2493,7 @@ class NameTable {
1808
2493
  sourceKey: key.name,
1809
2494
  span: key.span,
1810
2495
  label: `slice ${key.name}`,
1811
- detail: [...key.guards.length ? [`guards: ${key.guards.join(", ")}`] : [], ...key.live ? ["live"] : []]
2496
+ detail: [...guardDetailOf(facts, key.guards), ...key.live ? ["live"] : []]
1812
2497
  }, refName, key.name, modelNameSet, slice.name);
1813
2498
  }
1814
2499
  if (filterClass) {
@@ -1865,7 +2550,7 @@ class NameTable {
1865
2550
  role: key.verb === "pubsub" ? "subscribe" : "listen",
1866
2551
  gate: null,
1867
2552
  label: `${key.verb} ${key.name}`,
1868
- detail: key.guards.length ? [`guards: ${key.guards.join(", ")}`] : []
2553
+ detail: guardDetailOf(facts, key.guards)
1869
2554
  });
1870
2555
  }
1871
2556
  }
@@ -1922,15 +2607,13 @@ var sourcePattern = /\.(constant|document|service|signal|store)\.ts$/;
1922
2607
  var dictionaryPattern = /\.dictionary\.ts$/;
1923
2608
  var srvkitPattern = /\/srvkit\//;
1924
2609
  var wordPattern = /[A-Za-z_$][\w$]*/g;
1925
- var identifierPattern = /^[A-Za-z_$][\w$]*$/;
1926
- var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
2610
+ var identifierPattern2 = /^[A-Za-z_$][\w$]*$/;
1927
2611
  var stateReceivers = new Set(["use", "sel", "get", "do"]);
1928
2612
  var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
1929
2613
  var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
1930
2614
  var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
1931
2615
  var parseCacheSize = 64;
1932
2616
  var pathOf = (fileName) => fileName.replace(/\\/g, "/");
1933
- var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
1934
2617
  var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
1935
2618
 
1936
2619
  class AkanIndex {
@@ -2033,7 +2716,7 @@ ${[...new Set(text.match(wordPattern))].join(`
2033
2716
  }
2034
2717
  renameAt(givenName, offset, newKey, readText) {
2035
2718
  const sources = this.#sourceEntriesAt(pathOf(givenName), offset).filter((entry) => entry.renamed);
2036
- if (!sources.length || !identifierPattern.test(newKey))
2719
+ if (!sources.length || !identifierPattern2.test(newKey))
2037
2720
  return null;
2038
2721
  return [
2039
2722
  { span: sources[0].span, newText: newKey },
@@ -2061,6 +2744,8 @@ canPass
2061
2744
  adapt
2062
2745
  `) || words.includes(`
2063
2746
  dangerouslyAdapt
2747
+ `) || words.includes(`
2748
+ applyAdaptor
2064
2749
  `);
2065
2750
  }
2066
2751
  static #sourceKeyOf({ family, sourceKey, label, detail, span }) {