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

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/index.cjs CHANGED
@@ -60,7 +60,8 @@ var isEdited = (before, after) => before.digest !== undefined && after.digest !=
60
60
  var byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
61
61
 
62
62
  class AkanGraphDiff {
63
- static between(base, head) {
63
+ static between(given, head) {
64
+ const base = AkanGraphDiff.#renamed(given, head);
64
65
  const baseNodes = new Map(base.nodes.map((node) => [node.id, node]));
65
66
  const headNodes = new Map(head.nodes.map((node) => [node.id, node]));
66
67
  const marked = new Map;
@@ -79,6 +80,9 @@ class AkanGraphDiff {
79
80
  ...head.edges.flatMap((edge) => !baseKeys.has(edgeKeyOf(edge)) ? [{ ...edge, change: "added" }] : marked.has(edge.from) || marked.has(edge.to) ? [edge] : []),
80
81
  ...base.edges.filter((edge) => !headKeys.has(edgeKeyOf(edge))).map((edge) => ({ ...edge, change: "removed" }))
81
82
  ];
83
+ const shown = new Set([...marked.keys(), ...edges.flatMap((edge) => [edge.from, edge.to])]);
84
+ const listed = new Set(edges.map(edgeKeyOf));
85
+ edges.push(...head.edges.filter((edge) => edge.kind === "binds" && shown.has(edge.from) && !listed.has(edgeKeyOf(edge))));
82
86
  const context = edges.flatMap((edge) => [edge.from, edge.to]).filter((id) => !marked.has(id)).flatMap((id) => {
83
87
  const node = headNodes.get(id) ?? baseNodes.get(id);
84
88
  return node ? [node] : [];
@@ -90,6 +94,40 @@ class AkanGraphDiff {
90
94
  summary[node.change] += 1;
91
95
  return { version: 1, nodes, edges, summary };
92
96
  }
97
+ static #renamed(base, head) {
98
+ const baseIds = new Set(base.nodes.map((node) => node.id));
99
+ const headIds = new Set(head.nodes.map((node) => node.id));
100
+ const pairKeyOf = ({ kind, owner, name, at }) => at ? `${kind}
101
+ ${owner ?? ""}
102
+ ${name}
103
+ ${at.file}` : null;
104
+ const shortIdOf = ({ kind, owner, name }) => `${kind}:${owner ?? ""}:${name}`;
105
+ const isFileKeyed = (node) => node.id !== shortIdOf(node) && node.id.startsWith(`${node.kind}:${node.owner ?? ""}:`) && node.id.endsWith(`#${node.name}`);
106
+ const fromBase = new Map(base.nodes.flatMap((node) => {
107
+ const key = headIds.has(node.id) ? null : pairKeyOf(node);
108
+ return key ? [[key, node]] : [];
109
+ }));
110
+ const renames = new Map;
111
+ for (const node of head.nodes) {
112
+ if (baseIds.has(node.id))
113
+ continue;
114
+ const key = pairKeyOf(node);
115
+ const before = key ? fromBase.get(key) : undefined;
116
+ if (!before)
117
+ continue;
118
+ const isPair = isFileKeyed(node) ? before.id === shortIdOf(node) : isFileKeyed(before) && node.id === shortIdOf(before);
119
+ if (isPair)
120
+ renames.set(before.id, node.id);
121
+ }
122
+ if (!renames.size)
123
+ return base;
124
+ const rename = (id) => renames.get(id) ?? id;
125
+ return {
126
+ ...base,
127
+ nodes: base.nodes.map((node) => renames.has(node.id) ? { ...node, id: rename(node.id) } : node),
128
+ edges: base.edges.map((edge) => ({ ...edge, from: rename(edge.from), to: rename(edge.to) }))
129
+ };
130
+ }
93
131
  static #mark(node, change) {
94
132
  return node.fields ? { ...node, change, fields: node.fields.map((field) => ({ ...field, change })) } : { ...node, change };
95
133
  }
@@ -123,6 +161,261 @@ class AkanGraphDiff {
123
161
  return node.fields ? { ...node, fields: node.fields.map(({ digest: _field, ...field }) => field) } : node;
124
162
  }
125
163
  }
164
+ // pkgs/@akanjs/language/escapeRegExp.ts
165
+ var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
166
+ var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
167
+
168
+ // pkgs/@akanjs/language/guardFacts.ts
169
+ var guardLabelOf = (guard, labels) => {
170
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
171
+ const label = labels[local];
172
+ return label === undefined ? guard : role ? `${role} ${label}` : label;
173
+ };
174
+ var maxLabelArgs = 32;
175
+ var maxLayers = 4;
176
+ var guardRefReaderOf = (ts, ast, unwrap) => {
177
+ const pathOf = (expression) => {
178
+ if (ts.isIdentifier(expression))
179
+ return expression.text;
180
+ if (!ts.isPropertyAccessExpression(expression))
181
+ return null;
182
+ const head = pathOf(expression.expression);
183
+ return head && `${head}.${expression.name.text}`;
184
+ };
185
+ const layersOf = (call) => {
186
+ const inner = unwrap(call.expression);
187
+ if (!ts.isCallExpression(inner))
188
+ return { root: call, layers: 0 };
189
+ const { root, layers } = layersOf(inner);
190
+ return { root, layers: layers + 1 };
191
+ };
192
+ const calleeOf = (call) => pathOf(unwrap(layersOf(call).root.expression)) ?? "?";
193
+ const labelOf = (source, call) => {
194
+ const args = call.arguments.map((arg) => arg.getText(source)).join(", ");
195
+ const inner = unwrap(call.expression);
196
+ const head = ts.isCallExpression(inner) ? labelOf(source, inner) : pathOf(inner) ?? "?";
197
+ return `${head}(${args.length <= maxLabelArgs && !args.includes(`
198
+ `) ? args : "…"})`;
199
+ };
200
+ const guardRefOf = (expression, made, member) => {
201
+ const value = unwrap(expression);
202
+ if (!ts.isCallExpression(value))
203
+ return pathOf(value) ?? ast.expressionName(value);
204
+ const prefix = `${member}:${calleeOf(value)}@`;
205
+ const name = `${prefix}${made.filter((other) => other.name.startsWith(prefix)).length + 1}`;
206
+ made.push({ call: value, name });
207
+ return name;
208
+ };
209
+ const guardsIn = (source, object, made, member) => {
210
+ const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
211
+ if (!guards)
212
+ return [];
213
+ const refOf = (expression) => guardRefOf(expression, made, member);
214
+ if (ts.isArrayLiteralExpression(guards))
215
+ return guards.elements.map(refOf).filter((name) => name !== null);
216
+ if (ts.isObjectLiteralExpression(guards))
217
+ return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
218
+ const value = unwrap(property.initializer);
219
+ const names = ts.isArrayLiteralExpression(value) ? value.elements.map(refOf) : [refOf(value)];
220
+ return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
221
+ });
222
+ const single = refOf(guards);
223
+ return single ? [single] : [];
224
+ };
225
+ return { pathOf, layersOf, labelOf, guardsIn };
226
+ };
227
+ var guardFactoryReaderOf = (ts, ast, { spanOf, extentOf, unwrap, docOf }, pathOf, layersOf) => {
228
+ const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
229
+ const scopeOf = (node) => {
230
+ const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
231
+ const value = scope && unwrap(scope);
232
+ return value && ts.isStringLiteralLike(value) ? value.text : null;
233
+ };
234
+ const functionsOf = (source) => {
235
+ const functions = new Map;
236
+ for (const statement of source.statements) {
237
+ if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
238
+ functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
239
+ if (!ts.isVariableStatement(statement))
240
+ continue;
241
+ for (const declaration of statement.declarationList.declarations) {
242
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
243
+ if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
244
+ functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
245
+ }
246
+ }
247
+ return functions;
248
+ };
249
+ const returnedOf = (body, guardNames, depth = 0) => {
250
+ const locals = new Map;
251
+ const pending = [];
252
+ const visit = (node) => {
253
+ if (ts.isClassDeclaration(node) && node.name)
254
+ locals.set(node.name.text, node);
255
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
256
+ locals.set(node.name.text, node.initializer);
257
+ if (ts.isReturnStatement(node) && node.expression)
258
+ pending.push(node.expression);
259
+ if (!ts.isFunctionLike(node) && !ts.isClassLike(node))
260
+ ts.forEachChild(node, visit);
261
+ };
262
+ if (ts.isBlock(body))
263
+ ts.forEachChild(body, visit);
264
+ else
265
+ pending.push(body);
266
+ let guard = null;
267
+ const calls = [];
268
+ const seen = new Set;
269
+ const passed = new Set;
270
+ const returned = [];
271
+ for (let node = pending.pop();node; node = pending.pop()) {
272
+ if (seen.has(node))
273
+ continue;
274
+ seen.add(node);
275
+ const value = ts.isExpression(node) ? unwrap(node) : node;
276
+ if ((ts.isClassExpression(value) || ts.isClassDeclaration(value)) && implementsGuard(value, guardNames))
277
+ guard ??= value;
278
+ else if ((ts.isArrowFunction(value) || ts.isFunctionExpression(value)) && !passed.has(node) && depth < maxLayers)
279
+ returned.push(value.body);
280
+ else if (ts.isConditionalExpression(value))
281
+ pending.push(value.whenTrue, value.whenFalse);
282
+ else if (ts.isBinaryExpression(value))
283
+ pending.push(value.left, value.right);
284
+ else if (ts.isIdentifier(value)) {
285
+ const local = locals.get(value.text);
286
+ if (local)
287
+ pending.push(local);
288
+ } else if (ts.isCallExpression(value)) {
289
+ const { root, layers } = layersOf(value);
290
+ const callee = pathOf(unwrap(root.expression));
291
+ if (callee && !passed.has(node) && !calls.some((call) => call.callee === callee))
292
+ calls.push({ callee, layers });
293
+ for (let call = value;ts.isCallExpression(call); call = unwrap(call.expression)) {
294
+ for (const arg of call.arguments)
295
+ passed.add(arg);
296
+ pending.push(...call.arguments);
297
+ }
298
+ }
299
+ }
300
+ const [inner] = returned;
301
+ if (guard || calls.length || !inner)
302
+ return { guard, calls, layers: 0 };
303
+ const nested = returnedOf(inner, guardNames, depth + 1);
304
+ return { ...nested, layers: nested.layers + 1 };
305
+ };
306
+ const guardFactoriesOf = (source, guardNames, isImported) => {
307
+ const functions = functionsOf(source);
308
+ const returned = new Map([...functions].map(([name, { body }]) => [name, returnedOf(body, guardNames)]));
309
+ const members = new Set;
310
+ const isFactoryCall = (callee) => members.has(callee) || !functions.has(callee) && isImported(callee);
311
+ for (let isGrowing = true;isGrowing; ) {
312
+ isGrowing = false;
313
+ for (const [name, { guard, calls }] of returned) {
314
+ if (members.has(name) || !guard && !calls.some(({ callee }) => isFactoryCall(callee)))
315
+ continue;
316
+ members.add(name);
317
+ isGrowing = true;
318
+ }
319
+ }
320
+ const factories = new Map;
321
+ for (const name of members) {
322
+ const found = functions.get(name);
323
+ const made = returned.get(name);
324
+ if (!found || !made)
325
+ continue;
326
+ factories.set(name, {
327
+ name,
328
+ span: spanOf(source, found.name),
329
+ extent: extentOf(source, found.statement),
330
+ scope: made.guard ? scopeOf(made.guard) : null,
331
+ doc: docOf(source, found.statement),
332
+ direct: !!made.guard,
333
+ via: made.calls.filter(({ callee }) => isFactoryCall(callee)),
334
+ layers: made.layers
335
+ });
336
+ }
337
+ return factories;
338
+ };
339
+ return { implementsGuard, scopeOf, guardFactoriesOf };
340
+ };
341
+ var createGuardFactReader = (ts, ast, base) => {
342
+ const { spanOf, extentOf, unwrap, docOf } = base;
343
+ const { pathOf, layersOf, labelOf, guardsIn } = guardRefReaderOf(ts, ast, unwrap);
344
+ const { implementsGuard, scopeOf, guardFactoriesOf } = guardFactoryReaderOf(ts, ast, base, pathOf, layersOf);
345
+ const guardsOf = (source, imports, madeInPlace) => {
346
+ const guardNames = new Set([
347
+ "Guard",
348
+ ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
349
+ ]);
350
+ const imported = new Set(imports.map(({ name }) => name));
351
+ const isImported = (callee) => imported.has(callee.split(".")[0] ?? callee);
352
+ const factories = guardFactoriesOf(source, guardNames, isImported);
353
+ const guards = [];
354
+ const calls = [];
355
+ const makeOf = (made, value, isInPlace = false) => {
356
+ if (ts.isClassExpression(value) && implementsGuard(value, guardNames)) {
357
+ guards.push({ ...made, scope: scopeOf(value), factory: null, layers: 0 });
358
+ return;
359
+ }
360
+ const { root, layers } = ts.isCallExpression(value) ? layersOf(value) : { root: null, layers: 0 };
361
+ const callee = root && pathOf(unwrap(root.expression));
362
+ const factory = callee && factories.get(callee);
363
+ if (factory)
364
+ guards.push({ ...made, scope: factory.scope, factory: factory.name, layers });
365
+ else if (callee && (isImported(callee) || isInPlace))
366
+ calls.push({ ...made, callee, kind: "call", scope: null, layers });
367
+ };
368
+ for (const statement of source.statements) {
369
+ if (ts.isClassDeclaration(statement) && statement.name) {
370
+ const declared = {
371
+ name: statement.name.text,
372
+ span: spanOf(source, statement.name),
373
+ extent: extentOf(source, statement),
374
+ scope: scopeOf(statement),
375
+ doc: docOf(source, statement)
376
+ };
377
+ const base = statement.heritageClauses?.find(({ token }) => token === ts.SyntaxKind.ExtendsKeyword)?.types[0];
378
+ const callee = base && pathOf(base.expression);
379
+ if (implementsGuard(statement, guardNames))
380
+ guards.push({ ...declared, factory: null, layers: 0 });
381
+ else if (callee)
382
+ calls.push({ ...declared, callee, kind: "extends", layers: 0 });
383
+ }
384
+ if (!ts.isVariableStatement(statement))
385
+ continue;
386
+ for (const declaration of statement.declarationList.declarations) {
387
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
388
+ if (!initializer || !ts.isIdentifier(declaration.name))
389
+ continue;
390
+ const holder = declaration.name.text;
391
+ makeOf({
392
+ name: holder,
393
+ span: spanOf(source, declaration.name),
394
+ extent: extentOf(source, statement),
395
+ doc: docOf(source, statement)
396
+ }, initializer);
397
+ if (!ts.isObjectLiteralExpression(initializer))
398
+ continue;
399
+ for (const property of initializer.properties) {
400
+ const key = ts.isPropertyAssignment(property) ? ast.nodeName(property.name) : null;
401
+ if (key && ts.isPropertyAssignment(property))
402
+ makeOf({
403
+ name: `${holder}.${key}`,
404
+ span: spanOf(source, property.name),
405
+ extent: extentOf(source, property),
406
+ doc: docOf(source, property)
407
+ }, unwrap(property.initializer));
408
+ }
409
+ }
410
+ }
411
+ for (const { call, name } of madeInPlace)
412
+ makeOf({ name, span: spanOf(source, call), extent: extentOf(source, call), doc: null }, call, true);
413
+ const guardLabels = Object.fromEntries(madeInPlace.map(({ call, name }) => [name, labelOf(source, call)]));
414
+ return { guards, guardFactories: [...factories.values()], guardCalls: calls, guardLabels };
415
+ };
416
+ return { guardsIn, guardsOf };
417
+ };
418
+
126
419
  // pkgs/@akanjs/language/moduleFacts.ts
127
420
  var testFilePattern = /\.(?:test|spec)\.tsx?$/;
128
421
  var moduleCallees = new Set([
@@ -191,21 +484,6 @@ var createFactReader = (ts, ast) => {
191
484
  const value = unwrap(expression);
192
485
  return ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) ? `${value.expression.text}.${value.name.text}` : ast.expressionName(value);
193
486
  };
194
- const guardsIn = (object) => {
195
- const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
196
- if (!guards)
197
- return [];
198
- if (ts.isArrayLiteralExpression(guards))
199
- return guards.elements.map(guardNameOf).filter((name) => name !== null);
200
- if (ts.isObjectLiteralExpression(guards))
201
- return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
202
- const value = unwrap(property.initializer);
203
- const names = ts.isArrayLiteralExpression(value) ? value.elements.map(guardNameOf) : [guardNameOf(value)];
204
- return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
205
- });
206
- const single = guardNameOf(guards);
207
- return single ? [single] : [];
208
- };
209
487
  const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
210
488
  const chainOf = (initializer) => {
211
489
  let root = unwrap(initializer);
@@ -222,7 +500,7 @@ var createFactReader = (ts, ast) => {
222
500
  const node = keyName(property);
223
501
  return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
224
502
  });
225
- const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
503
+ const sliceKeysOf = (source, builder, made, model) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
226
504
  if (!ts.isPropertyAssignment(property))
227
505
  return [];
228
506
  const name = ast.nodeName(property.name);
@@ -234,7 +512,7 @@ var createFactReader = (ts, ast) => {
234
512
  name,
235
513
  span: spanOf(source, property.name),
236
514
  extent: extentOf(source, property),
237
- guards: root ? guardsIn(optionObjectOf(root)) : [],
515
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
238
516
  live: methods.includes("live"),
239
517
  argCount: methods.filter((method) => argMethods.has(method)).length
240
518
  }
@@ -284,7 +562,7 @@ var createFactReader = (ts, ast) => {
284
562
  const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
285
563
  return `${ms / size}${unit}`;
286
564
  };
287
- const modesOf = (option) => {
565
+ const modesOf = (option, type) => {
288
566
  const object = option && unwrap(option);
289
567
  if (!object || !ts.isObjectLiteralExpression(object))
290
568
  return [];
@@ -294,9 +572,9 @@ var createFactReader = (ts, ast) => {
294
572
  if (name === "enabled")
295
573
  return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
296
574
  if (name === "once")
297
- return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
575
+ return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
298
576
  if (name === "lock")
299
- return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
577
+ return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
300
578
  if (name !== "serverMode" && name !== "operationMode")
301
579
  return [];
302
580
  const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
@@ -314,7 +592,8 @@ var createFactReader = (ts, ast) => {
314
592
  const { root } = chainOf(property.initializer);
315
593
  const type = root ? verbOf(root.expression) : null;
316
594
  const [first, second] = root?.arguments ?? [];
317
- const schedule = type === "cron" && first ? ts.isStringLiteralLike(first) ? first.text : first.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
595
+ const cron = type === "cron" && first ? unwrap(first) : null;
596
+ const schedule = cron ? ts.isStringLiteralLike(cron) ? cron.text : cron.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
318
597
  const option = type === "initialize" || type === "destroy" ? first : second;
319
598
  return [
320
599
  {
@@ -323,7 +602,7 @@ var createFactReader = (ts, ast) => {
323
602
  extent: extentOf(source, property),
324
603
  type,
325
604
  schedule,
326
- modes: modesOf(option)
605
+ modes: modesOf(option, type)
327
606
  }
328
607
  ];
329
608
  });
@@ -340,7 +619,20 @@ var createFactReader = (ts, ast) => {
340
619
  return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
341
620
  });
342
621
  };
343
- const endpointKeysOf = (source, builder) => {
622
+ const adaptorBindingsIn = (source) => {
623
+ const bindings = [];
624
+ const visit = (node) => {
625
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "applyAdaptor") {
626
+ const [role, adaptor] = node.arguments.map(guardNameOf);
627
+ if (role && adaptor)
628
+ bindings.push({ role, adaptor, span: spanOf(source, node.expression.name) });
629
+ }
630
+ ts.forEachChild(node, visit);
631
+ };
632
+ visit(source);
633
+ return bindings;
634
+ };
635
+ const endpointKeysOf = (source, builder, made, model) => {
344
636
  const verbOf = verbReaderOf(builder?.parameters[0]);
345
637
  return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
346
638
  if (!ts.isPropertyAssignment(property))
@@ -355,7 +647,7 @@ var createFactReader = (ts, ast) => {
355
647
  span: spanOf(source, property.name),
356
648
  extent: extentOf(source, property),
357
649
  verb: root ? verbOf(root.expression) : null,
358
- guards: root ? guardsIn(optionObjectOf(root)) : [],
650
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
359
651
  argCount: methods.filter((method) => argMethods.has(method)).length
360
652
  }
361
653
  ];
@@ -422,22 +714,6 @@ var createFactReader = (ts, ast) => {
422
714
  })) : []
423
715
  ];
424
716
  };
425
- const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
426
- const guardClassIn = (node, guardNames) => {
427
- if ((ts.isClassExpression(node) || ts.isClassDeclaration(node)) && implementsGuard(node, guardNames))
428
- return node;
429
- return ts.forEachChild(node, (child) => guardClassIn(child, guardNames)) ?? null;
430
- };
431
- const callsIn = (node, names) => {
432
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && names.has(node.expression.text))
433
- return node.expression.text;
434
- return ts.forEachChild(node, (child) => callsIn(child, names)) ?? null;
435
- };
436
- const scopeOf = (node) => {
437
- const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
438
- const value = scope && unwrap(scope);
439
- return value && ts.isStringLiteralLike(value) ? value.text : null;
440
- };
441
717
  const docOf = (source, node) => {
442
718
  const { text } = source;
443
719
  const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
@@ -453,108 +729,20 @@ var createFactReader = (ts, ast) => {
453
729
  `)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
454
730
  return lines.length ? lines.join(" ") : null;
455
731
  };
456
- const functionsOf = (source) => {
457
- const functions = new Map;
458
- for (const statement of source.statements) {
459
- if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
460
- functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
461
- if (!ts.isVariableStatement(statement))
462
- continue;
463
- for (const declaration of statement.declarationList.declarations) {
464
- const initializer = declaration.initializer && unwrap(declaration.initializer);
465
- if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
466
- functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
467
- }
468
- }
469
- return functions;
470
- };
471
- const guardFactoriesOf = (source, guardNames) => {
472
- const functions = functionsOf(source);
473
- const factories = new Map;
474
- const factoryOf = (name, scope, via) => {
475
- const found = functions.get(name);
476
- if (!found)
477
- return;
478
- factories.set(name, {
479
- name,
480
- span: spanOf(source, found.name),
481
- extent: extentOf(source, found.statement),
482
- scope,
483
- doc: docOf(source, found.statement),
484
- via
485
- });
486
- };
487
- for (const [name, { body }] of functions) {
488
- const guard = guardClassIn(body, guardNames);
489
- if (guard)
490
- factoryOf(name, scopeOf(guard), null);
491
- }
492
- for (let isGrowing = true;isGrowing; ) {
493
- isGrowing = false;
494
- for (const [name, { body }] of functions) {
495
- const called = factories.has(name) ? null : callsIn(body, new Set(factories.keys()));
496
- if (!called)
497
- continue;
498
- factoryOf(name, factories.get(called)?.scope ?? null, called);
499
- isGrowing = true;
500
- }
501
- }
502
- return factories;
503
- };
504
- const guardsOf = (source, imports) => {
505
- const guardNames = new Set([
506
- "Guard",
507
- ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
508
- ]);
509
- const imported = new Set(imports.map(({ name }) => name));
510
- const factories = guardFactoriesOf(source, guardNames);
511
- const guards = [];
512
- const calls = [];
513
- for (const statement of source.statements) {
514
- if (ts.isClassDeclaration(statement) && statement.name && implementsGuard(statement, guardNames))
515
- guards.push({
516
- name: statement.name.text,
517
- span: spanOf(source, statement.name),
518
- extent: extentOf(source, statement),
519
- scope: scopeOf(statement),
520
- doc: docOf(source, statement),
521
- factory: null
522
- });
523
- if (!ts.isVariableStatement(statement))
524
- continue;
525
- for (const declaration of statement.declarationList.declarations) {
526
- const initializer = declaration.initializer && unwrap(declaration.initializer);
527
- if (!initializer || !ts.isIdentifier(declaration.name))
528
- continue;
529
- const made = {
530
- name: declaration.name.text,
531
- span: spanOf(source, declaration.name),
532
- extent: extentOf(source, statement),
533
- doc: docOf(source, statement)
534
- };
535
- if (ts.isClassExpression(initializer) && implementsGuard(initializer, guardNames))
536
- guards.push({ ...made, scope: scopeOf(initializer), factory: null });
537
- if (!ts.isCallExpression(initializer) || !ts.isIdentifier(initializer.expression))
538
- continue;
539
- const callee = initializer.expression.text;
540
- const factory = factories.get(callee);
541
- if (factory)
542
- guards.push({ ...made, scope: factory.scope, factory: callee });
543
- else if (imported.has(callee))
544
- calls.push({ ...made, callee });
545
- }
546
- }
547
- return { guards, guardFactories: [...factories.values()], guardCalls: calls };
548
- };
732
+ const { guardsIn, guardsOf } = createGuardFactReader(ts, ast, { spanOf, extentOf, unwrap, docOf });
549
733
  const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
550
734
  const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
551
735
  const read = (source, owner) => {
552
736
  const imports = importsIn(source);
737
+ const madeInPlace = [];
553
738
  const facts = {
554
739
  fileName: source.fileName,
555
740
  owner,
556
741
  imports,
557
- ...guardsOf(source, imports),
742
+ guards: [],
743
+ guardFactories: [],
744
+ guardCalls: [],
745
+ guardLabels: {},
558
746
  constants: [],
559
747
  enums: [],
560
748
  filters: [],
@@ -564,6 +752,7 @@ var createFactReader = (ts, ast) => {
564
752
  endpoints: [],
565
753
  internals: [],
566
754
  adaptors: [],
755
+ adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
567
756
  stores: []
568
757
  };
569
758
  for (const statement of source.statements) {
@@ -588,16 +777,19 @@ var createFactReader = (ts, ast) => {
588
777
  ...name,
589
778
  model: moduleRefOf(call.arguments[0], "db"),
590
779
  methods: methodsOf(source, statement),
591
- plugs: plugsOf(source, builder)
780
+ plugs: plugsOf(source, builder),
781
+ fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
592
782
  });
593
- else if (callee === "slice")
783
+ else if (callee === "slice") {
784
+ const model = moduleRefOf(call.arguments[0], "srv");
785
+ const member = model ?? name.name;
594
786
  facts.slices.push({
595
787
  ...name,
596
- model: moduleRefOf(call.arguments[0], "srv"),
597
- guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
598
- keys: sliceKeysOf(source, builder)
788
+ model,
789
+ guards: guardsIn(source, call.arguments.find(ts.isObjectLiteralExpression), madeInPlace, member),
790
+ keys: sliceKeysOf(source, builder, madeInPlace, member)
599
791
  });
600
- else if (callee === "internal")
792
+ } else if (callee === "internal")
601
793
  facts.internals.push({
602
794
  ...name,
603
795
  model: moduleRefOf(call.arguments[0], "srv"),
@@ -612,13 +804,14 @@ var createFactReader = (ts, ast) => {
612
804
  plugs: plugsOf(source, builder),
613
805
  doc: docOf(source, statement)
614
806
  });
615
- } else if (callee === "endpoint")
807
+ } else if (callee === "endpoint") {
808
+ const model = moduleRefOf(call.arguments[0], "srv");
616
809
  facts.endpoints.push({
617
810
  ...name,
618
- model: moduleRefOf(call.arguments[0], "srv"),
619
- keys: endpointKeysOf(source, builder)
811
+ model,
812
+ keys: endpointKeysOf(source, builder, madeInPlace, model ?? name.name)
620
813
  });
621
- else if (callee === "store")
814
+ } else if (callee === "store")
622
815
  facts.stores.push({
623
816
  ...name,
624
817
  model: moduleRefOf(call.arguments[0], "sig"),
@@ -626,10 +819,647 @@ var createFactReader = (ts, ast) => {
626
819
  actions: methodsOf(source, statement)
627
820
  });
628
821
  }
629
- return facts;
630
- };
631
- return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
632
- };
822
+ return { ...facts, ...guardsOf(source, imports, madeInPlace) };
823
+ };
824
+ return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
825
+ };
826
+
827
+ // pkgs/@akanjs/language/guardResolver.ts
828
+ var frameworkOwner = "akanjs";
829
+ var frameworkPattern = /^akanjs(?:\/|$)/;
830
+ var aliasPattern = /^@(apps|libs)\/([^/]+)/;
831
+ var maxMapDepth = 4;
832
+ var identifierPattern = /^[\p{ID_Start}$_][\p{ID_Continue}$\u200c\u200d]*$/u;
833
+ var starExportPattern = /export\s*\*\s*from\s*["']([^"']+)["']/g;
834
+ var exportingPatternOf = (name) => new RegExp(`export\\s*\\{(?:[^}]*?[\\s,])?${escapeRegExp(name)}(?![\\w$])`);
835
+ var declaringPatternOf = (name, isAnyBinding) => {
836
+ const escaped = escapeRegExp(name);
837
+ const braced = "\\{(?:[^}]*?[\\s,])?";
838
+ const leads = [
839
+ "function\\s*\\*?\\s*",
840
+ "(?:const|let|var)\\s+",
841
+ `export\\s*${braced}`,
842
+ ...isAnyBinding ? ["class\\s+", `(?:const|let|var)\\s*${braced}`] : []
843
+ ];
844
+ return new RegExp(`(?:${leads.join("|")})${escaped}(?![\\w$])|,\\s*${escaped}\\s*(?::[^=;,]+)?=(?![=>])`);
845
+ };
846
+
847
+ class GuardResolver {
848
+ #source;
849
+ #routes;
850
+ #guards = new Map;
851
+ #madeInPlace = new Map;
852
+ #mapEntries = new Map;
853
+ #reexports = new Map;
854
+ #declarations = new Map;
855
+ #starExports = new Map;
856
+ #moduleExports = new Map;
857
+ #ownerFiles = null;
858
+ #modules = null;
859
+ constructor(source, routes) {
860
+ this.#source = source;
861
+ this.#routes = routes;
862
+ }
863
+ find(local, site) {
864
+ return this.#findGuard(local, site, 0);
865
+ }
866
+ madeInPlace(fileName, name) {
867
+ return this.#madeInPlace.get(`${fileName}
868
+ ${name}`);
869
+ }
870
+ declare(facts) {
871
+ const factories = new Map;
872
+ const declaredFactories = [];
873
+ for (const file of facts)
874
+ for (const fact of file.guardFactories) {
875
+ const factory = {
876
+ fact,
877
+ owner: file.owner,
878
+ site: file,
879
+ made: [],
880
+ isReal: fact.direct,
881
+ scope: fact.scope,
882
+ layers: fact.layers,
883
+ targets: []
884
+ };
885
+ factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
886
+ declaredFactories.push(factory);
887
+ }
888
+ for (const factory of declaredFactories)
889
+ factory.targets = factory.fact.via.flatMap(({ callee, layers }) => {
890
+ const target = this.#factoryAt(factories, callee, factory.site);
891
+ return target ? [{ target, layers }] : [];
892
+ });
893
+ for (let isGrowing = true;isGrowing; ) {
894
+ isGrowing = false;
895
+ for (const factory of declaredFactories) {
896
+ const via = factory.targets.find(({ target, layers }) => target.isReal && target.layers >= layers);
897
+ if (factory.isReal || !via)
898
+ continue;
899
+ factory.isReal = true;
900
+ factory.layers = factory.fact.layers + via.target.layers - via.layers;
901
+ factory.scope ??= via.target.scope;
902
+ isGrowing = true;
903
+ }
904
+ }
905
+ const partsOf = (factory, seen = new Set) => {
906
+ if (seen.has(factory))
907
+ return [];
908
+ seen.add(factory);
909
+ return [
910
+ { fileName: factory.site.fileName, extent: factory.fact.extent },
911
+ ...factory.targets.filter(({ target }) => target.isReal).flatMap(({ target }) => partsOf(target, seen))
912
+ ];
913
+ };
914
+ const declare = (file, made, scope, parts) => {
915
+ const { name, span, extent, doc } = made;
916
+ const label = file.guardLabels[name];
917
+ const own = { fileName: file.fileName, extent };
918
+ const guard = { name, owner: file.owner, span, scope, doc, parts: [own, ...parts], file: null, label };
919
+ if (name.includes("@"))
920
+ this.#madeInPlace.set(`${file.fileName}
921
+ ${name}`, guard);
922
+ else
923
+ this.#guards.set(name, [...this.#guards.get(name) ?? [], guard]);
924
+ return guard;
925
+ };
926
+ const extending = [];
927
+ for (const file of facts) {
928
+ for (const made of file.guards) {
929
+ const factory = made.factory ? factories.get(made.factory)?.find((other) => other.site.fileName === file.fileName) : undefined;
930
+ if (!made.factory)
931
+ declare(file, made, made.scope, []);
932
+ else if (factory?.isReal && factory.layers === made.layers)
933
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
934
+ }
935
+ for (const made of file.guardCalls) {
936
+ if (made.kind === "extends") {
937
+ extending.push([file, made]);
938
+ continue;
939
+ }
940
+ const factory = this.#factoryAt(factories, made.callee, file);
941
+ if (factory?.isReal && factory.layers === made.layers)
942
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
943
+ }
944
+ }
945
+ for (let isGrowing = true;isGrowing; ) {
946
+ isGrowing = false;
947
+ for (const [idx, entry] of extending.entries()) {
948
+ const [file, made] = entry;
949
+ const found = this.#findGuard(made.callee, file, 0, true);
950
+ if (!("guard" in found))
951
+ continue;
952
+ declare(file, made, made.scope ?? found.guard.scope, found.guard.parts);
953
+ extending.splice(idx, 1);
954
+ isGrowing = true;
955
+ break;
956
+ }
957
+ }
958
+ for (const { fact, made, isReal } of declaredFactories)
959
+ if (isReal && made.every((guard) => !guard.doc))
960
+ for (const guard of made)
961
+ guard.doc = fact.doc;
962
+ for (const list of this.#guards.values())
963
+ for (const guard of list)
964
+ if (list.some((other) => other !== guard && other.owner === guard.owner))
965
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
966
+ for (const guard of this.#madeInPlace.values())
967
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
968
+ return [...[...this.#guards.values()].flat(), ...this.#madeInPlace.values()];
969
+ }
970
+ #factoryAt(factories, callee, site, depth = 0) {
971
+ const [head = callee, member] = callee.split(".");
972
+ const imported = site.imports.find((item) => item.name === head);
973
+ if (!imported)
974
+ return member ? undefined : factories.get(callee)?.find((other) => other.site.fileName === site.fileName);
975
+ if (member && imported.imported !== "*")
976
+ return;
977
+ const from = this.ownerOfSpecifier(site.fileName, imported.from);
978
+ if (from === undefined || from === frameworkOwner)
979
+ return;
980
+ const name = member ?? (imported.imported === "default" || imported.imported === "*" ? head : imported.imported);
981
+ const candidates = factories.get(name) ?? [];
982
+ const near = this.pathOfSpecifier(site.fileName, imported.from);
983
+ const own = this.#nearest(candidates.filter((other) => other.owner === from), ({ site }) => site.fileName, near);
984
+ if (own)
985
+ return own;
986
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
987
+ if (reexport)
988
+ return this.#factoryAt(factories, reexport.local, reexport.site, depth + 1);
989
+ return from && near ? this.#throughStar(candidates, from, name, near) : undefined;
990
+ }
991
+ #findGuard(local, site, depth, isBase = false) {
992
+ const segments = local.split(".");
993
+ const head = segments[0] ?? local;
994
+ const imported = site.imports.find((item) => item.name === head);
995
+ const from = imported ? this.ownerOfSpecifier(site.fileName, imported.from) : undefined;
996
+ if (imported && from === undefined)
997
+ return { external: true };
998
+ const near = imported ? this.pathOfSpecifier(site.fileName, imported.from) : this.#source.relative(site.fileName).replace(/\.tsx?$/, "");
999
+ const named = imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : null;
1000
+ const path = imported?.imported === "*" ? segments.slice(1) : [named ?? head, ...segments.slice(1)];
1001
+ const holder = imported ? from ?? null : site.owner;
1002
+ const [objectName, member] = path;
1003
+ if (path.length === 2 && objectName && member && holder !== frameworkOwner && depth < maxMapDepth) {
1004
+ const made = this.#nearest((this.#guards.get(`${objectName}.${member}`) ?? []).filter((guard) => guard.owner === holder), ({ span }) => span.fileName, near);
1005
+ if (made)
1006
+ return { guard: made };
1007
+ for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
1008
+ const found = this.#findGuard(entry.name, entry.site, depth + 1, isBase);
1009
+ if ("guard" in found)
1010
+ return found;
1011
+ }
1012
+ }
1013
+ const name = path[path.length - 1] ?? head;
1014
+ if (from === frameworkOwner)
1015
+ return { name, from };
1016
+ const declared = this.#guards.get(name) ?? [];
1017
+ const own = this.#nearest(declared.filter((other) => other.owner === (imported ? from : site.owner)), ({ span }) => span.fileName, near);
1018
+ if (own)
1019
+ return { guard: own };
1020
+ if (!imported) {
1021
+ const visible = isBase ? undefined : declared.find((other) => this.visible(other.owner, site.owner));
1022
+ return visible ? { guard: visible } : { name, from };
1023
+ }
1024
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
1025
+ if (reexport)
1026
+ return this.#findGuard(reexport.local, reexport.site, depth + 1, isBase);
1027
+ const reached = from && near ? this.#throughStar(declared, from, name, near) : undefined;
1028
+ return reached ? { guard: reached } : { name, from };
1029
+ }
1030
+ reexportOf(owner, name, near) {
1031
+ const key = `${owner}
1032
+ ${name}
1033
+ ${near ?? ""}`;
1034
+ const cached = this.#reexports.get(key);
1035
+ if (cached !== undefined)
1036
+ return cached;
1037
+ let found = null;
1038
+ const exporting = exportingPatternOf(name);
1039
+ for (const fileName of this.#filesOf(owner, name, near)) {
1040
+ const text = this.#source.readText(fileName);
1041
+ if (text === undefined || !exporting.test(text))
1042
+ continue;
1043
+ const reexport = this.#reexportIn(this.#source.parse(fileName, text), name);
1044
+ if (!reexport)
1045
+ continue;
1046
+ const site = { fileName, owner, imports: [] };
1047
+ found = reexport.from ? {
1048
+ local: reexport.name,
1049
+ site: { ...site, imports: [{ ...reexport, imported: reexport.name, from: reexport.from }] }
1050
+ } : { local: reexport.name, site };
1051
+ break;
1052
+ }
1053
+ this.#reexports.set(key, found);
1054
+ return found;
1055
+ }
1056
+ #reexportIn(source, name) {
1057
+ const { ts, reader } = this.#source;
1058
+ for (const statement of source.statements) {
1059
+ if (!ts.isExportDeclaration(statement) || statement.isTypeOnly)
1060
+ continue;
1061
+ const { exportClause, moduleSpecifier } = statement;
1062
+ if (!exportClause || !ts.isNamedExports(exportClause))
1063
+ continue;
1064
+ const element = exportClause.elements.find((item) => !item.isTypeOnly && item.name.text === name);
1065
+ if (!element)
1066
+ continue;
1067
+ const original = (element.propertyName ?? element.name).text;
1068
+ if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier))
1069
+ return { name: original, from: moduleSpecifier.text };
1070
+ const imported = reader.importsIn(source).find((item) => item.name === original);
1071
+ if (!imported)
1072
+ return { name: original, from: null };
1073
+ return imported.imported === "default" || imported.imported === "*" ? null : { name: imported.imported, from: imported.from };
1074
+ }
1075
+ return null;
1076
+ }
1077
+ #filesOf(owner, name, near) {
1078
+ const isNear = (fileName) => {
1079
+ const relative = this.#source.relative(fileName);
1080
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1081
+ };
1082
+ if (!identifierPattern.test(name))
1083
+ return [];
1084
+ const candidates = this.#filesIn(owner).filter(([, words]) => words.includes(`
1085
+ ${name}
1086
+ `)).map(([fileName]) => fileName);
1087
+ return [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))];
1088
+ }
1089
+ #mapEntriesOf(objectName, member, fileName, owner) {
1090
+ const key = `${objectName}
1091
+ ${member}
1092
+ ${fileName ?? ""}
1093
+ ${owner ?? ""}`;
1094
+ const cached = this.#mapEntries.get(key);
1095
+ if (cached)
1096
+ return cached;
1097
+ const { ts, reader } = this.#source;
1098
+ const entries = [];
1099
+ const fileNames = [
1100
+ ...fileName ? [fileName] : [],
1101
+ ...(owner === null ? [] : this.#filesIn(owner)).filter(([other, words]) => other !== fileName && words.includes(`
1102
+ ${objectName}
1103
+ `) && words.includes(`
1104
+ ${member}
1105
+ `)).map(([other]) => other)
1106
+ ];
1107
+ for (const holder of fileNames) {
1108
+ const text = this.#source.readText(holder);
1109
+ if (text === undefined)
1110
+ continue;
1111
+ const source = this.#source.parse(holder, text);
1112
+ const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
1113
+ for (const statement of source.statements) {
1114
+ if (!ts.isVariableStatement(statement))
1115
+ continue;
1116
+ const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
1117
+ const object = map && reader.unwrap(map);
1118
+ if (!object || !ts.isObjectLiteralExpression(object))
1119
+ continue;
1120
+ for (const property of object.properties) {
1121
+ if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
1122
+ entries.push({ name: member, site });
1123
+ if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
1124
+ continue;
1125
+ const value = reader.unwrap(property.initializer);
1126
+ if (ts.isIdentifier(value))
1127
+ entries.push({ name: value.text, site });
1128
+ else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
1129
+ entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
1130
+ }
1131
+ }
1132
+ }
1133
+ this.#mapEntries.set(key, entries);
1134
+ return entries;
1135
+ }
1136
+ declarationOf(owner, name, near, depth = 0) {
1137
+ const key = `${owner}
1138
+ ${name}
1139
+ ${near ?? ""}`;
1140
+ const cached = this.#declarations.get(key);
1141
+ if (cached !== undefined)
1142
+ return cached;
1143
+ this.#declarations.set(key, null);
1144
+ const declaring = declaringPatternOf(name, false);
1145
+ let found = null;
1146
+ for (const fileName of this.#filesOf(owner, name, near)) {
1147
+ const text = this.#source.readText(fileName);
1148
+ if (text === undefined || !declaring.test(text))
1149
+ continue;
1150
+ const source = this.#source.parse(fileName, text);
1151
+ const declared = this.#routes.declarationIn(source, name);
1152
+ if (declared) {
1153
+ found = { source, ...declared, name };
1154
+ break;
1155
+ }
1156
+ const reexport = this.#reexportIn(source, name);
1157
+ if (!reexport)
1158
+ continue;
1159
+ if (!reexport.from) {
1160
+ const local = this.#routes.declarationIn(source, reexport.name);
1161
+ found = local ? { source, ...local, name: reexport.name } : null;
1162
+ break;
1163
+ }
1164
+ const next = this.ownerOfSpecifier(fileName, reexport.from);
1165
+ if (next && next !== frameworkOwner && depth < maxMapDepth)
1166
+ found = this.declarationOf(next, reexport.name, this.pathOfSpecifier(fileName, reexport.from), depth + 1);
1167
+ break;
1168
+ }
1169
+ this.#declarations.set(key, found);
1170
+ return found;
1171
+ }
1172
+ #throughStar(declared, owner, name, near) {
1173
+ if (!declared.length)
1174
+ return;
1175
+ const { owners, isOpaque } = this.starExportsOf(owner, near);
1176
+ const reached = declared.filter((other) => other.owner !== null && owners.includes(other.owner));
1177
+ return reached.length === 1 && !isOpaque && !this.declares(owner, name) ? reached[0] : undefined;
1178
+ }
1179
+ starExportsOf(owner, near) {
1180
+ const key = `${owner}
1181
+ ${near}`;
1182
+ const cached = this.#starExports.get(key);
1183
+ if (cached)
1184
+ return cached;
1185
+ const found = { owners: [], packages: [], paths: [near], isOpaque: false };
1186
+ this.#starExports.set(key, found);
1187
+ for (let idx = 0;idx < found.paths.length; idx++)
1188
+ for (const fileName of this.#modulesAt(found.paths[idx] ?? ""))
1189
+ for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? []) {
1190
+ const target = this.ownerOfSpecifier(fileName, specifier);
1191
+ const path = this.pathOfSpecifier(fileName, specifier);
1192
+ if (path === null && !found.packages.includes(specifier))
1193
+ found.packages.push(specifier);
1194
+ if (target === undefined || target === frameworkOwner || path === null)
1195
+ found.isOpaque = true;
1196
+ else if (!found.paths.includes(path))
1197
+ found.paths.push(path);
1198
+ if (target && target !== frameworkOwner && target !== owner && !found.owners.includes(target))
1199
+ found.owners.push(target);
1200
+ }
1201
+ return found;
1202
+ }
1203
+ exportsAt(path, name) {
1204
+ const key = `${path}
1205
+ ${name}`;
1206
+ const cached = this.#moduleExports.get(key);
1207
+ if (cached)
1208
+ return cached;
1209
+ const found = { named: null, stars: [] };
1210
+ const exporting = exportingPatternOf(name);
1211
+ for (const fileName of this.#modulesAt(path)) {
1212
+ const text = this.#source.readText(fileName);
1213
+ if (text === undefined)
1214
+ continue;
1215
+ const named = identifierPattern.test(name) && exporting.test(text) ? this.#reexportIn(this.#source.parse(fileName, text), name) : null;
1216
+ if (named) {
1217
+ found.named = { ...named, fileName };
1218
+ found.stars = [];
1219
+ break;
1220
+ }
1221
+ for (const [, specifier = ""] of text.matchAll(starExportPattern)) {
1222
+ const target = this.pathOfSpecifier(fileName, specifier);
1223
+ if (target !== null && !found.stars.includes(target))
1224
+ found.stars.push(target);
1225
+ }
1226
+ }
1227
+ this.#moduleExports.set(key, found);
1228
+ return found;
1229
+ }
1230
+ #modulesAt(path) {
1231
+ if (!this.#modules) {
1232
+ this.#modules = new Map;
1233
+ for (const [fileName] of this.#source.files) {
1234
+ const module2 = this.#source.relative(fileName).replace(/\.tsx?$/, "");
1235
+ for (const key of module2.endsWith("/index") ? [module2, module2.slice(0, -6)] : [module2])
1236
+ this.#modules.set(key, [...this.#modules.get(key) ?? [], fileName]);
1237
+ }
1238
+ }
1239
+ return this.#modules.get(path) ?? [];
1240
+ }
1241
+ hasModule(path) {
1242
+ return this.#modulesAt(path).length > 0;
1243
+ }
1244
+ packagesUnder(owner, folder) {
1245
+ const packages = new Set;
1246
+ for (const [fileName, words] of this.#filesIn(owner)) {
1247
+ if (!this.#source.relative(fileName).startsWith(`${folder}/`) || !words.includes(`
1248
+ export
1249
+ `))
1250
+ continue;
1251
+ for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? [])
1252
+ if (this.pathOfSpecifier(fileName, specifier) === null)
1253
+ packages.add(specifier);
1254
+ }
1255
+ return [...packages];
1256
+ }
1257
+ declares(owner, name) {
1258
+ const declaring = declaringPatternOf(name, true);
1259
+ return this.#filesOf(owner, name, null).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
1260
+ }
1261
+ declaresAt(path, name) {
1262
+ const declaring = declaringPatternOf(name, true);
1263
+ return this.#modulesAt(path).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
1264
+ }
1265
+ #filesIn(owner) {
1266
+ if (!this.#ownerFiles) {
1267
+ this.#ownerFiles = new Map;
1268
+ for (const entry of this.#source.files) {
1269
+ const fileOwner = testFilePattern.test(entry[0]) ? null : this.#source.ownerOf(entry[0]);
1270
+ const files = fileOwner === null ? null : this.#ownerFiles.get(fileOwner);
1271
+ if (files)
1272
+ files.push(entry);
1273
+ else if (fileOwner !== null)
1274
+ this.#ownerFiles.set(fileOwner, [entry]);
1275
+ }
1276
+ }
1277
+ return this.#ownerFiles.get(owner) ?? [];
1278
+ }
1279
+ #nearest(items, fileOf, near) {
1280
+ const isNear = (fileName) => {
1281
+ const relative = this.#source.relative(fileName);
1282
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1283
+ };
1284
+ return items.find((item) => isNear(fileOf(item))) ?? items[0];
1285
+ }
1286
+ inOwner(fileName, owner) {
1287
+ const relative = this.#source.relative(fileName);
1288
+ return owner && relative.startsWith(`${owner}/`) ? relative.slice(owner.length + 1) : relative;
1289
+ }
1290
+ ownerOfSpecifier(fileName, specifier) {
1291
+ if (frameworkPattern.test(specifier))
1292
+ return frameworkOwner;
1293
+ const path = this.pathOfSpecifier(fileName, specifier);
1294
+ return path === null ? undefined : ownerOf(path) ?? undefined;
1295
+ }
1296
+ pathOfSpecifier(fileName, specifier) {
1297
+ const alias = specifier.match(aliasPattern);
1298
+ if (alias)
1299
+ return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1300
+ if (!specifier.startsWith("."))
1301
+ return null;
1302
+ const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1303
+ for (const segment of specifier.split("/"))
1304
+ if (segment === "..")
1305
+ segments.pop();
1306
+ else if (segment !== ".")
1307
+ segments.push(segment);
1308
+ return segments.join("/");
1309
+ }
1310
+ visible(target, from) {
1311
+ return !from || !target || target === from || target.startsWith("libs/");
1312
+ }
1313
+ }
1314
+
1315
+ // pkgs/@akanjs/language/adaptorResolver.ts
1316
+ var maxModules = 64;
1317
+
1318
+ class AdaptorResolver {
1319
+ #source;
1320
+ #resolver;
1321
+ #sink;
1322
+ #adaptors = new Map;
1323
+ #plugs = new Map;
1324
+ #serviceFields = new Set;
1325
+ constructor(source, resolver, sink) {
1326
+ this.#source = source;
1327
+ this.#resolver = resolver;
1328
+ this.#sink = sink;
1329
+ }
1330
+ declare(facts) {
1331
+ const counts = new Map;
1332
+ for (const file of facts)
1333
+ for (const fact of file.adaptors) {
1334
+ const key = this.#sink.key("adaptor", file.owner, fact.name);
1335
+ counts.set(key, (counts.get(key) ?? 0) + 1);
1336
+ }
1337
+ const declared = facts.flatMap((file) => file.adaptors.map((fact) => {
1338
+ const key = this.#sink.key("adaptor", file.owner, fact.name);
1339
+ const id = (counts.get(key) ?? 0) > 1 ? this.#sink.key("adaptor", file.owner, `${this.#resolver.inOwner(file.fileName, file.owner)}#${fact.name}`) : key;
1340
+ const adaptor = { fact, owner: file.owner, fileName: file.fileName, id };
1341
+ this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], adaptor]);
1342
+ this.#nodeOf(adaptor);
1343
+ return { adaptor, file };
1344
+ }));
1345
+ for (const { adaptor, file } of declared)
1346
+ for (const plug of adaptor.fact.plugs)
1347
+ this.#sink.addEdge(adaptor.id, this.#idOf(plug.target, file), "plugs", plug.name, this.#sink.location(plug.span));
1348
+ for (const file of facts)
1349
+ for (const binding of file.adaptorBindings)
1350
+ this.#sink.addEdge(this.#idOf(binding.role, file), this.#idOf(binding.adaptor, file), "binds", file.owner, this.#sink.location(binding.span));
1351
+ }
1352
+ addService(refName, service, file) {
1353
+ for (const plug of service.plugs) {
1354
+ const key = `${refName}
1355
+ ${plug.name}`;
1356
+ this.#plugs.set(key, [
1357
+ ...this.#plugs.get(key) ?? [],
1358
+ { owner: file.owner, target: this.#idOf(plug.target, file) }
1359
+ ]);
1360
+ }
1361
+ for (const field of service.fields)
1362
+ this.#serviceFields.add(`${file.owner ?? ""}
1363
+ ${refName}
1364
+ ${field}`);
1365
+ }
1366
+ pluggedOf(owner, module2, field) {
1367
+ const plugs = this.#plugs.get(`${module2}
1368
+ ${field}`) ?? [];
1369
+ const own = plugs.filter((plug) => plug.owner === owner);
1370
+ if (!own.length && this.#serviceFields.has(`${owner ?? ""}
1371
+ ${module2}
1372
+ ${field}`))
1373
+ return [];
1374
+ const inherited = own.length ? own : plugs.filter((plug) => this.#resolver.visible(plug.owner, owner));
1375
+ return [...new Set(inherited.map(({ target }) => target))];
1376
+ }
1377
+ #idOf(local, file) {
1378
+ const [head, member] = local.split(".");
1379
+ const imported = file.imports.find((item) => item.name === head);
1380
+ const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1381
+ const path = imported ? this.#resolver.pathOfSpecifier(file.fileName, imported.from) : null;
1382
+ if (imported && path === null && !frameworkPattern.test(imported.from))
1383
+ return this.#packageAdaptorOf(name, imported.from);
1384
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
1385
+ const declared = this.#adaptors.get(name) ?? [];
1386
+ const holder = from === undefined ? file.owner : from;
1387
+ const own = this.#adaptorIn(name, declared, holder, path, file.fileName);
1388
+ if (own && "from" in own)
1389
+ return this.#packageAdaptorOf(own.name, own.from);
1390
+ const found = own === undefined && from !== frameworkOwner ? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, holder)) : own;
1391
+ if (found)
1392
+ return found.id;
1393
+ const reexported = own === undefined && from && from !== frameworkOwner ? this.#packageShimOf(from, name, path) : null;
1394
+ if (reexported)
1395
+ return this.#packageAdaptorOf(reexported.name, reexported.from);
1396
+ const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
1397
+ const detail = from === frameworkOwner ? [frameworkOwner] : [];
1398
+ return this.#sink.addNode(this.#sink.key("adaptor", owner, name), "adaptor", owner, name, null, detail).id;
1399
+ }
1400
+ #adaptorIn(name, declared, owner, path, fileName) {
1401
+ const own = declared.filter((adaptor) => adaptor.owner === owner);
1402
+ const [first] = own;
1403
+ if (path === null)
1404
+ return own.find((adaptor) => adaptor.fileName === fileName) ?? first;
1405
+ if (own.length === 1)
1406
+ return first;
1407
+ if (!this.#resolver.hasModule(path))
1408
+ return own.find((adaptor) => this.#source.relative(adaptor.fileName).startsWith(`${path}/`)) ?? first;
1409
+ const exported = this.#exportedAt(name, path, new Set).filter((item) => first || ("fact" in item));
1410
+ const [only] = exported;
1411
+ if (only)
1412
+ return exported.length === 1 ? only : null;
1413
+ return first ? null : undefined;
1414
+ }
1415
+ #exportedAt(name, path, seen) {
1416
+ const key = `${path}
1417
+ ${name}`;
1418
+ if (seen.has(key) || seen.size >= maxModules)
1419
+ return [];
1420
+ seen.add(key);
1421
+ const here = (this.#adaptors.get(name) ?? []).filter((adaptor) => {
1422
+ const module2 = this.#source.relative(adaptor.fileName).replace(/\.tsx?$/, "");
1423
+ return module2 === path || module2 === `${path}/index`;
1424
+ });
1425
+ if (here.length)
1426
+ return here;
1427
+ const { named, stars } = this.#resolver.exportsAt(path, name);
1428
+ if (named?.from && this.#resolver.pathOfSpecifier(named.fileName, named.from) === null)
1429
+ return [{ name: named.name === "default" ? name : named.name, from: named.from }];
1430
+ if (named) {
1431
+ const target = named.from === null ? path : this.#resolver.pathOfSpecifier(named.fileName, named.from);
1432
+ return target === null ? [] : this.#exportedAt(named.name, target, seen);
1433
+ }
1434
+ return [...new Set(stars.flatMap((star) => this.#exportedAt(name, star, seen)))];
1435
+ }
1436
+ #packageAdaptorOf(name, specifier) {
1437
+ if (frameworkPattern.test(specifier))
1438
+ return this.#sink.addNode(this.#sink.key("adaptor", null, name), "adaptor", null, name, null, [frameworkOwner]).id;
1439
+ const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
1440
+ return this.#sink.addNode(this.#sink.key("adaptor", pkg, name), "adaptor", null, name, null, [pkg]).id;
1441
+ }
1442
+ #packageShimOf(owner, name, near) {
1443
+ const reexport = this.#resolver.reexportOf(owner, name, near);
1444
+ const [shim] = reexport?.site.imports ?? [];
1445
+ if (reexport && shim && this.#resolver.pathOfSpecifier(reexport.site.fileName, shim.from) === null)
1446
+ return { name: reexport.local === "default" ? name : reexport.local, from: shim.from };
1447
+ if (reexport || !near)
1448
+ return null;
1449
+ const { packages, paths } = this.#resolver.hasModule(near) ? this.#resolver.starExportsOf(owner, near) : { packages: this.#resolver.packagesUnder(owner, near), paths: [] };
1450
+ const [only] = packages;
1451
+ const isDeclared = this.#resolver.declares(owner, name) || paths.some((path) => this.#resolver.declaresAt(path, name));
1452
+ return packages.length === 1 && only && !isDeclared ? { name, from: only } : null;
1453
+ }
1454
+ #nodeOf({ fact, owner, fileName, id }) {
1455
+ const detail = fact.refName ? [fact.refName] : [];
1456
+ const node = this.#sink.addNode(id, "adaptor", owner, fact.name, fact.span, detail);
1457
+ if (fact.doc && !node.doc)
1458
+ node.doc = fact.doc;
1459
+ this.#sink.digest(node, this.#source.readText(fileName), fact.extent);
1460
+ return node.id;
1461
+ }
1462
+ }
633
1463
 
634
1464
  // pkgs/akanjs/common/trimSlashes.ts
635
1465
  var trimTrailingSlashes = (value) => {
@@ -663,7 +1493,8 @@ var ROOT_LAYOUT_ROUTE_EXPORTS = new Set([
663
1493
  "wsConnect",
664
1494
  "layoutStyle",
665
1495
  "viewport",
666
- "McpAppRoot"
1496
+ "McpAppRoot",
1497
+ "LevelBoundary"
667
1498
  ]);
668
1499
  var RESERVED_ROUTE_CONFIG_EXPORTS = new Set([...ROOT_LAYOUT_ROUTE_EXPORTS].filter((name) => name !== "default" && !/^[A-Z]/.test(name)));
669
1500
  function parseRouteModuleKey(key) {
@@ -778,24 +1609,23 @@ rootLayout
778
1609
  }
779
1610
  declarationIn(source, name) {
780
1611
  const ts = this.#ts;
1612
+ const overloads = source.statements.filter((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === name);
1613
+ const [first] = overloads;
1614
+ const last = overloads.at(-1);
1615
+ if (first?.name && last)
1616
+ return { statement: first, declared: first.name, extent: { start: first.getStart(source), end: last.getEnd() } };
781
1617
  for (const statement of source.statements) {
782
- if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
783
- return { statement, declared: statement.name, initializer: null };
784
1618
  if (!ts.isVariableStatement(statement))
785
1619
  continue;
786
1620
  const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
787
1621
  if (declaration)
788
- return { statement, declared: declaration.name, initializer: declaration.initializer ?? null };
1622
+ return { statement, declared: declaration.name, extent: this.#reader.extentOf(source, statement) };
789
1623
  }
790
1624
  return null;
791
1625
  }
792
1626
  #guardOf(source, exported) {
793
1627
  const ts = this.#ts;
794
1628
  let current = ts.isExportAssignment(exported) ? this.#reader.unwrap(exported.expression) : null;
795
- if (current && ts.isIdentifier(current)) {
796
- const initializer = this.declarationIn(source, current.text)?.initializer;
797
- current = initializer ? this.#reader.unwrap(initializer) : null;
798
- }
799
1629
  while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
800
1630
  const { name, expression } = current.expression;
801
1631
  const [stage] = current.arguments;
@@ -807,9 +1637,20 @@ rootLayout
807
1637
  }
808
1638
  #callsIn(source, stage) {
809
1639
  const ts = this.#ts;
810
- const imported = new Set(this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from)).map(({ name }) => name));
1640
+ const imports = this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from));
1641
+ const imported = new Set(imports.filter(({ imported }) => imported !== "*").map(({ name }) => name));
1642
+ const namespaces = new Set(imports.filter(({ imported }) => imported === "*").map(({ name }) => name));
811
1643
  const calls = new Map;
812
- const callOf = (name) => {
1644
+ const callOf = (callee, shadowed) => {
1645
+ if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text) && !shadowed.has(callee.expression.text)) {
1646
+ const name = `${callee.expression.text}.${callee.name.text}`;
1647
+ if (!calls.has(name))
1648
+ calls.set(name, { name, local: null });
1649
+ return true;
1650
+ }
1651
+ if (!ts.isIdentifier(callee) || shadowed.has(callee.text))
1652
+ return false;
1653
+ const name = callee.text;
813
1654
  if (calls.has(name))
814
1655
  return true;
815
1656
  const declared = this.declarationIn(source, name);
@@ -819,23 +1660,71 @@ rootLayout
819
1660
  name,
820
1661
  local: declared && {
821
1662
  span: this.#reader.spanOf(source, declared.declared),
822
- extent: this.#reader.extentOf(source, declared.statement),
1663
+ extent: declared.extent,
823
1664
  doc: this.#reader.docOf(source, declared.statement)
824
1665
  }
825
1666
  });
826
1667
  return true;
827
1668
  };
828
- const visit = (node) => {
829
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && callOf(node.expression.text))
1669
+ const visit = (node, outer) => {
1670
+ const shadowed = this.#scopeOf(node, outer);
1671
+ if (ts.isCallExpression(node) && callOf(node.expression, shadowed))
830
1672
  return;
831
- ts.forEachChild(node, visit);
1673
+ ts.forEachChild(node, (child) => visit(child, shadowed));
832
1674
  };
833
1675
  if (ts.isIdentifier(stage))
834
- callOf(stage.text);
1676
+ callOf(stage, new Set);
835
1677
  else
836
- visit(stage);
1678
+ visit(stage, new Set);
837
1679
  return [...calls.values()];
838
1680
  }
1681
+ #scopeOf(node, outer) {
1682
+ const ts = this.#ts;
1683
+ const names = [];
1684
+ const bind = (name) => {
1685
+ if (ts.isIdentifier(name))
1686
+ names.push(name.text);
1687
+ else
1688
+ for (const element of name.elements)
1689
+ if (!ts.isOmittedExpression(element))
1690
+ bind(element.name);
1691
+ };
1692
+ const declare = (statement) => {
1693
+ if (ts.isVariableStatement(statement))
1694
+ for (const declaration of statement.declarationList.declarations)
1695
+ bind(declaration.name);
1696
+ else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
1697
+ names.push(statement.name.text);
1698
+ };
1699
+ const hoist = (child) => {
1700
+ if (ts.isFunctionLike(child) || ts.isClassLike(child))
1701
+ return;
1702
+ if (ts.isVariableDeclarationList(child) && !(child.flags & ts.NodeFlags.BlockScoped))
1703
+ for (const declaration of child.declarations)
1704
+ bind(declaration.name);
1705
+ ts.forEachChild(child, hoist);
1706
+ };
1707
+ if (ts.isFunctionLike(node)) {
1708
+ for (const parameter of node.parameters)
1709
+ bind(parameter.name);
1710
+ if (ts.isFunctionExpression(node) && node.name)
1711
+ names.push(node.name.text);
1712
+ if ("body" in node && node.body)
1713
+ ts.forEachChild(node.body, hoist);
1714
+ } else if (ts.isClassExpression(node) && node.name)
1715
+ names.push(node.name.text);
1716
+ else if (ts.isBlock(node))
1717
+ node.statements.forEach(declare);
1718
+ else if (ts.isCaseBlock(node))
1719
+ for (const clause of node.clauses)
1720
+ clause.statements.forEach(declare);
1721
+ else if (ts.isCatchClause(node) && node.variableDeclaration)
1722
+ bind(node.variableDeclaration.name);
1723
+ else if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer))
1724
+ for (const declaration of node.initializer.declarations)
1725
+ bind(declaration.name);
1726
+ return names.length ? new Set([...outer, ...names]) : outer;
1727
+ }
839
1728
  }
840
1729
 
841
1730
  // pkgs/@akanjs/language/akanGraph.ts
@@ -851,10 +1740,6 @@ var builtinGuards = {
851
1740
  Public: { scope: "account", doc: "Passes every caller, signed in or not." },
852
1741
  None: { scope: "account", doc: "Refuses every caller." }
853
1742
  };
854
- var frameworkOwner = "akanjs";
855
- var frameworkPattern = /^akanjs(?:\/|$)/;
856
- var aliasPattern = /^@(apps|libs)\/([^/]+)/;
857
- var maxMapDepth = 4;
858
1743
 
859
1744
  class GraphBuilder {
860
1745
  #source;
@@ -862,16 +1747,21 @@ class GraphBuilder {
862
1747
  #edges = new Map;
863
1748
  #contexts = new Map;
864
1749
  #members = new Map;
865
- #guards = new Map;
866
- #mapEntries = new Map;
867
1750
  #routeFiles = new Map;
868
- #declarations = new Map;
869
1751
  #routes;
870
- #adaptors = new Map;
871
- #plugs = new Map;
1752
+ #resolver;
1753
+ #adaptors;
872
1754
  constructor(source) {
873
1755
  this.#source = source;
874
1756
  this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
1757
+ this.#resolver = new GuardResolver(source, this.#routes);
1758
+ this.#adaptors = new AdaptorResolver(source, this.#resolver, {
1759
+ key: (kind, owner, scope) => this.#key(kind, owner, scope),
1760
+ addNode: (id, kind, owner, name, span, detail) => this.#addNode(id, kind, owner, null, name, span, detail),
1761
+ addEdge: (from, to, kind, label, at) => this.#addEdge(from, to, kind, label, at),
1762
+ digest: (node, text, extent) => this.#digest(node, text, extent),
1763
+ location: (span) => this.#location(span)
1764
+ });
875
1765
  }
876
1766
  build() {
877
1767
  const { facts } = this.#source;
@@ -883,17 +1773,9 @@ class GraphBuilder {
883
1773
  ...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
884
1774
  ];
885
1775
  const typed = [];
886
- for (const file of facts)
887
- for (const fact of file.adaptors) {
888
- const declared = { fact, owner: file.owner, fileName: file.fileName };
889
- this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
890
- this.#adaptorNodeOf(declared);
891
- }
892
- for (const file of facts)
893
- for (const fact of file.adaptors)
894
- for (const plug of fact.plugs)
895
- this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
896
- this.#declareGuards(facts);
1776
+ this.#adaptors.declare(facts);
1777
+ for (const guard of this.#resolver.declare(facts))
1778
+ this.#guardNodeOf(guard);
897
1779
  for (const file of facts) {
898
1780
  const { owner } = file;
899
1781
  const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
@@ -924,10 +1806,7 @@ class GraphBuilder {
924
1806
  }
925
1807
  for (const method of service.methods)
926
1808
  this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
927
- for (const plug of service.plugs)
928
- this.#plugs.set(`${owner ?? ""}
929
- ${refName}
930
- ${plug.name}`, this.#adaptorIdOf(plug.target, file));
1809
+ this.#adaptors.addService(refName, service, file);
931
1810
  }
932
1811
  for (const filter of file.filters) {
933
1812
  if (!filter.name.endsWith("Filter"))
@@ -937,31 +1816,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
937
1816
  for (const query of filter.queries)
938
1817
  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);
939
1818
  }
940
- for (const slice of file.slices) {
941
- const refName = slice.model;
942
- if (!refName)
943
- continue;
944
- const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards);
945
- for (const guard of slice.guards)
946
- this.#guard(fallback, guard, file);
947
- for (const key of slice.keys) {
948
- const detail = [...key.guards, ...key.live ? ["live"] : []];
949
- const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
950
- for (const guard of key.guards)
951
- this.#guard(node, guard, file);
952
- }
953
- }
954
- for (const endpoint of file.endpoints) {
955
- const refName = endpoint.model;
956
- if (!refName)
957
- continue;
958
- for (const key of endpoint.keys) {
959
- const detail = [...key.verb ? [key.verb] : [], ...key.guards];
960
- const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
961
- for (const guard of key.guards)
962
- this.#guard(node, guard, file);
963
- }
964
- }
1819
+ this.#addGuarded(file);
965
1820
  for (const internal of file.internals)
966
1821
  for (const key of internal.keys)
967
1822
  if (internal.model && key.type !== "resolveField")
@@ -977,7 +1832,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
977
1832
  types.set(node.label, [...types.get(node.label) ?? [], node]);
978
1833
  for (const node of typed)
979
1834
  for (const field of node.fields ?? []) {
980
- const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#visible(type.owner, node.owner));
1835
+ const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#resolver.visible(type.owner, node.owner));
981
1836
  field.ref = (candidates.find((type) => type.owner === node.owner) ?? candidates[0])?.id ?? null;
982
1837
  if (field.ref)
983
1838
  this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
@@ -1059,61 +1914,52 @@ ${label ?? ""}`;
1059
1914
  else
1060
1915
  this.#edges.set(key, { from, to, kind, label, count: 1, at });
1061
1916
  }
1062
- #guard(node, guard, file) {
1063
- const [role, local] = guard.includes(" ") ? guard.split(" ") : [null, guard];
1064
- if (!local || local === "?")
1065
- return;
1066
- this.#addEdge(node.id, this.#guardIdOf(local, file), "guard", role, node.at);
1067
- }
1068
- #declareGuards(facts) {
1069
- const factories = new Map;
1070
- for (const file of facts)
1071
- for (const fact of file.guardFactories) {
1072
- const factory = { fact, owner: file.owner, fileName: file.fileName, made: [] };
1073
- factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
1074
- }
1075
- const sameFile = (factory, name) => name ? factories.get(name)?.find((other) => other.fileName === factory.fileName) : undefined;
1076
- const declare = (guard, factory) => {
1077
- this.#guards.set(guard.name, [...this.#guards.get(guard.name) ?? [], guard]);
1078
- factory?.made.push(guard);
1079
- };
1080
- const madeBy = (factory, fileName, extent) => {
1081
- const parts = [{ fileName, extent }];
1082
- const seen = new Set;
1083
- for (let current = factory;current && !seen.has(current); current = sameFile(current, current.fact.via)) {
1084
- seen.add(current);
1085
- parts.push({ fileName: current.fileName, extent: current.fact.extent });
1086
- }
1087
- return parts;
1088
- };
1089
- for (const file of facts) {
1090
- const { owner, fileName } = file;
1091
- for (const { name, span, extent, scope, doc, factory: callee } of file.guards) {
1092
- const factory = callee ? factories.get(callee)?.find((other) => other.fileName === fileName) : undefined;
1093
- declare({ name, owner, span, scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
1917
+ #addGuarded(file) {
1918
+ const { owner } = file;
1919
+ for (const slice of file.slices) {
1920
+ const refName = slice.model;
1921
+ if (!refName)
1922
+ continue;
1923
+ const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards.map((guard) => guardLabelOf(guard, file.guardLabels)));
1924
+ for (const guard of slice.guards)
1925
+ this.#guard(fallback, guard, file);
1926
+ for (const key of slice.keys) {
1927
+ const detail = [
1928
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels)),
1929
+ ...key.live ? ["live"] : []
1930
+ ];
1931
+ const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1932
+ for (const guard of key.guards)
1933
+ this.#guard(node, guard, file);
1094
1934
  }
1095
- for (const { name, span, extent, doc, callee } of file.guardCalls) {
1096
- const imported = file.imports.find((item) => item.name === callee);
1097
- if (!imported)
1098
- continue;
1099
- const from = this.#ownerOfSpecifier(fileName, imported.from);
1100
- const candidates = factories.get(imported.imported === "default" || imported.imported === "*" ? callee : imported.imported) ?? [];
1101
- const factory = candidates.find((other) => other.owner === from) ?? this.#soleVisible(candidates, from ?? owner);
1102
- if (factory)
1103
- declare({ name, owner, span, scope: factory.fact.scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
1935
+ }
1936
+ for (const endpoint of file.endpoints) {
1937
+ const refName = endpoint.model;
1938
+ if (!refName)
1939
+ continue;
1940
+ for (const key of endpoint.keys) {
1941
+ const detail = [
1942
+ ...key.verb ? [key.verb] : [],
1943
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels))
1944
+ ];
1945
+ const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1946
+ for (const guard of key.guards)
1947
+ this.#guard(node, guard, file);
1104
1948
  }
1105
1949
  }
1106
- for (const list of factories.values())
1107
- for (const { fact, made } of list)
1108
- if (made.every((guard) => !guard.doc))
1109
- for (const guard of made)
1110
- guard.doc = fact.doc;
1111
- for (const list of this.#guards.values())
1112
- for (const guard of list)
1113
- this.#guardNodeOf(guard);
1950
+ }
1951
+ #guard(node, guard, file) {
1952
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
1953
+ if (!local || local === "?")
1954
+ return;
1955
+ const id = local.includes("@") ? this.#madeInPlaceIdOf(local, file) : this.#guardIdOf(local, file);
1956
+ if (id)
1957
+ this.#addEdge(node.id, id, "guard", role, node.at);
1114
1958
  }
1115
1959
  #guardIdOf(local, site) {
1116
- const found = this.#findGuard(local, site, 0);
1960
+ const found = this.#resolver.find(local, site);
1961
+ if ("external" in found)
1962
+ return null;
1117
1963
  if ("guard" in found)
1118
1964
  return this.#guardNodeOf(found.guard);
1119
1965
  const { name, from } = found;
@@ -1126,99 +1972,18 @@ ${label ?? ""}`;
1126
1972
  node.doc = builtin.doc;
1127
1973
  return node.id;
1128
1974
  }
1129
- #findGuard(local, site, depth) {
1130
- const [head = local, member] = local.split(".");
1131
- const imported = site.imports.find((item) => item.name === head);
1132
- const from = imported ? this.#ownerOfSpecifier(site.fileName, imported.from) : undefined;
1133
- const holder = imported ? from : site.owner;
1134
- if (member && imported?.imported !== "*" && holder !== undefined && holder !== frameworkOwner && depth < maxMapDepth) {
1135
- const objectName = imported && imported.imported !== "default" ? imported.imported : head;
1136
- for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
1137
- const found = this.#findGuard(entry.name, entry.site, depth + 1);
1138
- if ("guard" in found)
1139
- return found;
1140
- }
1141
- }
1142
- const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1143
- const declared = this.#guards.get(name) ?? [];
1144
- 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));
1145
- return guard ? { guard } : { name, from };
1146
- }
1147
- #mapEntriesOf(objectName, member, fileName, owner) {
1148
- const key = `${objectName}
1149
- ${member}
1150
- ${fileName ?? ""}
1151
- ${owner ?? ""}`;
1152
- const cached = this.#mapEntries.get(key);
1153
- if (cached)
1154
- return cached;
1155
- const { ts, reader } = this.#source;
1156
- const entries = [];
1157
- const fileNames = [
1158
- ...fileName ? [fileName] : [],
1159
- ...this.#source.files.filter(([other, words]) => other !== fileName && owner !== null && !testFilePattern.test(other) && words.includes(`
1160
- ${objectName}
1161
- `) && words.includes(`
1162
- ${member}
1163
- `) && this.#source.ownerOf(other) === owner).map(([other]) => other)
1164
- ];
1165
- for (const holder of fileNames) {
1166
- const text = this.#source.readText(holder);
1167
- if (text === undefined)
1168
- continue;
1169
- const source = this.#source.parse(holder, text);
1170
- const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
1171
- for (const statement of source.statements) {
1172
- if (!ts.isVariableStatement(statement))
1173
- continue;
1174
- const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
1175
- const object = map && reader.unwrap(map);
1176
- if (!object || !ts.isObjectLiteralExpression(object))
1177
- continue;
1178
- for (const property of object.properties) {
1179
- if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
1180
- entries.push({ name: member, site });
1181
- if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
1182
- continue;
1183
- const value = reader.unwrap(property.initializer);
1184
- if (ts.isIdentifier(value))
1185
- entries.push({ name: value.text, site });
1186
- else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
1187
- entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
1188
- }
1189
- }
1190
- }
1191
- this.#mapEntries.set(key, entries);
1192
- return entries;
1193
- }
1194
- #declarationOf(owner, name, near) {
1195
- const key = `${owner}
1196
- ${name}
1197
- ${near ?? ""}`;
1198
- const cached = this.#declarations.get(key);
1199
- if (cached !== undefined)
1200
- return cached;
1201
- const isNear = (fileName) => {
1202
- const relative = this.#source.relative(fileName);
1203
- return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1204
- };
1205
- const candidates = this.#source.files.filter(([fileName, words]) => !testFilePattern.test(fileName) && words.includes(`
1206
- ${name}
1207
- `) && this.#source.ownerOf(fileName) === owner).map(([fileName]) => fileName);
1208
- let found = null;
1209
- for (const fileName of [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))]) {
1210
- const text = this.#source.readText(fileName);
1211
- if (text === undefined)
1212
- continue;
1213
- const source = this.#source.parse(fileName, text);
1214
- const declared = this.#routes.declarationIn(source, name);
1215
- if (!declared)
1216
- continue;
1217
- found = { source, statement: declared.statement, declared: declared.declared };
1218
- break;
1219
- }
1220
- this.#declarations.set(key, found);
1221
- return found;
1975
+ #madeInPlaceIdOf(name, file) {
1976
+ const made = this.#resolver.madeInPlace(file.fileName, name);
1977
+ if (made)
1978
+ return this.#guardNodeOf(made);
1979
+ const head = name.slice(name.indexOf(":") + 1, name.indexOf("@")).split(".")[0] ?? name;
1980
+ const imported = file.imports.find((item) => item.name === head);
1981
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : file.owner;
1982
+ if (from === undefined || from === frameworkOwner)
1983
+ return null;
1984
+ const scope = `${this.#resolver.inOwner(file.fileName, file.owner)}#${name}`;
1985
+ const label = guardLabelOf(name, file.guardLabels);
1986
+ return this.#addNode(this.#key("guard", file.owner, scope), "guard", file.owner, null, name, null, [], label).id;
1222
1987
  }
1223
1988
  #addRoutes() {
1224
1989
  const routes = [];
@@ -1271,22 +2036,23 @@ ${dir}`, node);
1271
2036
  }
1272
2037
  #routeGuardIdOf({ name: local, local: declared }, fact, text) {
1273
2038
  if (declared) {
1274
- const file = this.#source.relative(fact.fileName).slice(fact.owner.length + 1);
1275
- const id = this.#key("guard", fact.owner, `${file}#${local}`);
2039
+ const id = this.#key("guard", fact.owner, `${this.#resolver.inOwner(fact.fileName, fact.owner)}#${local}`);
1276
2040
  return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
1277
2041
  }
1278
- const imported = fact.imports.find((item) => item.name === local);
1279
- if (!imported)
2042
+ const [head = local, member] = local.split(".");
2043
+ const imported = fact.imports.find((item) => item.name === head);
2044
+ if (!imported || member && imported.imported !== "*")
1280
2045
  return null;
1281
- const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
1282
- const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
2046
+ const name = member ?? (imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local);
2047
+ const from = this.#resolver.ownerOfSpecifier(fact.fileName, imported.from);
1283
2048
  if (!from || from === frameworkOwner)
1284
2049
  return null;
1285
- const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
2050
+ const found = this.#resolver.declarationOf(from, name, this.#resolver.pathOfSpecifier(fact.fileName, imported.from));
1286
2051
  if (!found)
1287
2052
  return null;
1288
2053
  const { reader } = this.#source;
1289
- 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));
2054
+ const owner = this.#source.ownerOf(found.source.fileName) ?? from;
2055
+ 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);
1290
2056
  }
1291
2057
  #routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
1292
2058
  const isNew = !this.#nodes.has(id);
@@ -1296,60 +2062,22 @@ ${dir}`, node);
1296
2062
  this.#digest(node, text, extent);
1297
2063
  return node.id;
1298
2064
  }
1299
- #adaptorIdOf(local, file) {
1300
- const [head, member] = local.split(".");
1301
- const imported = file.imports.find((item) => item.name === head);
1302
- const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
1303
- const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1304
- const declared = this.#adaptors.get(name) ?? [];
1305
- 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)));
1306
- if (found)
1307
- return this.#adaptorNodeOf(found);
1308
- const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
1309
- const detail = from === frameworkOwner ? [frameworkOwner] : [];
1310
- return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
1311
- }
1312
- #adaptorNodeOf({ fact, owner, fileName }) {
1313
- const id = this.#key("adaptor", owner, fact.name);
1314
- const detail = fact.refName ? [fact.refName] : [];
1315
- const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
1316
- if (fact.doc && !node.doc)
1317
- node.doc = fact.doc;
1318
- this.#digest(node, this.#source.readText(fileName), fact.extent);
1319
- return node.id;
1320
- }
1321
- #guardNodeOf({ name, owner, span, scope, doc, parts }) {
1322
- const node = this.#addNode(this.#key("guard", owner, name), "guard", owner, null, name, span, scope ? [scope] : []);
2065
+ #guardNodeOf({ name, owner, span, scope, doc, parts, file, label }) {
2066
+ const id = this.#key("guard", owner, file ? `${file}#${name}` : name);
2067
+ const node = this.#addNode(id, "guard", owner, null, name, span, scope ? [scope] : [], label ?? name);
1323
2068
  if (doc && !node.doc)
1324
2069
  node.doc = doc;
1325
- const digest = node.digest ? null : this.#hashOf(...parts.map(({ fileName, extent }) => [
1326
- this.#source.readText(fileName),
1327
- extent
1328
- ]));
1329
- if (digest)
1330
- node.digest = digest;
2070
+ if (node.digest)
2071
+ return node.id;
2072
+ const digest = this.#hashOf(...parts.map(({ fileName, extent }) => [this.#source.readText(fileName), extent]));
2073
+ if (!digest)
2074
+ return node.id;
2075
+ node.digest = digest;
2076
+ const sources = [...new Set(parts.map(({ fileName }) => this.#source.relative(fileName)))].filter((source) => source !== node.at?.file);
2077
+ if (sources.length)
2078
+ node.sources = sources;
1331
2079
  return node.id;
1332
2080
  }
1333
- #ownerOfSpecifier(fileName, specifier) {
1334
- if (frameworkPattern.test(specifier))
1335
- return frameworkOwner;
1336
- const path = this.#pathOfSpecifier(fileName, specifier);
1337
- return path === null ? undefined : ownerOf(path) ?? undefined;
1338
- }
1339
- #pathOfSpecifier(fileName, specifier) {
1340
- const alias = specifier.match(aliasPattern);
1341
- if (alias)
1342
- return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1343
- if (!specifier.startsWith("."))
1344
- return null;
1345
- const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1346
- for (const segment of specifier.split("/"))
1347
- if (segment === "..")
1348
- segments.pop();
1349
- else if (segment !== ".")
1350
- segments.push(segment);
1351
- return segments.join("/");
1352
- }
1353
2081
  #moduleDirOf(fileName) {
1354
2082
  const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
1355
2083
  return dir?.startsWith("_") && !dir.startsWith("__") ? dir.slice(1) : dir;
@@ -1385,20 +2113,13 @@ ${dir}`, node);
1385
2113
  visit(constant);
1386
2114
  return [...fields.values()];
1387
2115
  }
1388
- #visible(target, from) {
1389
- return !from || !target || target === from || target.startsWith("libs/");
1390
- }
1391
- #soleVisible(declared, from) {
1392
- const visible = declared.filter((other) => this.#visible(other.owner, from));
1393
- return visible.length === 1 ? visible[0] : undefined;
1394
- }
1395
2116
  #targetNodeOf({ family, owner, refName, sourceKey }) {
1396
2117
  const isDefault = /^[A-Z]/.test(sourceKey);
1397
2118
  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;
1398
2119
  return id && this.#nodes.has(id) ? id : null;
1399
2120
  }
1400
2121
  #membersNamed(kind, name, owner, refName) {
1401
- const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#visible(node.owner, owner));
2122
+ const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#resolver.visible(node.owner, owner));
1402
2123
  const own = nodes.filter((node) => node.owner === owner);
1403
2124
  return (own.length ? own : nodes).map((node) => node.id);
1404
2125
  }
@@ -1443,10 +2164,8 @@ st
1443
2164
  visit(source);
1444
2165
  }
1445
2166
  #link(fileName, source, identifier, chain, context, owner) {
1446
- const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#plugs.get(`${context.owner ?? ""}
1447
- ${context.module}
1448
- ${chain[1]}`) : undefined;
1449
- const resolved = plugged ? [plugged] : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
2167
+ const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#adaptors.pluggedOf(context.owner, context.module, chain[1]) : [];
2168
+ const resolved = plugged.length ? plugged : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
1450
2169
  const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
1451
2170
  if (!targets.length)
1452
2171
  return;
@@ -1752,6 +2471,10 @@ var endpointNames = (key) => {
1752
2471
  };
1753
2472
 
1754
2473
  // pkgs/@akanjs/language/nameTable.ts
2474
+ var guardDetailOf = (facts, guards) => {
2475
+ const labels = Object.assign({}, ...facts.map((file) => file.guardLabels));
2476
+ return guards.length ? [`guards: ${guards.map((guard) => guardLabelOf(guard, labels)).join(", ")}`] : [];
2477
+ };
1755
2478
  var capitalize3 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
1756
2479
 
1757
2480
  class NameTable {
@@ -1851,7 +2574,7 @@ class NameTable {
1851
2574
  role: "sig",
1852
2575
  gate: "sig",
1853
2576
  label: sliceClass.name,
1854
- detail: sliceClass.guards.length ? [`guards: ${sliceClass.guards.join(", ")}`] : []
2577
+ detail: guardDetailOf(facts, sliceClass.guards)
1855
2578
  });
1856
2579
  if (modelDoc)
1857
2580
  this.#add({
@@ -1916,7 +2639,7 @@ class NameTable {
1916
2639
  sourceKey: slice.name,
1917
2640
  span: slice.span,
1918
2641
  label: `slice ${refName} (default)`,
1919
- detail: slice.guards.length ? [`guards: ${slice.guards.join(", ")}`] : []
2642
+ detail: guardDetailOf(facts, slice.guards)
1920
2643
  };
1921
2644
  this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
1922
2645
  for (const key of slice.keys)
@@ -1926,7 +2649,7 @@ class NameTable {
1926
2649
  sourceKey: key.name,
1927
2650
  span: key.span,
1928
2651
  label: `slice ${key.name}`,
1929
- detail: [...key.guards.length ? [`guards: ${key.guards.join(", ")}`] : [], ...key.live ? ["live"] : []]
2652
+ detail: [...guardDetailOf(facts, key.guards), ...key.live ? ["live"] : []]
1930
2653
  }, refName, key.name, modelNameSet, slice.name);
1931
2654
  }
1932
2655
  if (filterClass) {
@@ -1983,7 +2706,7 @@ class NameTable {
1983
2706
  role: key.verb === "pubsub" ? "subscribe" : "listen",
1984
2707
  gate: null,
1985
2708
  label: `${key.verb} ${key.name}`,
1986
- detail: key.guards.length ? [`guards: ${key.guards.join(", ")}`] : []
2709
+ detail: guardDetailOf(facts, key.guards)
1987
2710
  });
1988
2711
  }
1989
2712
  }
@@ -2040,15 +2763,13 @@ var sourcePattern = /\.(constant|document|service|signal|store)\.ts$/;
2040
2763
  var dictionaryPattern = /\.dictionary\.ts$/;
2041
2764
  var srvkitPattern = /\/srvkit\//;
2042
2765
  var wordPattern = /[A-Za-z_$][\w$]*/g;
2043
- var identifierPattern = /^[A-Za-z_$][\w$]*$/;
2044
- var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
2766
+ var identifierPattern2 = /^[A-Za-z_$][\w$]*$/;
2045
2767
  var stateReceivers = new Set(["use", "sel", "get", "do"]);
2046
2768
  var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
2047
2769
  var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
2048
2770
  var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
2049
2771
  var parseCacheSize = 64;
2050
2772
  var pathOf = (fileName) => fileName.replace(/\\/g, "/");
2051
- var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
2052
2773
  var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
2053
2774
 
2054
2775
  class AkanIndex {
@@ -2151,7 +2872,7 @@ ${[...new Set(text.match(wordPattern))].join(`
2151
2872
  }
2152
2873
  renameAt(givenName, offset, newKey, readText) {
2153
2874
  const sources = this.#sourceEntriesAt(pathOf(givenName), offset).filter((entry) => entry.renamed);
2154
- if (!sources.length || !identifierPattern.test(newKey))
2875
+ if (!sources.length || !identifierPattern2.test(newKey))
2155
2876
  return null;
2156
2877
  return [
2157
2878
  { span: sources[0].span, newText: newKey },
@@ -2179,6 +2900,8 @@ canPass
2179
2900
  adapt
2180
2901
  `) || words.includes(`
2181
2902
  dangerouslyAdapt
2903
+ `) || words.includes(`
2904
+ applyAdaptor
2182
2905
  `);
2183
2906
  }
2184
2907
  static #sourceKeyOf({ family, sourceKey, label, detail, span }) {
@@ -2859,6 +3582,11 @@ ${union.name}`)));
2859
3582
  const field = this.#memberOf(this.#classOf(receiver), receiver.name.text);
2860
3583
  return field && ts.isPropertyDeclaration(field) ? this.#namesIn(field.type) : new Set;
2861
3584
  }
3585
+ if (ts.isCallExpression(receiver) && ts.isPropertyAccessExpression(receiver.expression) && receiver.expression.expression.kind === ts.SyntaxKind.ThisKeyword) {
3586
+ const field = this.#memberOf(this.#classOf(receiver), receiver.expression.name.text);
3587
+ const type = field && ts.isPropertyDeclaration(field) ? field.type : undefined;
3588
+ return type && ts.isFunctionTypeNode(type) ? this.#namesIn(type.type) : new Set;
3589
+ }
2862
3590
  return this.#declaredTypeNames(receiver) ?? new Set;
2863
3591
  }
2864
3592
  #memberName(name) {