@akanjs/language 3.2.0-rc.0 → 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.js CHANGED
@@ -8,7 +8,8 @@ var isEdited = (before, after) => before.digest !== undefined && after.digest !=
8
8
  var byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
9
9
 
10
10
  class AkanGraphDiff {
11
- static between(base, head) {
11
+ static between(given, head) {
12
+ const base = AkanGraphDiff.#renamed(given, head);
12
13
  const baseNodes = new Map(base.nodes.map((node) => [node.id, node]));
13
14
  const headNodes = new Map(head.nodes.map((node) => [node.id, node]));
14
15
  const marked = new Map;
@@ -27,6 +28,9 @@ class AkanGraphDiff {
27
28
  ...head.edges.flatMap((edge) => !baseKeys.has(edgeKeyOf(edge)) ? [{ ...edge, change: "added" }] : marked.has(edge.from) || marked.has(edge.to) ? [edge] : []),
28
29
  ...base.edges.filter((edge) => !headKeys.has(edgeKeyOf(edge))).map((edge) => ({ ...edge, change: "removed" }))
29
30
  ];
31
+ const shown = new Set([...marked.keys(), ...edges.flatMap((edge) => [edge.from, edge.to])]);
32
+ const listed = new Set(edges.map(edgeKeyOf));
33
+ edges.push(...head.edges.filter((edge) => edge.kind === "binds" && shown.has(edge.from) && !listed.has(edgeKeyOf(edge))));
30
34
  const context = edges.flatMap((edge) => [edge.from, edge.to]).filter((id) => !marked.has(id)).flatMap((id) => {
31
35
  const node = headNodes.get(id) ?? baseNodes.get(id);
32
36
  return node ? [node] : [];
@@ -38,6 +42,40 @@ class AkanGraphDiff {
38
42
  summary[node.change] += 1;
39
43
  return { version: 1, nodes, edges, summary };
40
44
  }
45
+ static #renamed(base, head) {
46
+ const baseIds = new Set(base.nodes.map((node) => node.id));
47
+ const headIds = new Set(head.nodes.map((node) => node.id));
48
+ const pairKeyOf = ({ kind, owner, name, at }) => at ? `${kind}
49
+ ${owner ?? ""}
50
+ ${name}
51
+ ${at.file}` : null;
52
+ const shortIdOf = ({ kind, owner, name }) => `${kind}:${owner ?? ""}:${name}`;
53
+ const isFileKeyed = (node) => node.id !== shortIdOf(node) && node.id.startsWith(`${node.kind}:${node.owner ?? ""}:`) && node.id.endsWith(`#${node.name}`);
54
+ const fromBase = new Map(base.nodes.flatMap((node) => {
55
+ const key = headIds.has(node.id) ? null : pairKeyOf(node);
56
+ return key ? [[key, node]] : [];
57
+ }));
58
+ const renames = new Map;
59
+ for (const node of head.nodes) {
60
+ if (baseIds.has(node.id))
61
+ continue;
62
+ const key = pairKeyOf(node);
63
+ const before = key ? fromBase.get(key) : undefined;
64
+ if (!before)
65
+ continue;
66
+ const isPair = isFileKeyed(node) ? before.id === shortIdOf(node) : isFileKeyed(before) && node.id === shortIdOf(before);
67
+ if (isPair)
68
+ renames.set(before.id, node.id);
69
+ }
70
+ if (!renames.size)
71
+ return base;
72
+ const rename = (id) => renames.get(id) ?? id;
73
+ return {
74
+ ...base,
75
+ nodes: base.nodes.map((node) => renames.has(node.id) ? { ...node, id: rename(node.id) } : node),
76
+ edges: base.edges.map((edge) => ({ ...edge, from: rename(edge.from), to: rename(edge.to) }))
77
+ };
78
+ }
41
79
  static #mark(node, change) {
42
80
  return node.fields ? { ...node, change, fields: node.fields.map((field) => ({ ...field, change })) } : { ...node, change };
43
81
  }
@@ -71,6 +109,261 @@ class AkanGraphDiff {
71
109
  return node.fields ? { ...node, fields: node.fields.map(({ digest: _field, ...field }) => field) } : node;
72
110
  }
73
111
  }
112
+ // pkgs/@akanjs/language/escapeRegExp.ts
113
+ var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
114
+ var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
115
+
116
+ // pkgs/@akanjs/language/guardFacts.ts
117
+ var guardLabelOf = (guard, labels) => {
118
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
119
+ const label = labels[local];
120
+ return label === undefined ? guard : role ? `${role} ${label}` : label;
121
+ };
122
+ var maxLabelArgs = 32;
123
+ var maxLayers = 4;
124
+ var guardRefReaderOf = (ts, ast, unwrap) => {
125
+ const pathOf = (expression) => {
126
+ if (ts.isIdentifier(expression))
127
+ return expression.text;
128
+ if (!ts.isPropertyAccessExpression(expression))
129
+ return null;
130
+ const head = pathOf(expression.expression);
131
+ return head && `${head}.${expression.name.text}`;
132
+ };
133
+ const layersOf = (call) => {
134
+ const inner = unwrap(call.expression);
135
+ if (!ts.isCallExpression(inner))
136
+ return { root: call, layers: 0 };
137
+ const { root, layers } = layersOf(inner);
138
+ return { root, layers: layers + 1 };
139
+ };
140
+ const calleeOf = (call) => pathOf(unwrap(layersOf(call).root.expression)) ?? "?";
141
+ const labelOf = (source, call) => {
142
+ const args = call.arguments.map((arg) => arg.getText(source)).join(", ");
143
+ const inner = unwrap(call.expression);
144
+ const head = ts.isCallExpression(inner) ? labelOf(source, inner) : pathOf(inner) ?? "?";
145
+ return `${head}(${args.length <= maxLabelArgs && !args.includes(`
146
+ `) ? args : "…"})`;
147
+ };
148
+ const guardRefOf = (expression, made, member) => {
149
+ const value = unwrap(expression);
150
+ if (!ts.isCallExpression(value))
151
+ return pathOf(value) ?? ast.expressionName(value);
152
+ const prefix = `${member}:${calleeOf(value)}@`;
153
+ const name = `${prefix}${made.filter((other) => other.name.startsWith(prefix)).length + 1}`;
154
+ made.push({ call: value, name });
155
+ return name;
156
+ };
157
+ const guardsIn = (source, object, made, member) => {
158
+ const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
159
+ if (!guards)
160
+ return [];
161
+ const refOf = (expression) => guardRefOf(expression, made, member);
162
+ if (ts.isArrayLiteralExpression(guards))
163
+ return guards.elements.map(refOf).filter((name) => name !== null);
164
+ if (ts.isObjectLiteralExpression(guards))
165
+ return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
166
+ const value = unwrap(property.initializer);
167
+ const names = ts.isArrayLiteralExpression(value) ? value.elements.map(refOf) : [refOf(value)];
168
+ return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
169
+ });
170
+ const single = refOf(guards);
171
+ return single ? [single] : [];
172
+ };
173
+ return { pathOf, layersOf, labelOf, guardsIn };
174
+ };
175
+ var guardFactoryReaderOf = (ts, ast, { spanOf, extentOf, unwrap, docOf }, pathOf, layersOf) => {
176
+ const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
177
+ const scopeOf = (node) => {
178
+ const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
179
+ const value = scope && unwrap(scope);
180
+ return value && ts.isStringLiteralLike(value) ? value.text : null;
181
+ };
182
+ const functionsOf = (source) => {
183
+ const functions = new Map;
184
+ for (const statement of source.statements) {
185
+ if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
186
+ functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
187
+ if (!ts.isVariableStatement(statement))
188
+ continue;
189
+ for (const declaration of statement.declarationList.declarations) {
190
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
191
+ if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
192
+ functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
193
+ }
194
+ }
195
+ return functions;
196
+ };
197
+ const returnedOf = (body, guardNames, depth = 0) => {
198
+ const locals = new Map;
199
+ const pending = [];
200
+ const visit = (node) => {
201
+ if (ts.isClassDeclaration(node) && node.name)
202
+ locals.set(node.name.text, node);
203
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
204
+ locals.set(node.name.text, node.initializer);
205
+ if (ts.isReturnStatement(node) && node.expression)
206
+ pending.push(node.expression);
207
+ if (!ts.isFunctionLike(node) && !ts.isClassLike(node))
208
+ ts.forEachChild(node, visit);
209
+ };
210
+ if (ts.isBlock(body))
211
+ ts.forEachChild(body, visit);
212
+ else
213
+ pending.push(body);
214
+ let guard = null;
215
+ const calls = [];
216
+ const seen = new Set;
217
+ const passed = new Set;
218
+ const returned = [];
219
+ for (let node = pending.pop();node; node = pending.pop()) {
220
+ if (seen.has(node))
221
+ continue;
222
+ seen.add(node);
223
+ const value = ts.isExpression(node) ? unwrap(node) : node;
224
+ if ((ts.isClassExpression(value) || ts.isClassDeclaration(value)) && implementsGuard(value, guardNames))
225
+ guard ??= value;
226
+ else if ((ts.isArrowFunction(value) || ts.isFunctionExpression(value)) && !passed.has(node) && depth < maxLayers)
227
+ returned.push(value.body);
228
+ else if (ts.isConditionalExpression(value))
229
+ pending.push(value.whenTrue, value.whenFalse);
230
+ else if (ts.isBinaryExpression(value))
231
+ pending.push(value.left, value.right);
232
+ else if (ts.isIdentifier(value)) {
233
+ const local = locals.get(value.text);
234
+ if (local)
235
+ pending.push(local);
236
+ } else if (ts.isCallExpression(value)) {
237
+ const { root, layers } = layersOf(value);
238
+ const callee = pathOf(unwrap(root.expression));
239
+ if (callee && !passed.has(node) && !calls.some((call) => call.callee === callee))
240
+ calls.push({ callee, layers });
241
+ for (let call = value;ts.isCallExpression(call); call = unwrap(call.expression)) {
242
+ for (const arg of call.arguments)
243
+ passed.add(arg);
244
+ pending.push(...call.arguments);
245
+ }
246
+ }
247
+ }
248
+ const [inner] = returned;
249
+ if (guard || calls.length || !inner)
250
+ return { guard, calls, layers: 0 };
251
+ const nested = returnedOf(inner, guardNames, depth + 1);
252
+ return { ...nested, layers: nested.layers + 1 };
253
+ };
254
+ const guardFactoriesOf = (source, guardNames, isImported) => {
255
+ const functions = functionsOf(source);
256
+ const returned = new Map([...functions].map(([name, { body }]) => [name, returnedOf(body, guardNames)]));
257
+ const members = new Set;
258
+ const isFactoryCall = (callee) => members.has(callee) || !functions.has(callee) && isImported(callee);
259
+ for (let isGrowing = true;isGrowing; ) {
260
+ isGrowing = false;
261
+ for (const [name, { guard, calls }] of returned) {
262
+ if (members.has(name) || !guard && !calls.some(({ callee }) => isFactoryCall(callee)))
263
+ continue;
264
+ members.add(name);
265
+ isGrowing = true;
266
+ }
267
+ }
268
+ const factories = new Map;
269
+ for (const name of members) {
270
+ const found = functions.get(name);
271
+ const made = returned.get(name);
272
+ if (!found || !made)
273
+ continue;
274
+ factories.set(name, {
275
+ name,
276
+ span: spanOf(source, found.name),
277
+ extent: extentOf(source, found.statement),
278
+ scope: made.guard ? scopeOf(made.guard) : null,
279
+ doc: docOf(source, found.statement),
280
+ direct: !!made.guard,
281
+ via: made.calls.filter(({ callee }) => isFactoryCall(callee)),
282
+ layers: made.layers
283
+ });
284
+ }
285
+ return factories;
286
+ };
287
+ return { implementsGuard, scopeOf, guardFactoriesOf };
288
+ };
289
+ var createGuardFactReader = (ts, ast, base) => {
290
+ const { spanOf, extentOf, unwrap, docOf } = base;
291
+ const { pathOf, layersOf, labelOf, guardsIn } = guardRefReaderOf(ts, ast, unwrap);
292
+ const { implementsGuard, scopeOf, guardFactoriesOf } = guardFactoryReaderOf(ts, ast, base, pathOf, layersOf);
293
+ const guardsOf = (source, imports, madeInPlace) => {
294
+ const guardNames = new Set([
295
+ "Guard",
296
+ ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
297
+ ]);
298
+ const imported = new Set(imports.map(({ name }) => name));
299
+ const isImported = (callee) => imported.has(callee.split(".")[0] ?? callee);
300
+ const factories = guardFactoriesOf(source, guardNames, isImported);
301
+ const guards = [];
302
+ const calls = [];
303
+ const makeOf = (made, value, isInPlace = false) => {
304
+ if (ts.isClassExpression(value) && implementsGuard(value, guardNames)) {
305
+ guards.push({ ...made, scope: scopeOf(value), factory: null, layers: 0 });
306
+ return;
307
+ }
308
+ const { root, layers } = ts.isCallExpression(value) ? layersOf(value) : { root: null, layers: 0 };
309
+ const callee = root && pathOf(unwrap(root.expression));
310
+ const factory = callee && factories.get(callee);
311
+ if (factory)
312
+ guards.push({ ...made, scope: factory.scope, factory: factory.name, layers });
313
+ else if (callee && (isImported(callee) || isInPlace))
314
+ calls.push({ ...made, callee, kind: "call", scope: null, layers });
315
+ };
316
+ for (const statement of source.statements) {
317
+ if (ts.isClassDeclaration(statement) && statement.name) {
318
+ const declared = {
319
+ name: statement.name.text,
320
+ span: spanOf(source, statement.name),
321
+ extent: extentOf(source, statement),
322
+ scope: scopeOf(statement),
323
+ doc: docOf(source, statement)
324
+ };
325
+ const base = statement.heritageClauses?.find(({ token }) => token === ts.SyntaxKind.ExtendsKeyword)?.types[0];
326
+ const callee = base && pathOf(base.expression);
327
+ if (implementsGuard(statement, guardNames))
328
+ guards.push({ ...declared, factory: null, layers: 0 });
329
+ else if (callee)
330
+ calls.push({ ...declared, callee, kind: "extends", layers: 0 });
331
+ }
332
+ if (!ts.isVariableStatement(statement))
333
+ continue;
334
+ for (const declaration of statement.declarationList.declarations) {
335
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
336
+ if (!initializer || !ts.isIdentifier(declaration.name))
337
+ continue;
338
+ const holder = declaration.name.text;
339
+ makeOf({
340
+ name: holder,
341
+ span: spanOf(source, declaration.name),
342
+ extent: extentOf(source, statement),
343
+ doc: docOf(source, statement)
344
+ }, initializer);
345
+ if (!ts.isObjectLiteralExpression(initializer))
346
+ continue;
347
+ for (const property of initializer.properties) {
348
+ const key = ts.isPropertyAssignment(property) ? ast.nodeName(property.name) : null;
349
+ if (key && ts.isPropertyAssignment(property))
350
+ makeOf({
351
+ name: `${holder}.${key}`,
352
+ span: spanOf(source, property.name),
353
+ extent: extentOf(source, property),
354
+ doc: docOf(source, property)
355
+ }, unwrap(property.initializer));
356
+ }
357
+ }
358
+ }
359
+ for (const { call, name } of madeInPlace)
360
+ makeOf({ name, span: spanOf(source, call), extent: extentOf(source, call), doc: null }, call, true);
361
+ const guardLabels = Object.fromEntries(madeInPlace.map(({ call, name }) => [name, labelOf(source, call)]));
362
+ return { guards, guardFactories: [...factories.values()], guardCalls: calls, guardLabels };
363
+ };
364
+ return { guardsIn, guardsOf };
365
+ };
366
+
74
367
  // pkgs/@akanjs/language/moduleFacts.ts
75
368
  var testFilePattern = /\.(?:test|spec)\.tsx?$/;
76
369
  var moduleCallees = new Set([
@@ -139,21 +432,6 @@ var createFactReader = (ts, ast) => {
139
432
  const value = unwrap(expression);
140
433
  return ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) ? `${value.expression.text}.${value.name.text}` : ast.expressionName(value);
141
434
  };
142
- const guardsIn = (object) => {
143
- const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
144
- if (!guards)
145
- return [];
146
- if (ts.isArrayLiteralExpression(guards))
147
- return guards.elements.map(guardNameOf).filter((name) => name !== null);
148
- if (ts.isObjectLiteralExpression(guards))
149
- return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
150
- const value = unwrap(property.initializer);
151
- const names = ts.isArrayLiteralExpression(value) ? value.elements.map(guardNameOf) : [guardNameOf(value)];
152
- return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
153
- });
154
- const single = guardNameOf(guards);
155
- return single ? [single] : [];
156
- };
157
435
  const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
158
436
  const chainOf = (initializer) => {
159
437
  let root = unwrap(initializer);
@@ -170,7 +448,7 @@ var createFactReader = (ts, ast) => {
170
448
  const node = keyName(property);
171
449
  return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
172
450
  });
173
- const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
451
+ const sliceKeysOf = (source, builder, made, model) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
174
452
  if (!ts.isPropertyAssignment(property))
175
453
  return [];
176
454
  const name = ast.nodeName(property.name);
@@ -182,7 +460,7 @@ var createFactReader = (ts, ast) => {
182
460
  name,
183
461
  span: spanOf(source, property.name),
184
462
  extent: extentOf(source, property),
185
- guards: root ? guardsIn(optionObjectOf(root)) : [],
463
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
186
464
  live: methods.includes("live"),
187
465
  argCount: methods.filter((method) => argMethods.has(method)).length
188
466
  }
@@ -232,7 +510,7 @@ var createFactReader = (ts, ast) => {
232
510
  const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
233
511
  return `${ms / size}${unit}`;
234
512
  };
235
- const modesOf = (option) => {
513
+ const modesOf = (option, type) => {
236
514
  const object = option && unwrap(option);
237
515
  if (!object || !ts.isObjectLiteralExpression(object))
238
516
  return [];
@@ -242,9 +520,9 @@ var createFactReader = (ts, ast) => {
242
520
  if (name === "enabled")
243
521
  return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
244
522
  if (name === "once")
245
- return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
523
+ return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
246
524
  if (name === "lock")
247
- return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
525
+ return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
248
526
  if (name !== "serverMode" && name !== "operationMode")
249
527
  return [];
250
528
  const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
@@ -262,7 +540,8 @@ var createFactReader = (ts, ast) => {
262
540
  const { root } = chainOf(property.initializer);
263
541
  const type = root ? verbOf(root.expression) : null;
264
542
  const [first, second] = root?.arguments ?? [];
265
- const schedule = type === "cron" && first ? ts.isStringLiteralLike(first) ? first.text : first.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
543
+ const cron = type === "cron" && first ? unwrap(first) : null;
544
+ const schedule = cron ? ts.isStringLiteralLike(cron) ? cron.text : cron.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
266
545
  const option = type === "initialize" || type === "destroy" ? first : second;
267
546
  return [
268
547
  {
@@ -271,7 +550,7 @@ var createFactReader = (ts, ast) => {
271
550
  extent: extentOf(source, property),
272
551
  type,
273
552
  schedule,
274
- modes: modesOf(option)
553
+ modes: modesOf(option, type)
275
554
  }
276
555
  ];
277
556
  });
@@ -288,7 +567,20 @@ var createFactReader = (ts, ast) => {
288
567
  return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
289
568
  });
290
569
  };
291
- const endpointKeysOf = (source, builder) => {
570
+ const adaptorBindingsIn = (source) => {
571
+ const bindings = [];
572
+ const visit = (node) => {
573
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "applyAdaptor") {
574
+ const [role, adaptor] = node.arguments.map(guardNameOf);
575
+ if (role && adaptor)
576
+ bindings.push({ role, adaptor, span: spanOf(source, node.expression.name) });
577
+ }
578
+ ts.forEachChild(node, visit);
579
+ };
580
+ visit(source);
581
+ return bindings;
582
+ };
583
+ const endpointKeysOf = (source, builder, made, model) => {
292
584
  const verbOf = verbReaderOf(builder?.parameters[0]);
293
585
  return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
294
586
  if (!ts.isPropertyAssignment(property))
@@ -303,7 +595,7 @@ var createFactReader = (ts, ast) => {
303
595
  span: spanOf(source, property.name),
304
596
  extent: extentOf(source, property),
305
597
  verb: root ? verbOf(root.expression) : null,
306
- guards: root ? guardsIn(optionObjectOf(root)) : [],
598
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
307
599
  argCount: methods.filter((method) => argMethods.has(method)).length
308
600
  }
309
601
  ];
@@ -370,22 +662,6 @@ var createFactReader = (ts, ast) => {
370
662
  })) : []
371
663
  ];
372
664
  };
373
- const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
374
- const guardClassIn = (node, guardNames) => {
375
- if ((ts.isClassExpression(node) || ts.isClassDeclaration(node)) && implementsGuard(node, guardNames))
376
- return node;
377
- return ts.forEachChild(node, (child) => guardClassIn(child, guardNames)) ?? null;
378
- };
379
- const callsIn = (node, names) => {
380
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && names.has(node.expression.text))
381
- return node.expression.text;
382
- return ts.forEachChild(node, (child) => callsIn(child, names)) ?? null;
383
- };
384
- const scopeOf = (node) => {
385
- const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
386
- const value = scope && unwrap(scope);
387
- return value && ts.isStringLiteralLike(value) ? value.text : null;
388
- };
389
665
  const docOf = (source, node) => {
390
666
  const { text } = source;
391
667
  const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
@@ -401,108 +677,20 @@ var createFactReader = (ts, ast) => {
401
677
  `)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
402
678
  return lines.length ? lines.join(" ") : null;
403
679
  };
404
- const functionsOf = (source) => {
405
- const functions = new Map;
406
- for (const statement of source.statements) {
407
- if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
408
- functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
409
- if (!ts.isVariableStatement(statement))
410
- continue;
411
- for (const declaration of statement.declarationList.declarations) {
412
- const initializer = declaration.initializer && unwrap(declaration.initializer);
413
- if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
414
- functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
415
- }
416
- }
417
- return functions;
418
- };
419
- const guardFactoriesOf = (source, guardNames) => {
420
- const functions = functionsOf(source);
421
- const factories = new Map;
422
- const factoryOf = (name, scope, via) => {
423
- const found = functions.get(name);
424
- if (!found)
425
- return;
426
- factories.set(name, {
427
- name,
428
- span: spanOf(source, found.name),
429
- extent: extentOf(source, found.statement),
430
- scope,
431
- doc: docOf(source, found.statement),
432
- via
433
- });
434
- };
435
- for (const [name, { body }] of functions) {
436
- const guard = guardClassIn(body, guardNames);
437
- if (guard)
438
- factoryOf(name, scopeOf(guard), null);
439
- }
440
- for (let isGrowing = true;isGrowing; ) {
441
- isGrowing = false;
442
- for (const [name, { body }] of functions) {
443
- const called = factories.has(name) ? null : callsIn(body, new Set(factories.keys()));
444
- if (!called)
445
- continue;
446
- factoryOf(name, factories.get(called)?.scope ?? null, called);
447
- isGrowing = true;
448
- }
449
- }
450
- return factories;
451
- };
452
- const guardsOf = (source, imports) => {
453
- const guardNames = new Set([
454
- "Guard",
455
- ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
456
- ]);
457
- const imported = new Set(imports.map(({ name }) => name));
458
- const factories = guardFactoriesOf(source, guardNames);
459
- const guards = [];
460
- const calls = [];
461
- for (const statement of source.statements) {
462
- if (ts.isClassDeclaration(statement) && statement.name && implementsGuard(statement, guardNames))
463
- guards.push({
464
- name: statement.name.text,
465
- span: spanOf(source, statement.name),
466
- extent: extentOf(source, statement),
467
- scope: scopeOf(statement),
468
- doc: docOf(source, statement),
469
- factory: null
470
- });
471
- if (!ts.isVariableStatement(statement))
472
- continue;
473
- for (const declaration of statement.declarationList.declarations) {
474
- const initializer = declaration.initializer && unwrap(declaration.initializer);
475
- if (!initializer || !ts.isIdentifier(declaration.name))
476
- continue;
477
- const made = {
478
- name: declaration.name.text,
479
- span: spanOf(source, declaration.name),
480
- extent: extentOf(source, statement),
481
- doc: docOf(source, statement)
482
- };
483
- if (ts.isClassExpression(initializer) && implementsGuard(initializer, guardNames))
484
- guards.push({ ...made, scope: scopeOf(initializer), factory: null });
485
- if (!ts.isCallExpression(initializer) || !ts.isIdentifier(initializer.expression))
486
- continue;
487
- const callee = initializer.expression.text;
488
- const factory = factories.get(callee);
489
- if (factory)
490
- guards.push({ ...made, scope: factory.scope, factory: callee });
491
- else if (imported.has(callee))
492
- calls.push({ ...made, callee });
493
- }
494
- }
495
- return { guards, guardFactories: [...factories.values()], guardCalls: calls };
496
- };
680
+ const { guardsIn, guardsOf } = createGuardFactReader(ts, ast, { spanOf, extentOf, unwrap, docOf });
497
681
  const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
498
682
  const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
499
683
  const read = (source, owner) => {
500
684
  const imports = importsIn(source);
685
+ const madeInPlace = [];
501
686
  const facts = {
502
687
  fileName: source.fileName,
503
688
  owner,
504
689
  imports,
505
- ...guardsOf(source, imports),
690
+ guards: [],
691
+ guardFactories: [],
692
+ guardCalls: [],
693
+ guardLabels: {},
506
694
  constants: [],
507
695
  enums: [],
508
696
  filters: [],
@@ -512,6 +700,7 @@ var createFactReader = (ts, ast) => {
512
700
  endpoints: [],
513
701
  internals: [],
514
702
  adaptors: [],
703
+ adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
515
704
  stores: []
516
705
  };
517
706
  for (const statement of source.statements) {
@@ -536,16 +725,19 @@ var createFactReader = (ts, ast) => {
536
725
  ...name,
537
726
  model: moduleRefOf(call.arguments[0], "db"),
538
727
  methods: methodsOf(source, statement),
539
- plugs: plugsOf(source, builder)
728
+ plugs: plugsOf(source, builder),
729
+ fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
540
730
  });
541
- else if (callee === "slice")
731
+ else if (callee === "slice") {
732
+ const model = moduleRefOf(call.arguments[0], "srv");
733
+ const member = model ?? name.name;
542
734
  facts.slices.push({
543
735
  ...name,
544
- model: moduleRefOf(call.arguments[0], "srv"),
545
- guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
546
- keys: sliceKeysOf(source, builder)
736
+ model,
737
+ guards: guardsIn(source, call.arguments.find(ts.isObjectLiteralExpression), madeInPlace, member),
738
+ keys: sliceKeysOf(source, builder, madeInPlace, member)
547
739
  });
548
- else if (callee === "internal")
740
+ } else if (callee === "internal")
549
741
  facts.internals.push({
550
742
  ...name,
551
743
  model: moduleRefOf(call.arguments[0], "srv"),
@@ -560,13 +752,14 @@ var createFactReader = (ts, ast) => {
560
752
  plugs: plugsOf(source, builder),
561
753
  doc: docOf(source, statement)
562
754
  });
563
- } else if (callee === "endpoint")
755
+ } else if (callee === "endpoint") {
756
+ const model = moduleRefOf(call.arguments[0], "srv");
564
757
  facts.endpoints.push({
565
758
  ...name,
566
- model: moduleRefOf(call.arguments[0], "srv"),
567
- keys: endpointKeysOf(source, builder)
759
+ model,
760
+ keys: endpointKeysOf(source, builder, madeInPlace, model ?? name.name)
568
761
  });
569
- else if (callee === "store")
762
+ } else if (callee === "store")
570
763
  facts.stores.push({
571
764
  ...name,
572
765
  model: moduleRefOf(call.arguments[0], "sig"),
@@ -574,10 +767,647 @@ var createFactReader = (ts, ast) => {
574
767
  actions: methodsOf(source, statement)
575
768
  });
576
769
  }
577
- return facts;
578
- };
579
- return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
580
- };
770
+ return { ...facts, ...guardsOf(source, imports, madeInPlace) };
771
+ };
772
+ return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
773
+ };
774
+
775
+ // pkgs/@akanjs/language/guardResolver.ts
776
+ var frameworkOwner = "akanjs";
777
+ var frameworkPattern = /^akanjs(?:\/|$)/;
778
+ var aliasPattern = /^@(apps|libs)\/([^/]+)/;
779
+ var maxMapDepth = 4;
780
+ var identifierPattern = /^[\p{ID_Start}$_][\p{ID_Continue}$\u200c\u200d]*$/u;
781
+ var starExportPattern = /export\s*\*\s*from\s*["']([^"']+)["']/g;
782
+ var exportingPatternOf = (name) => new RegExp(`export\\s*\\{(?:[^}]*?[\\s,])?${escapeRegExp(name)}(?![\\w$])`);
783
+ var declaringPatternOf = (name, isAnyBinding) => {
784
+ const escaped = escapeRegExp(name);
785
+ const braced = "\\{(?:[^}]*?[\\s,])?";
786
+ const leads = [
787
+ "function\\s*\\*?\\s*",
788
+ "(?:const|let|var)\\s+",
789
+ `export\\s*${braced}`,
790
+ ...isAnyBinding ? ["class\\s+", `(?:const|let|var)\\s*${braced}`] : []
791
+ ];
792
+ return new RegExp(`(?:${leads.join("|")})${escaped}(?![\\w$])|,\\s*${escaped}\\s*(?::[^=;,]+)?=(?![=>])`);
793
+ };
794
+
795
+ class GuardResolver {
796
+ #source;
797
+ #routes;
798
+ #guards = new Map;
799
+ #madeInPlace = new Map;
800
+ #mapEntries = new Map;
801
+ #reexports = new Map;
802
+ #declarations = new Map;
803
+ #starExports = new Map;
804
+ #moduleExports = new Map;
805
+ #ownerFiles = null;
806
+ #modules = null;
807
+ constructor(source, routes) {
808
+ this.#source = source;
809
+ this.#routes = routes;
810
+ }
811
+ find(local, site) {
812
+ return this.#findGuard(local, site, 0);
813
+ }
814
+ madeInPlace(fileName, name) {
815
+ return this.#madeInPlace.get(`${fileName}
816
+ ${name}`);
817
+ }
818
+ declare(facts) {
819
+ const factories = new Map;
820
+ const declaredFactories = [];
821
+ for (const file of facts)
822
+ for (const fact of file.guardFactories) {
823
+ const factory = {
824
+ fact,
825
+ owner: file.owner,
826
+ site: file,
827
+ made: [],
828
+ isReal: fact.direct,
829
+ scope: fact.scope,
830
+ layers: fact.layers,
831
+ targets: []
832
+ };
833
+ factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
834
+ declaredFactories.push(factory);
835
+ }
836
+ for (const factory of declaredFactories)
837
+ factory.targets = factory.fact.via.flatMap(({ callee, layers }) => {
838
+ const target = this.#factoryAt(factories, callee, factory.site);
839
+ return target ? [{ target, layers }] : [];
840
+ });
841
+ for (let isGrowing = true;isGrowing; ) {
842
+ isGrowing = false;
843
+ for (const factory of declaredFactories) {
844
+ const via = factory.targets.find(({ target, layers }) => target.isReal && target.layers >= layers);
845
+ if (factory.isReal || !via)
846
+ continue;
847
+ factory.isReal = true;
848
+ factory.layers = factory.fact.layers + via.target.layers - via.layers;
849
+ factory.scope ??= via.target.scope;
850
+ isGrowing = true;
851
+ }
852
+ }
853
+ const partsOf = (factory, seen = new Set) => {
854
+ if (seen.has(factory))
855
+ return [];
856
+ seen.add(factory);
857
+ return [
858
+ { fileName: factory.site.fileName, extent: factory.fact.extent },
859
+ ...factory.targets.filter(({ target }) => target.isReal).flatMap(({ target }) => partsOf(target, seen))
860
+ ];
861
+ };
862
+ const declare = (file, made, scope, parts) => {
863
+ const { name, span, extent, doc } = made;
864
+ const label = file.guardLabels[name];
865
+ const own = { fileName: file.fileName, extent };
866
+ const guard = { name, owner: file.owner, span, scope, doc, parts: [own, ...parts], file: null, label };
867
+ if (name.includes("@"))
868
+ this.#madeInPlace.set(`${file.fileName}
869
+ ${name}`, guard);
870
+ else
871
+ this.#guards.set(name, [...this.#guards.get(name) ?? [], guard]);
872
+ return guard;
873
+ };
874
+ const extending = [];
875
+ for (const file of facts) {
876
+ for (const made of file.guards) {
877
+ const factory = made.factory ? factories.get(made.factory)?.find((other) => other.site.fileName === file.fileName) : undefined;
878
+ if (!made.factory)
879
+ declare(file, made, made.scope, []);
880
+ else if (factory?.isReal && factory.layers === made.layers)
881
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
882
+ }
883
+ for (const made of file.guardCalls) {
884
+ if (made.kind === "extends") {
885
+ extending.push([file, made]);
886
+ continue;
887
+ }
888
+ const factory = this.#factoryAt(factories, made.callee, file);
889
+ if (factory?.isReal && factory.layers === made.layers)
890
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
891
+ }
892
+ }
893
+ for (let isGrowing = true;isGrowing; ) {
894
+ isGrowing = false;
895
+ for (const [idx, entry] of extending.entries()) {
896
+ const [file, made] = entry;
897
+ const found = this.#findGuard(made.callee, file, 0, true);
898
+ if (!("guard" in found))
899
+ continue;
900
+ declare(file, made, made.scope ?? found.guard.scope, found.guard.parts);
901
+ extending.splice(idx, 1);
902
+ isGrowing = true;
903
+ break;
904
+ }
905
+ }
906
+ for (const { fact, made, isReal } of declaredFactories)
907
+ if (isReal && made.every((guard) => !guard.doc))
908
+ for (const guard of made)
909
+ guard.doc = fact.doc;
910
+ for (const list of this.#guards.values())
911
+ for (const guard of list)
912
+ if (list.some((other) => other !== guard && other.owner === guard.owner))
913
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
914
+ for (const guard of this.#madeInPlace.values())
915
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
916
+ return [...[...this.#guards.values()].flat(), ...this.#madeInPlace.values()];
917
+ }
918
+ #factoryAt(factories, callee, site, depth = 0) {
919
+ const [head = callee, member] = callee.split(".");
920
+ const imported = site.imports.find((item) => item.name === head);
921
+ if (!imported)
922
+ return member ? undefined : factories.get(callee)?.find((other) => other.site.fileName === site.fileName);
923
+ if (member && imported.imported !== "*")
924
+ return;
925
+ const from = this.ownerOfSpecifier(site.fileName, imported.from);
926
+ if (from === undefined || from === frameworkOwner)
927
+ return;
928
+ const name = member ?? (imported.imported === "default" || imported.imported === "*" ? head : imported.imported);
929
+ const candidates = factories.get(name) ?? [];
930
+ const near = this.pathOfSpecifier(site.fileName, imported.from);
931
+ const own = this.#nearest(candidates.filter((other) => other.owner === from), ({ site }) => site.fileName, near);
932
+ if (own)
933
+ return own;
934
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
935
+ if (reexport)
936
+ return this.#factoryAt(factories, reexport.local, reexport.site, depth + 1);
937
+ return from && near ? this.#throughStar(candidates, from, name, near) : undefined;
938
+ }
939
+ #findGuard(local, site, depth, isBase = false) {
940
+ const segments = local.split(".");
941
+ const head = segments[0] ?? local;
942
+ const imported = site.imports.find((item) => item.name === head);
943
+ const from = imported ? this.ownerOfSpecifier(site.fileName, imported.from) : undefined;
944
+ if (imported && from === undefined)
945
+ return { external: true };
946
+ const near = imported ? this.pathOfSpecifier(site.fileName, imported.from) : this.#source.relative(site.fileName).replace(/\.tsx?$/, "");
947
+ const named = imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : null;
948
+ const path = imported?.imported === "*" ? segments.slice(1) : [named ?? head, ...segments.slice(1)];
949
+ const holder = imported ? from ?? null : site.owner;
950
+ const [objectName, member] = path;
951
+ if (path.length === 2 && objectName && member && holder !== frameworkOwner && depth < maxMapDepth) {
952
+ const made = this.#nearest((this.#guards.get(`${objectName}.${member}`) ?? []).filter((guard) => guard.owner === holder), ({ span }) => span.fileName, near);
953
+ if (made)
954
+ return { guard: made };
955
+ for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
956
+ const found = this.#findGuard(entry.name, entry.site, depth + 1, isBase);
957
+ if ("guard" in found)
958
+ return found;
959
+ }
960
+ }
961
+ const name = path[path.length - 1] ?? head;
962
+ if (from === frameworkOwner)
963
+ return { name, from };
964
+ const declared = this.#guards.get(name) ?? [];
965
+ const own = this.#nearest(declared.filter((other) => other.owner === (imported ? from : site.owner)), ({ span }) => span.fileName, near);
966
+ if (own)
967
+ return { guard: own };
968
+ if (!imported) {
969
+ const visible = isBase ? undefined : declared.find((other) => this.visible(other.owner, site.owner));
970
+ return visible ? { guard: visible } : { name, from };
971
+ }
972
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
973
+ if (reexport)
974
+ return this.#findGuard(reexport.local, reexport.site, depth + 1, isBase);
975
+ const reached = from && near ? this.#throughStar(declared, from, name, near) : undefined;
976
+ return reached ? { guard: reached } : { name, from };
977
+ }
978
+ reexportOf(owner, name, near) {
979
+ const key = `${owner}
980
+ ${name}
981
+ ${near ?? ""}`;
982
+ const cached = this.#reexports.get(key);
983
+ if (cached !== undefined)
984
+ return cached;
985
+ let found = null;
986
+ const exporting = exportingPatternOf(name);
987
+ for (const fileName of this.#filesOf(owner, name, near)) {
988
+ const text = this.#source.readText(fileName);
989
+ if (text === undefined || !exporting.test(text))
990
+ continue;
991
+ const reexport = this.#reexportIn(this.#source.parse(fileName, text), name);
992
+ if (!reexport)
993
+ continue;
994
+ const site = { fileName, owner, imports: [] };
995
+ found = reexport.from ? {
996
+ local: reexport.name,
997
+ site: { ...site, imports: [{ ...reexport, imported: reexport.name, from: reexport.from }] }
998
+ } : { local: reexport.name, site };
999
+ break;
1000
+ }
1001
+ this.#reexports.set(key, found);
1002
+ return found;
1003
+ }
1004
+ #reexportIn(source, name) {
1005
+ const { ts, reader } = this.#source;
1006
+ for (const statement of source.statements) {
1007
+ if (!ts.isExportDeclaration(statement) || statement.isTypeOnly)
1008
+ continue;
1009
+ const { exportClause, moduleSpecifier } = statement;
1010
+ if (!exportClause || !ts.isNamedExports(exportClause))
1011
+ continue;
1012
+ const element = exportClause.elements.find((item) => !item.isTypeOnly && item.name.text === name);
1013
+ if (!element)
1014
+ continue;
1015
+ const original = (element.propertyName ?? element.name).text;
1016
+ if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier))
1017
+ return { name: original, from: moduleSpecifier.text };
1018
+ const imported = reader.importsIn(source).find((item) => item.name === original);
1019
+ if (!imported)
1020
+ return { name: original, from: null };
1021
+ return imported.imported === "default" || imported.imported === "*" ? null : { name: imported.imported, from: imported.from };
1022
+ }
1023
+ return null;
1024
+ }
1025
+ #filesOf(owner, name, near) {
1026
+ const isNear = (fileName) => {
1027
+ const relative = this.#source.relative(fileName);
1028
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1029
+ };
1030
+ if (!identifierPattern.test(name))
1031
+ return [];
1032
+ const candidates = this.#filesIn(owner).filter(([, words]) => words.includes(`
1033
+ ${name}
1034
+ `)).map(([fileName]) => fileName);
1035
+ return [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))];
1036
+ }
1037
+ #mapEntriesOf(objectName, member, fileName, owner) {
1038
+ const key = `${objectName}
1039
+ ${member}
1040
+ ${fileName ?? ""}
1041
+ ${owner ?? ""}`;
1042
+ const cached = this.#mapEntries.get(key);
1043
+ if (cached)
1044
+ return cached;
1045
+ const { ts, reader } = this.#source;
1046
+ const entries = [];
1047
+ const fileNames = [
1048
+ ...fileName ? [fileName] : [],
1049
+ ...(owner === null ? [] : this.#filesIn(owner)).filter(([other, words]) => other !== fileName && words.includes(`
1050
+ ${objectName}
1051
+ `) && words.includes(`
1052
+ ${member}
1053
+ `)).map(([other]) => other)
1054
+ ];
1055
+ for (const holder of fileNames) {
1056
+ const text = this.#source.readText(holder);
1057
+ if (text === undefined)
1058
+ continue;
1059
+ const source = this.#source.parse(holder, text);
1060
+ const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
1061
+ for (const statement of source.statements) {
1062
+ if (!ts.isVariableStatement(statement))
1063
+ continue;
1064
+ const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
1065
+ const object = map && reader.unwrap(map);
1066
+ if (!object || !ts.isObjectLiteralExpression(object))
1067
+ continue;
1068
+ for (const property of object.properties) {
1069
+ if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
1070
+ entries.push({ name: member, site });
1071
+ if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
1072
+ continue;
1073
+ const value = reader.unwrap(property.initializer);
1074
+ if (ts.isIdentifier(value))
1075
+ entries.push({ name: value.text, site });
1076
+ else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
1077
+ entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
1078
+ }
1079
+ }
1080
+ }
1081
+ this.#mapEntries.set(key, entries);
1082
+ return entries;
1083
+ }
1084
+ declarationOf(owner, name, near, depth = 0) {
1085
+ const key = `${owner}
1086
+ ${name}
1087
+ ${near ?? ""}`;
1088
+ const cached = this.#declarations.get(key);
1089
+ if (cached !== undefined)
1090
+ return cached;
1091
+ this.#declarations.set(key, null);
1092
+ const declaring = declaringPatternOf(name, false);
1093
+ let found = null;
1094
+ for (const fileName of this.#filesOf(owner, name, near)) {
1095
+ const text = this.#source.readText(fileName);
1096
+ if (text === undefined || !declaring.test(text))
1097
+ continue;
1098
+ const source = this.#source.parse(fileName, text);
1099
+ const declared = this.#routes.declarationIn(source, name);
1100
+ if (declared) {
1101
+ found = { source, ...declared, name };
1102
+ break;
1103
+ }
1104
+ const reexport = this.#reexportIn(source, name);
1105
+ if (!reexport)
1106
+ continue;
1107
+ if (!reexport.from) {
1108
+ const local = this.#routes.declarationIn(source, reexport.name);
1109
+ found = local ? { source, ...local, name: reexport.name } : null;
1110
+ break;
1111
+ }
1112
+ const next = this.ownerOfSpecifier(fileName, reexport.from);
1113
+ if (next && next !== frameworkOwner && depth < maxMapDepth)
1114
+ found = this.declarationOf(next, reexport.name, this.pathOfSpecifier(fileName, reexport.from), depth + 1);
1115
+ break;
1116
+ }
1117
+ this.#declarations.set(key, found);
1118
+ return found;
1119
+ }
1120
+ #throughStar(declared, owner, name, near) {
1121
+ if (!declared.length)
1122
+ return;
1123
+ const { owners, isOpaque } = this.starExportsOf(owner, near);
1124
+ const reached = declared.filter((other) => other.owner !== null && owners.includes(other.owner));
1125
+ return reached.length === 1 && !isOpaque && !this.declares(owner, name) ? reached[0] : undefined;
1126
+ }
1127
+ starExportsOf(owner, near) {
1128
+ const key = `${owner}
1129
+ ${near}`;
1130
+ const cached = this.#starExports.get(key);
1131
+ if (cached)
1132
+ return cached;
1133
+ const found = { owners: [], packages: [], paths: [near], isOpaque: false };
1134
+ this.#starExports.set(key, found);
1135
+ for (let idx = 0;idx < found.paths.length; idx++)
1136
+ for (const fileName of this.#modulesAt(found.paths[idx] ?? ""))
1137
+ for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? []) {
1138
+ const target = this.ownerOfSpecifier(fileName, specifier);
1139
+ const path = this.pathOfSpecifier(fileName, specifier);
1140
+ if (path === null && !found.packages.includes(specifier))
1141
+ found.packages.push(specifier);
1142
+ if (target === undefined || target === frameworkOwner || path === null)
1143
+ found.isOpaque = true;
1144
+ else if (!found.paths.includes(path))
1145
+ found.paths.push(path);
1146
+ if (target && target !== frameworkOwner && target !== owner && !found.owners.includes(target))
1147
+ found.owners.push(target);
1148
+ }
1149
+ return found;
1150
+ }
1151
+ exportsAt(path, name) {
1152
+ const key = `${path}
1153
+ ${name}`;
1154
+ const cached = this.#moduleExports.get(key);
1155
+ if (cached)
1156
+ return cached;
1157
+ const found = { named: null, stars: [] };
1158
+ const exporting = exportingPatternOf(name);
1159
+ for (const fileName of this.#modulesAt(path)) {
1160
+ const text = this.#source.readText(fileName);
1161
+ if (text === undefined)
1162
+ continue;
1163
+ const named = identifierPattern.test(name) && exporting.test(text) ? this.#reexportIn(this.#source.parse(fileName, text), name) : null;
1164
+ if (named) {
1165
+ found.named = { ...named, fileName };
1166
+ found.stars = [];
1167
+ break;
1168
+ }
1169
+ for (const [, specifier = ""] of text.matchAll(starExportPattern)) {
1170
+ const target = this.pathOfSpecifier(fileName, specifier);
1171
+ if (target !== null && !found.stars.includes(target))
1172
+ found.stars.push(target);
1173
+ }
1174
+ }
1175
+ this.#moduleExports.set(key, found);
1176
+ return found;
1177
+ }
1178
+ #modulesAt(path) {
1179
+ if (!this.#modules) {
1180
+ this.#modules = new Map;
1181
+ for (const [fileName] of this.#source.files) {
1182
+ const module = this.#source.relative(fileName).replace(/\.tsx?$/, "");
1183
+ for (const key of module.endsWith("/index") ? [module, module.slice(0, -6)] : [module])
1184
+ this.#modules.set(key, [...this.#modules.get(key) ?? [], fileName]);
1185
+ }
1186
+ }
1187
+ return this.#modules.get(path) ?? [];
1188
+ }
1189
+ hasModule(path) {
1190
+ return this.#modulesAt(path).length > 0;
1191
+ }
1192
+ packagesUnder(owner, folder) {
1193
+ const packages = new Set;
1194
+ for (const [fileName, words] of this.#filesIn(owner)) {
1195
+ if (!this.#source.relative(fileName).startsWith(`${folder}/`) || !words.includes(`
1196
+ export
1197
+ `))
1198
+ continue;
1199
+ for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? [])
1200
+ if (this.pathOfSpecifier(fileName, specifier) === null)
1201
+ packages.add(specifier);
1202
+ }
1203
+ return [...packages];
1204
+ }
1205
+ declares(owner, name) {
1206
+ const declaring = declaringPatternOf(name, true);
1207
+ return this.#filesOf(owner, name, null).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
1208
+ }
1209
+ declaresAt(path, name) {
1210
+ const declaring = declaringPatternOf(name, true);
1211
+ return this.#modulesAt(path).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
1212
+ }
1213
+ #filesIn(owner) {
1214
+ if (!this.#ownerFiles) {
1215
+ this.#ownerFiles = new Map;
1216
+ for (const entry of this.#source.files) {
1217
+ const fileOwner = testFilePattern.test(entry[0]) ? null : this.#source.ownerOf(entry[0]);
1218
+ const files = fileOwner === null ? null : this.#ownerFiles.get(fileOwner);
1219
+ if (files)
1220
+ files.push(entry);
1221
+ else if (fileOwner !== null)
1222
+ this.#ownerFiles.set(fileOwner, [entry]);
1223
+ }
1224
+ }
1225
+ return this.#ownerFiles.get(owner) ?? [];
1226
+ }
1227
+ #nearest(items, fileOf, near) {
1228
+ const isNear = (fileName) => {
1229
+ const relative = this.#source.relative(fileName);
1230
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1231
+ };
1232
+ return items.find((item) => isNear(fileOf(item))) ?? items[0];
1233
+ }
1234
+ inOwner(fileName, owner) {
1235
+ const relative = this.#source.relative(fileName);
1236
+ return owner && relative.startsWith(`${owner}/`) ? relative.slice(owner.length + 1) : relative;
1237
+ }
1238
+ ownerOfSpecifier(fileName, specifier) {
1239
+ if (frameworkPattern.test(specifier))
1240
+ return frameworkOwner;
1241
+ const path = this.pathOfSpecifier(fileName, specifier);
1242
+ return path === null ? undefined : ownerOf(path) ?? undefined;
1243
+ }
1244
+ pathOfSpecifier(fileName, specifier) {
1245
+ const alias = specifier.match(aliasPattern);
1246
+ if (alias)
1247
+ return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1248
+ if (!specifier.startsWith("."))
1249
+ return null;
1250
+ const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1251
+ for (const segment of specifier.split("/"))
1252
+ if (segment === "..")
1253
+ segments.pop();
1254
+ else if (segment !== ".")
1255
+ segments.push(segment);
1256
+ return segments.join("/");
1257
+ }
1258
+ visible(target, from) {
1259
+ return !from || !target || target === from || target.startsWith("libs/");
1260
+ }
1261
+ }
1262
+
1263
+ // pkgs/@akanjs/language/adaptorResolver.ts
1264
+ var maxModules = 64;
1265
+
1266
+ class AdaptorResolver {
1267
+ #source;
1268
+ #resolver;
1269
+ #sink;
1270
+ #adaptors = new Map;
1271
+ #plugs = new Map;
1272
+ #serviceFields = new Set;
1273
+ constructor(source, resolver, sink) {
1274
+ this.#source = source;
1275
+ this.#resolver = resolver;
1276
+ this.#sink = sink;
1277
+ }
1278
+ declare(facts) {
1279
+ const counts = new Map;
1280
+ for (const file of facts)
1281
+ for (const fact of file.adaptors) {
1282
+ const key = this.#sink.key("adaptor", file.owner, fact.name);
1283
+ counts.set(key, (counts.get(key) ?? 0) + 1);
1284
+ }
1285
+ const declared = facts.flatMap((file) => file.adaptors.map((fact) => {
1286
+ const key = this.#sink.key("adaptor", file.owner, fact.name);
1287
+ const id = (counts.get(key) ?? 0) > 1 ? this.#sink.key("adaptor", file.owner, `${this.#resolver.inOwner(file.fileName, file.owner)}#${fact.name}`) : key;
1288
+ const adaptor = { fact, owner: file.owner, fileName: file.fileName, id };
1289
+ this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], adaptor]);
1290
+ this.#nodeOf(adaptor);
1291
+ return { adaptor, file };
1292
+ }));
1293
+ for (const { adaptor, file } of declared)
1294
+ for (const plug of adaptor.fact.plugs)
1295
+ this.#sink.addEdge(adaptor.id, this.#idOf(plug.target, file), "plugs", plug.name, this.#sink.location(plug.span));
1296
+ for (const file of facts)
1297
+ for (const binding of file.adaptorBindings)
1298
+ this.#sink.addEdge(this.#idOf(binding.role, file), this.#idOf(binding.adaptor, file), "binds", file.owner, this.#sink.location(binding.span));
1299
+ }
1300
+ addService(refName, service, file) {
1301
+ for (const plug of service.plugs) {
1302
+ const key = `${refName}
1303
+ ${plug.name}`;
1304
+ this.#plugs.set(key, [
1305
+ ...this.#plugs.get(key) ?? [],
1306
+ { owner: file.owner, target: this.#idOf(plug.target, file) }
1307
+ ]);
1308
+ }
1309
+ for (const field of service.fields)
1310
+ this.#serviceFields.add(`${file.owner ?? ""}
1311
+ ${refName}
1312
+ ${field}`);
1313
+ }
1314
+ pluggedOf(owner, module, field) {
1315
+ const plugs = this.#plugs.get(`${module}
1316
+ ${field}`) ?? [];
1317
+ const own = plugs.filter((plug) => plug.owner === owner);
1318
+ if (!own.length && this.#serviceFields.has(`${owner ?? ""}
1319
+ ${module}
1320
+ ${field}`))
1321
+ return [];
1322
+ const inherited = own.length ? own : plugs.filter((plug) => this.#resolver.visible(plug.owner, owner));
1323
+ return [...new Set(inherited.map(({ target }) => target))];
1324
+ }
1325
+ #idOf(local, file) {
1326
+ const [head, member] = local.split(".");
1327
+ const imported = file.imports.find((item) => item.name === head);
1328
+ const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1329
+ const path = imported ? this.#resolver.pathOfSpecifier(file.fileName, imported.from) : null;
1330
+ if (imported && path === null && !frameworkPattern.test(imported.from))
1331
+ return this.#packageAdaptorOf(name, imported.from);
1332
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
1333
+ const declared = this.#adaptors.get(name) ?? [];
1334
+ const holder = from === undefined ? file.owner : from;
1335
+ const own = this.#adaptorIn(name, declared, holder, path, file.fileName);
1336
+ if (own && "from" in own)
1337
+ return this.#packageAdaptorOf(own.name, own.from);
1338
+ const found = own === undefined && from !== frameworkOwner ? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, holder)) : own;
1339
+ if (found)
1340
+ return found.id;
1341
+ const reexported = own === undefined && from && from !== frameworkOwner ? this.#packageShimOf(from, name, path) : null;
1342
+ if (reexported)
1343
+ return this.#packageAdaptorOf(reexported.name, reexported.from);
1344
+ const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
1345
+ const detail = from === frameworkOwner ? [frameworkOwner] : [];
1346
+ return this.#sink.addNode(this.#sink.key("adaptor", owner, name), "adaptor", owner, name, null, detail).id;
1347
+ }
1348
+ #adaptorIn(name, declared, owner, path, fileName) {
1349
+ const own = declared.filter((adaptor) => adaptor.owner === owner);
1350
+ const [first] = own;
1351
+ if (path === null)
1352
+ return own.find((adaptor) => adaptor.fileName === fileName) ?? first;
1353
+ if (own.length === 1)
1354
+ return first;
1355
+ if (!this.#resolver.hasModule(path))
1356
+ return own.find((adaptor) => this.#source.relative(adaptor.fileName).startsWith(`${path}/`)) ?? first;
1357
+ const exported = this.#exportedAt(name, path, new Set).filter((item) => first || ("fact" in item));
1358
+ const [only] = exported;
1359
+ if (only)
1360
+ return exported.length === 1 ? only : null;
1361
+ return first ? null : undefined;
1362
+ }
1363
+ #exportedAt(name, path, seen) {
1364
+ const key = `${path}
1365
+ ${name}`;
1366
+ if (seen.has(key) || seen.size >= maxModules)
1367
+ return [];
1368
+ seen.add(key);
1369
+ const here = (this.#adaptors.get(name) ?? []).filter((adaptor) => {
1370
+ const module = this.#source.relative(adaptor.fileName).replace(/\.tsx?$/, "");
1371
+ return module === path || module === `${path}/index`;
1372
+ });
1373
+ if (here.length)
1374
+ return here;
1375
+ const { named, stars } = this.#resolver.exportsAt(path, name);
1376
+ if (named?.from && this.#resolver.pathOfSpecifier(named.fileName, named.from) === null)
1377
+ return [{ name: named.name === "default" ? name : named.name, from: named.from }];
1378
+ if (named) {
1379
+ const target = named.from === null ? path : this.#resolver.pathOfSpecifier(named.fileName, named.from);
1380
+ return target === null ? [] : this.#exportedAt(named.name, target, seen);
1381
+ }
1382
+ return [...new Set(stars.flatMap((star) => this.#exportedAt(name, star, seen)))];
1383
+ }
1384
+ #packageAdaptorOf(name, specifier) {
1385
+ if (frameworkPattern.test(specifier))
1386
+ return this.#sink.addNode(this.#sink.key("adaptor", null, name), "adaptor", null, name, null, [frameworkOwner]).id;
1387
+ const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
1388
+ return this.#sink.addNode(this.#sink.key("adaptor", pkg, name), "adaptor", null, name, null, [pkg]).id;
1389
+ }
1390
+ #packageShimOf(owner, name, near) {
1391
+ const reexport = this.#resolver.reexportOf(owner, name, near);
1392
+ const [shim] = reexport?.site.imports ?? [];
1393
+ if (reexport && shim && this.#resolver.pathOfSpecifier(reexport.site.fileName, shim.from) === null)
1394
+ return { name: reexport.local === "default" ? name : reexport.local, from: shim.from };
1395
+ if (reexport || !near)
1396
+ return null;
1397
+ const { packages, paths } = this.#resolver.hasModule(near) ? this.#resolver.starExportsOf(owner, near) : { packages: this.#resolver.packagesUnder(owner, near), paths: [] };
1398
+ const [only] = packages;
1399
+ const isDeclared = this.#resolver.declares(owner, name) || paths.some((path) => this.#resolver.declaresAt(path, name));
1400
+ return packages.length === 1 && only && !isDeclared ? { name, from: only } : null;
1401
+ }
1402
+ #nodeOf({ fact, owner, fileName, id }) {
1403
+ const detail = fact.refName ? [fact.refName] : [];
1404
+ const node = this.#sink.addNode(id, "adaptor", owner, fact.name, fact.span, detail);
1405
+ if (fact.doc && !node.doc)
1406
+ node.doc = fact.doc;
1407
+ this.#sink.digest(node, this.#source.readText(fileName), fact.extent);
1408
+ return node.id;
1409
+ }
1410
+ }
581
1411
 
582
1412
  // pkgs/akanjs/common/trimSlashes.ts
583
1413
  var trimTrailingSlashes = (value) => {
@@ -611,7 +1441,8 @@ var ROOT_LAYOUT_ROUTE_EXPORTS = new Set([
611
1441
  "wsConnect",
612
1442
  "layoutStyle",
613
1443
  "viewport",
614
- "McpAppRoot"
1444
+ "McpAppRoot",
1445
+ "LevelBoundary"
615
1446
  ]);
616
1447
  var RESERVED_ROUTE_CONFIG_EXPORTS = new Set([...ROOT_LAYOUT_ROUTE_EXPORTS].filter((name) => name !== "default" && !/^[A-Z]/.test(name)));
617
1448
  function parseRouteModuleKey(key) {
@@ -726,24 +1557,23 @@ rootLayout
726
1557
  }
727
1558
  declarationIn(source, name) {
728
1559
  const ts = this.#ts;
1560
+ const overloads = source.statements.filter((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === name);
1561
+ const [first] = overloads;
1562
+ const last = overloads.at(-1);
1563
+ if (first?.name && last)
1564
+ return { statement: first, declared: first.name, extent: { start: first.getStart(source), end: last.getEnd() } };
729
1565
  for (const statement of source.statements) {
730
- if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
731
- return { statement, declared: statement.name, initializer: null };
732
1566
  if (!ts.isVariableStatement(statement))
733
1567
  continue;
734
1568
  const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
735
1569
  if (declaration)
736
- return { statement, declared: declaration.name, initializer: declaration.initializer ?? null };
1570
+ return { statement, declared: declaration.name, extent: this.#reader.extentOf(source, statement) };
737
1571
  }
738
1572
  return null;
739
1573
  }
740
1574
  #guardOf(source, exported) {
741
1575
  const ts = this.#ts;
742
1576
  let current = ts.isExportAssignment(exported) ? this.#reader.unwrap(exported.expression) : null;
743
- if (current && ts.isIdentifier(current)) {
744
- const initializer = this.declarationIn(source, current.text)?.initializer;
745
- current = initializer ? this.#reader.unwrap(initializer) : null;
746
- }
747
1577
  while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
748
1578
  const { name, expression } = current.expression;
749
1579
  const [stage] = current.arguments;
@@ -755,9 +1585,20 @@ rootLayout
755
1585
  }
756
1586
  #callsIn(source, stage) {
757
1587
  const ts = this.#ts;
758
- const imported = new Set(this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from)).map(({ name }) => name));
1588
+ const imports = this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from));
1589
+ const imported = new Set(imports.filter(({ imported }) => imported !== "*").map(({ name }) => name));
1590
+ const namespaces = new Set(imports.filter(({ imported }) => imported === "*").map(({ name }) => name));
759
1591
  const calls = new Map;
760
- const callOf = (name) => {
1592
+ const callOf = (callee, shadowed) => {
1593
+ if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text) && !shadowed.has(callee.expression.text)) {
1594
+ const name = `${callee.expression.text}.${callee.name.text}`;
1595
+ if (!calls.has(name))
1596
+ calls.set(name, { name, local: null });
1597
+ return true;
1598
+ }
1599
+ if (!ts.isIdentifier(callee) || shadowed.has(callee.text))
1600
+ return false;
1601
+ const name = callee.text;
761
1602
  if (calls.has(name))
762
1603
  return true;
763
1604
  const declared = this.declarationIn(source, name);
@@ -767,23 +1608,71 @@ rootLayout
767
1608
  name,
768
1609
  local: declared && {
769
1610
  span: this.#reader.spanOf(source, declared.declared),
770
- extent: this.#reader.extentOf(source, declared.statement),
1611
+ extent: declared.extent,
771
1612
  doc: this.#reader.docOf(source, declared.statement)
772
1613
  }
773
1614
  });
774
1615
  return true;
775
1616
  };
776
- const visit = (node) => {
777
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && callOf(node.expression.text))
1617
+ const visit = (node, outer) => {
1618
+ const shadowed = this.#scopeOf(node, outer);
1619
+ if (ts.isCallExpression(node) && callOf(node.expression, shadowed))
778
1620
  return;
779
- ts.forEachChild(node, visit);
1621
+ ts.forEachChild(node, (child) => visit(child, shadowed));
780
1622
  };
781
1623
  if (ts.isIdentifier(stage))
782
- callOf(stage.text);
1624
+ callOf(stage, new Set);
783
1625
  else
784
- visit(stage);
1626
+ visit(stage, new Set);
785
1627
  return [...calls.values()];
786
1628
  }
1629
+ #scopeOf(node, outer) {
1630
+ const ts = this.#ts;
1631
+ const names = [];
1632
+ const bind = (name) => {
1633
+ if (ts.isIdentifier(name))
1634
+ names.push(name.text);
1635
+ else
1636
+ for (const element of name.elements)
1637
+ if (!ts.isOmittedExpression(element))
1638
+ bind(element.name);
1639
+ };
1640
+ const declare = (statement) => {
1641
+ if (ts.isVariableStatement(statement))
1642
+ for (const declaration of statement.declarationList.declarations)
1643
+ bind(declaration.name);
1644
+ else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
1645
+ names.push(statement.name.text);
1646
+ };
1647
+ const hoist = (child) => {
1648
+ if (ts.isFunctionLike(child) || ts.isClassLike(child))
1649
+ return;
1650
+ if (ts.isVariableDeclarationList(child) && !(child.flags & ts.NodeFlags.BlockScoped))
1651
+ for (const declaration of child.declarations)
1652
+ bind(declaration.name);
1653
+ ts.forEachChild(child, hoist);
1654
+ };
1655
+ if (ts.isFunctionLike(node)) {
1656
+ for (const parameter of node.parameters)
1657
+ bind(parameter.name);
1658
+ if (ts.isFunctionExpression(node) && node.name)
1659
+ names.push(node.name.text);
1660
+ if ("body" in node && node.body)
1661
+ ts.forEachChild(node.body, hoist);
1662
+ } else if (ts.isClassExpression(node) && node.name)
1663
+ names.push(node.name.text);
1664
+ else if (ts.isBlock(node))
1665
+ node.statements.forEach(declare);
1666
+ else if (ts.isCaseBlock(node))
1667
+ for (const clause of node.clauses)
1668
+ clause.statements.forEach(declare);
1669
+ else if (ts.isCatchClause(node) && node.variableDeclaration)
1670
+ bind(node.variableDeclaration.name);
1671
+ else if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer))
1672
+ for (const declaration of node.initializer.declarations)
1673
+ bind(declaration.name);
1674
+ return names.length ? new Set([...outer, ...names]) : outer;
1675
+ }
787
1676
  }
788
1677
 
789
1678
  // pkgs/@akanjs/language/akanGraph.ts
@@ -799,10 +1688,6 @@ var builtinGuards = {
799
1688
  Public: { scope: "account", doc: "Passes every caller, signed in or not." },
800
1689
  None: { scope: "account", doc: "Refuses every caller." }
801
1690
  };
802
- var frameworkOwner = "akanjs";
803
- var frameworkPattern = /^akanjs(?:\/|$)/;
804
- var aliasPattern = /^@(apps|libs)\/([^/]+)/;
805
- var maxMapDepth = 4;
806
1691
 
807
1692
  class GraphBuilder {
808
1693
  #source;
@@ -810,16 +1695,21 @@ class GraphBuilder {
810
1695
  #edges = new Map;
811
1696
  #contexts = new Map;
812
1697
  #members = new Map;
813
- #guards = new Map;
814
- #mapEntries = new Map;
815
1698
  #routeFiles = new Map;
816
- #declarations = new Map;
817
1699
  #routes;
818
- #adaptors = new Map;
819
- #plugs = new Map;
1700
+ #resolver;
1701
+ #adaptors;
820
1702
  constructor(source) {
821
1703
  this.#source = source;
822
1704
  this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
1705
+ this.#resolver = new GuardResolver(source, this.#routes);
1706
+ this.#adaptors = new AdaptorResolver(source, this.#resolver, {
1707
+ key: (kind, owner, scope) => this.#key(kind, owner, scope),
1708
+ addNode: (id, kind, owner, name, span, detail) => this.#addNode(id, kind, owner, null, name, span, detail),
1709
+ addEdge: (from, to, kind, label, at) => this.#addEdge(from, to, kind, label, at),
1710
+ digest: (node, text, extent) => this.#digest(node, text, extent),
1711
+ location: (span) => this.#location(span)
1712
+ });
823
1713
  }
824
1714
  build() {
825
1715
  const { facts } = this.#source;
@@ -831,17 +1721,9 @@ class GraphBuilder {
831
1721
  ...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
832
1722
  ];
833
1723
  const typed = [];
834
- for (const file of facts)
835
- for (const fact of file.adaptors) {
836
- const declared = { fact, owner: file.owner, fileName: file.fileName };
837
- this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
838
- this.#adaptorNodeOf(declared);
839
- }
840
- for (const file of facts)
841
- for (const fact of file.adaptors)
842
- for (const plug of fact.plugs)
843
- this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
844
- this.#declareGuards(facts);
1724
+ this.#adaptors.declare(facts);
1725
+ for (const guard of this.#resolver.declare(facts))
1726
+ this.#guardNodeOf(guard);
845
1727
  for (const file of facts) {
846
1728
  const { owner } = file;
847
1729
  const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
@@ -872,10 +1754,7 @@ class GraphBuilder {
872
1754
  }
873
1755
  for (const method of service.methods)
874
1756
  this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
875
- for (const plug of service.plugs)
876
- this.#plugs.set(`${owner ?? ""}
877
- ${refName}
878
- ${plug.name}`, this.#adaptorIdOf(plug.target, file));
1757
+ this.#adaptors.addService(refName, service, file);
879
1758
  }
880
1759
  for (const filter of file.filters) {
881
1760
  if (!filter.name.endsWith("Filter"))
@@ -885,31 +1764,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
885
1764
  for (const query of filter.queries)
886
1765
  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);
887
1766
  }
888
- for (const slice of file.slices) {
889
- const refName = slice.model;
890
- if (!refName)
891
- continue;
892
- const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards);
893
- for (const guard of slice.guards)
894
- this.#guard(fallback, guard, file);
895
- for (const key of slice.keys) {
896
- const detail = [...key.guards, ...key.live ? ["live"] : []];
897
- const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
898
- for (const guard of key.guards)
899
- this.#guard(node, guard, file);
900
- }
901
- }
902
- for (const endpoint of file.endpoints) {
903
- const refName = endpoint.model;
904
- if (!refName)
905
- continue;
906
- for (const key of endpoint.keys) {
907
- const detail = [...key.verb ? [key.verb] : [], ...key.guards];
908
- const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
909
- for (const guard of key.guards)
910
- this.#guard(node, guard, file);
911
- }
912
- }
1767
+ this.#addGuarded(file);
913
1768
  for (const internal of file.internals)
914
1769
  for (const key of internal.keys)
915
1770
  if (internal.model && key.type !== "resolveField")
@@ -925,7 +1780,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
925
1780
  types.set(node.label, [...types.get(node.label) ?? [], node]);
926
1781
  for (const node of typed)
927
1782
  for (const field of node.fields ?? []) {
928
- const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#visible(type.owner, node.owner));
1783
+ const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#resolver.visible(type.owner, node.owner));
929
1784
  field.ref = (candidates.find((type) => type.owner === node.owner) ?? candidates[0])?.id ?? null;
930
1785
  if (field.ref)
931
1786
  this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
@@ -1007,61 +1862,52 @@ ${label ?? ""}`;
1007
1862
  else
1008
1863
  this.#edges.set(key, { from, to, kind, label, count: 1, at });
1009
1864
  }
1010
- #guard(node, guard, file) {
1011
- const [role, local] = guard.includes(" ") ? guard.split(" ") : [null, guard];
1012
- if (!local || local === "?")
1013
- return;
1014
- this.#addEdge(node.id, this.#guardIdOf(local, file), "guard", role, node.at);
1015
- }
1016
- #declareGuards(facts) {
1017
- const factories = new Map;
1018
- for (const file of facts)
1019
- for (const fact of file.guardFactories) {
1020
- const factory = { fact, owner: file.owner, fileName: file.fileName, made: [] };
1021
- factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
1022
- }
1023
- const sameFile = (factory, name) => name ? factories.get(name)?.find((other) => other.fileName === factory.fileName) : undefined;
1024
- const declare = (guard, factory) => {
1025
- this.#guards.set(guard.name, [...this.#guards.get(guard.name) ?? [], guard]);
1026
- factory?.made.push(guard);
1027
- };
1028
- const madeBy = (factory, fileName, extent) => {
1029
- const parts = [{ fileName, extent }];
1030
- const seen = new Set;
1031
- for (let current = factory;current && !seen.has(current); current = sameFile(current, current.fact.via)) {
1032
- seen.add(current);
1033
- parts.push({ fileName: current.fileName, extent: current.fact.extent });
1034
- }
1035
- return parts;
1036
- };
1037
- for (const file of facts) {
1038
- const { owner, fileName } = file;
1039
- for (const { name, span, extent, scope, doc, factory: callee } of file.guards) {
1040
- const factory = callee ? factories.get(callee)?.find((other) => other.fileName === fileName) : undefined;
1041
- declare({ name, owner, span, scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
1865
+ #addGuarded(file) {
1866
+ const { owner } = file;
1867
+ for (const slice of file.slices) {
1868
+ const refName = slice.model;
1869
+ if (!refName)
1870
+ continue;
1871
+ const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards.map((guard) => guardLabelOf(guard, file.guardLabels)));
1872
+ for (const guard of slice.guards)
1873
+ this.#guard(fallback, guard, file);
1874
+ for (const key of slice.keys) {
1875
+ const detail = [
1876
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels)),
1877
+ ...key.live ? ["live"] : []
1878
+ ];
1879
+ const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1880
+ for (const guard of key.guards)
1881
+ this.#guard(node, guard, file);
1042
1882
  }
1043
- for (const { name, span, extent, doc, callee } of file.guardCalls) {
1044
- const imported = file.imports.find((item) => item.name === callee);
1045
- if (!imported)
1046
- continue;
1047
- const from = this.#ownerOfSpecifier(fileName, imported.from);
1048
- const candidates = factories.get(imported.imported === "default" || imported.imported === "*" ? callee : imported.imported) ?? [];
1049
- const factory = candidates.find((other) => other.owner === from) ?? this.#soleVisible(candidates, from ?? owner);
1050
- if (factory)
1051
- declare({ name, owner, span, scope: factory.fact.scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
1883
+ }
1884
+ for (const endpoint of file.endpoints) {
1885
+ const refName = endpoint.model;
1886
+ if (!refName)
1887
+ continue;
1888
+ for (const key of endpoint.keys) {
1889
+ const detail = [
1890
+ ...key.verb ? [key.verb] : [],
1891
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels))
1892
+ ];
1893
+ const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1894
+ for (const guard of key.guards)
1895
+ this.#guard(node, guard, file);
1052
1896
  }
1053
1897
  }
1054
- for (const list of factories.values())
1055
- for (const { fact, made } of list)
1056
- if (made.every((guard) => !guard.doc))
1057
- for (const guard of made)
1058
- guard.doc = fact.doc;
1059
- for (const list of this.#guards.values())
1060
- for (const guard of list)
1061
- this.#guardNodeOf(guard);
1898
+ }
1899
+ #guard(node, guard, file) {
1900
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
1901
+ if (!local || local === "?")
1902
+ return;
1903
+ const id = local.includes("@") ? this.#madeInPlaceIdOf(local, file) : this.#guardIdOf(local, file);
1904
+ if (id)
1905
+ this.#addEdge(node.id, id, "guard", role, node.at);
1062
1906
  }
1063
1907
  #guardIdOf(local, site) {
1064
- const found = this.#findGuard(local, site, 0);
1908
+ const found = this.#resolver.find(local, site);
1909
+ if ("external" in found)
1910
+ return null;
1065
1911
  if ("guard" in found)
1066
1912
  return this.#guardNodeOf(found.guard);
1067
1913
  const { name, from } = found;
@@ -1074,99 +1920,18 @@ ${label ?? ""}`;
1074
1920
  node.doc = builtin.doc;
1075
1921
  return node.id;
1076
1922
  }
1077
- #findGuard(local, site, depth) {
1078
- const [head = local, member] = local.split(".");
1079
- const imported = site.imports.find((item) => item.name === head);
1080
- const from = imported ? this.#ownerOfSpecifier(site.fileName, imported.from) : undefined;
1081
- const holder = imported ? from : site.owner;
1082
- if (member && imported?.imported !== "*" && holder !== undefined && holder !== frameworkOwner && depth < maxMapDepth) {
1083
- const objectName = imported && imported.imported !== "default" ? imported.imported : head;
1084
- for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
1085
- const found = this.#findGuard(entry.name, entry.site, depth + 1);
1086
- if ("guard" in found)
1087
- return found;
1088
- }
1089
- }
1090
- const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1091
- const declared = this.#guards.get(name) ?? [];
1092
- 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));
1093
- return guard ? { guard } : { name, from };
1094
- }
1095
- #mapEntriesOf(objectName, member, fileName, owner) {
1096
- const key = `${objectName}
1097
- ${member}
1098
- ${fileName ?? ""}
1099
- ${owner ?? ""}`;
1100
- const cached = this.#mapEntries.get(key);
1101
- if (cached)
1102
- return cached;
1103
- const { ts, reader } = this.#source;
1104
- const entries = [];
1105
- const fileNames = [
1106
- ...fileName ? [fileName] : [],
1107
- ...this.#source.files.filter(([other, words]) => other !== fileName && owner !== null && !testFilePattern.test(other) && words.includes(`
1108
- ${objectName}
1109
- `) && words.includes(`
1110
- ${member}
1111
- `) && this.#source.ownerOf(other) === owner).map(([other]) => other)
1112
- ];
1113
- for (const holder of fileNames) {
1114
- const text = this.#source.readText(holder);
1115
- if (text === undefined)
1116
- continue;
1117
- const source = this.#source.parse(holder, text);
1118
- const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
1119
- for (const statement of source.statements) {
1120
- if (!ts.isVariableStatement(statement))
1121
- continue;
1122
- const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
1123
- const object = map && reader.unwrap(map);
1124
- if (!object || !ts.isObjectLiteralExpression(object))
1125
- continue;
1126
- for (const property of object.properties) {
1127
- if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
1128
- entries.push({ name: member, site });
1129
- if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
1130
- continue;
1131
- const value = reader.unwrap(property.initializer);
1132
- if (ts.isIdentifier(value))
1133
- entries.push({ name: value.text, site });
1134
- else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
1135
- entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
1136
- }
1137
- }
1138
- }
1139
- this.#mapEntries.set(key, entries);
1140
- return entries;
1141
- }
1142
- #declarationOf(owner, name, near) {
1143
- const key = `${owner}
1144
- ${name}
1145
- ${near ?? ""}`;
1146
- const cached = this.#declarations.get(key);
1147
- if (cached !== undefined)
1148
- return cached;
1149
- const isNear = (fileName) => {
1150
- const relative = this.#source.relative(fileName);
1151
- return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1152
- };
1153
- const candidates = this.#source.files.filter(([fileName, words]) => !testFilePattern.test(fileName) && words.includes(`
1154
- ${name}
1155
- `) && this.#source.ownerOf(fileName) === owner).map(([fileName]) => fileName);
1156
- let found = null;
1157
- for (const fileName of [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))]) {
1158
- const text = this.#source.readText(fileName);
1159
- if (text === undefined)
1160
- continue;
1161
- const source = this.#source.parse(fileName, text);
1162
- const declared = this.#routes.declarationIn(source, name);
1163
- if (!declared)
1164
- continue;
1165
- found = { source, statement: declared.statement, declared: declared.declared };
1166
- break;
1167
- }
1168
- this.#declarations.set(key, found);
1169
- return found;
1923
+ #madeInPlaceIdOf(name, file) {
1924
+ const made = this.#resolver.madeInPlace(file.fileName, name);
1925
+ if (made)
1926
+ return this.#guardNodeOf(made);
1927
+ const head = name.slice(name.indexOf(":") + 1, name.indexOf("@")).split(".")[0] ?? name;
1928
+ const imported = file.imports.find((item) => item.name === head);
1929
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : file.owner;
1930
+ if (from === undefined || from === frameworkOwner)
1931
+ return null;
1932
+ const scope = `${this.#resolver.inOwner(file.fileName, file.owner)}#${name}`;
1933
+ const label = guardLabelOf(name, file.guardLabels);
1934
+ return this.#addNode(this.#key("guard", file.owner, scope), "guard", file.owner, null, name, null, [], label).id;
1170
1935
  }
1171
1936
  #addRoutes() {
1172
1937
  const routes = [];
@@ -1219,22 +1984,23 @@ ${dir}`, node);
1219
1984
  }
1220
1985
  #routeGuardIdOf({ name: local, local: declared }, fact, text) {
1221
1986
  if (declared) {
1222
- const file = this.#source.relative(fact.fileName).slice(fact.owner.length + 1);
1223
- const id = this.#key("guard", fact.owner, `${file}#${local}`);
1987
+ const id = this.#key("guard", fact.owner, `${this.#resolver.inOwner(fact.fileName, fact.owner)}#${local}`);
1224
1988
  return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
1225
1989
  }
1226
- const imported = fact.imports.find((item) => item.name === local);
1227
- if (!imported)
1990
+ const [head = local, member] = local.split(".");
1991
+ const imported = fact.imports.find((item) => item.name === head);
1992
+ if (!imported || member && imported.imported !== "*")
1228
1993
  return null;
1229
- const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
1230
- const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
1994
+ const name = member ?? (imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local);
1995
+ const from = this.#resolver.ownerOfSpecifier(fact.fileName, imported.from);
1231
1996
  if (!from || from === frameworkOwner)
1232
1997
  return null;
1233
- const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
1998
+ const found = this.#resolver.declarationOf(from, name, this.#resolver.pathOfSpecifier(fact.fileName, imported.from));
1234
1999
  if (!found)
1235
2000
  return null;
1236
2001
  const { reader } = this.#source;
1237
- 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));
2002
+ const owner = this.#source.ownerOf(found.source.fileName) ?? from;
2003
+ 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);
1238
2004
  }
1239
2005
  #routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
1240
2006
  const isNew = !this.#nodes.has(id);
@@ -1244,60 +2010,22 @@ ${dir}`, node);
1244
2010
  this.#digest(node, text, extent);
1245
2011
  return node.id;
1246
2012
  }
1247
- #adaptorIdOf(local, file) {
1248
- const [head, member] = local.split(".");
1249
- const imported = file.imports.find((item) => item.name === head);
1250
- const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
1251
- const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1252
- const declared = this.#adaptors.get(name) ?? [];
1253
- 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)));
1254
- if (found)
1255
- return this.#adaptorNodeOf(found);
1256
- const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
1257
- const detail = from === frameworkOwner ? [frameworkOwner] : [];
1258
- return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
1259
- }
1260
- #adaptorNodeOf({ fact, owner, fileName }) {
1261
- const id = this.#key("adaptor", owner, fact.name);
1262
- const detail = fact.refName ? [fact.refName] : [];
1263
- const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
1264
- if (fact.doc && !node.doc)
1265
- node.doc = fact.doc;
1266
- this.#digest(node, this.#source.readText(fileName), fact.extent);
1267
- return node.id;
1268
- }
1269
- #guardNodeOf({ name, owner, span, scope, doc, parts }) {
1270
- const node = this.#addNode(this.#key("guard", owner, name), "guard", owner, null, name, span, scope ? [scope] : []);
2013
+ #guardNodeOf({ name, owner, span, scope, doc, parts, file, label }) {
2014
+ const id = this.#key("guard", owner, file ? `${file}#${name}` : name);
2015
+ const node = this.#addNode(id, "guard", owner, null, name, span, scope ? [scope] : [], label ?? name);
1271
2016
  if (doc && !node.doc)
1272
2017
  node.doc = doc;
1273
- const digest = node.digest ? null : this.#hashOf(...parts.map(({ fileName, extent }) => [
1274
- this.#source.readText(fileName),
1275
- extent
1276
- ]));
1277
- if (digest)
1278
- node.digest = digest;
2018
+ if (node.digest)
2019
+ return node.id;
2020
+ const digest = this.#hashOf(...parts.map(({ fileName, extent }) => [this.#source.readText(fileName), extent]));
2021
+ if (!digest)
2022
+ return node.id;
2023
+ node.digest = digest;
2024
+ const sources = [...new Set(parts.map(({ fileName }) => this.#source.relative(fileName)))].filter((source) => source !== node.at?.file);
2025
+ if (sources.length)
2026
+ node.sources = sources;
1279
2027
  return node.id;
1280
2028
  }
1281
- #ownerOfSpecifier(fileName, specifier) {
1282
- if (frameworkPattern.test(specifier))
1283
- return frameworkOwner;
1284
- const path = this.#pathOfSpecifier(fileName, specifier);
1285
- return path === null ? undefined : ownerOf(path) ?? undefined;
1286
- }
1287
- #pathOfSpecifier(fileName, specifier) {
1288
- const alias = specifier.match(aliasPattern);
1289
- if (alias)
1290
- return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1291
- if (!specifier.startsWith("."))
1292
- return null;
1293
- const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1294
- for (const segment of specifier.split("/"))
1295
- if (segment === "..")
1296
- segments.pop();
1297
- else if (segment !== ".")
1298
- segments.push(segment);
1299
- return segments.join("/");
1300
- }
1301
2029
  #moduleDirOf(fileName) {
1302
2030
  const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
1303
2031
  return dir?.startsWith("_") && !dir.startsWith("__") ? dir.slice(1) : dir;
@@ -1333,20 +2061,13 @@ ${dir}`, node);
1333
2061
  visit(constant);
1334
2062
  return [...fields.values()];
1335
2063
  }
1336
- #visible(target, from) {
1337
- return !from || !target || target === from || target.startsWith("libs/");
1338
- }
1339
- #soleVisible(declared, from) {
1340
- const visible = declared.filter((other) => this.#visible(other.owner, from));
1341
- return visible.length === 1 ? visible[0] : undefined;
1342
- }
1343
2064
  #targetNodeOf({ family, owner, refName, sourceKey }) {
1344
2065
  const isDefault = /^[A-Z]/.test(sourceKey);
1345
2066
  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;
1346
2067
  return id && this.#nodes.has(id) ? id : null;
1347
2068
  }
1348
2069
  #membersNamed(kind, name, owner, refName) {
1349
- const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#visible(node.owner, owner));
2070
+ const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#resolver.visible(node.owner, owner));
1350
2071
  const own = nodes.filter((node) => node.owner === owner);
1351
2072
  return (own.length ? own : nodes).map((node) => node.id);
1352
2073
  }
@@ -1391,10 +2112,8 @@ st
1391
2112
  visit(source);
1392
2113
  }
1393
2114
  #link(fileName, source, identifier, chain, context, owner) {
1394
- const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#plugs.get(`${context.owner ?? ""}
1395
- ${context.module}
1396
- ${chain[1]}`) : undefined;
1397
- const resolved = plugged ? [plugged] : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
2115
+ const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#adaptors.pluggedOf(context.owner, context.module, chain[1]) : [];
2116
+ const resolved = plugged.length ? plugged : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
1398
2117
  const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
1399
2118
  if (!targets.length)
1400
2119
  return;
@@ -1700,6 +2419,10 @@ var endpointNames = (key) => {
1700
2419
  };
1701
2420
 
1702
2421
  // pkgs/@akanjs/language/nameTable.ts
2422
+ var guardDetailOf = (facts, guards) => {
2423
+ const labels = Object.assign({}, ...facts.map((file) => file.guardLabels));
2424
+ return guards.length ? [`guards: ${guards.map((guard) => guardLabelOf(guard, labels)).join(", ")}`] : [];
2425
+ };
1703
2426
  var capitalize3 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
1704
2427
 
1705
2428
  class NameTable {
@@ -1799,7 +2522,7 @@ class NameTable {
1799
2522
  role: "sig",
1800
2523
  gate: "sig",
1801
2524
  label: sliceClass.name,
1802
- detail: sliceClass.guards.length ? [`guards: ${sliceClass.guards.join(", ")}`] : []
2525
+ detail: guardDetailOf(facts, sliceClass.guards)
1803
2526
  });
1804
2527
  if (modelDoc)
1805
2528
  this.#add({
@@ -1864,7 +2587,7 @@ class NameTable {
1864
2587
  sourceKey: slice.name,
1865
2588
  span: slice.span,
1866
2589
  label: `slice ${refName} (default)`,
1867
- detail: slice.guards.length ? [`guards: ${slice.guards.join(", ")}`] : []
2590
+ detail: guardDetailOf(facts, slice.guards)
1868
2591
  };
1869
2592
  this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
1870
2593
  for (const key of slice.keys)
@@ -1874,7 +2597,7 @@ class NameTable {
1874
2597
  sourceKey: key.name,
1875
2598
  span: key.span,
1876
2599
  label: `slice ${key.name}`,
1877
- detail: [...key.guards.length ? [`guards: ${key.guards.join(", ")}`] : [], ...key.live ? ["live"] : []]
2600
+ detail: [...guardDetailOf(facts, key.guards), ...key.live ? ["live"] : []]
1878
2601
  }, refName, key.name, modelNameSet, slice.name);
1879
2602
  }
1880
2603
  if (filterClass) {
@@ -1931,7 +2654,7 @@ class NameTable {
1931
2654
  role: key.verb === "pubsub" ? "subscribe" : "listen",
1932
2655
  gate: null,
1933
2656
  label: `${key.verb} ${key.name}`,
1934
- detail: key.guards.length ? [`guards: ${key.guards.join(", ")}`] : []
2657
+ detail: guardDetailOf(facts, key.guards)
1935
2658
  });
1936
2659
  }
1937
2660
  }
@@ -1988,15 +2711,13 @@ var sourcePattern = /\.(constant|document|service|signal|store)\.ts$/;
1988
2711
  var dictionaryPattern = /\.dictionary\.ts$/;
1989
2712
  var srvkitPattern = /\/srvkit\//;
1990
2713
  var wordPattern = /[A-Za-z_$][\w$]*/g;
1991
- var identifierPattern = /^[A-Za-z_$][\w$]*$/;
1992
- var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
2714
+ var identifierPattern2 = /^[A-Za-z_$][\w$]*$/;
1993
2715
  var stateReceivers = new Set(["use", "sel", "get", "do"]);
1994
2716
  var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
1995
2717
  var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
1996
2718
  var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
1997
2719
  var parseCacheSize = 64;
1998
2720
  var pathOf = (fileName) => fileName.replace(/\\/g, "/");
1999
- var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
2000
2721
  var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
2001
2722
 
2002
2723
  class AkanIndex {
@@ -2099,7 +2820,7 @@ ${[...new Set(text.match(wordPattern))].join(`
2099
2820
  }
2100
2821
  renameAt(givenName, offset, newKey, readText) {
2101
2822
  const sources = this.#sourceEntriesAt(pathOf(givenName), offset).filter((entry) => entry.renamed);
2102
- if (!sources.length || !identifierPattern.test(newKey))
2823
+ if (!sources.length || !identifierPattern2.test(newKey))
2103
2824
  return null;
2104
2825
  return [
2105
2826
  { span: sources[0].span, newText: newKey },
@@ -2127,6 +2848,8 @@ canPass
2127
2848
  adapt
2128
2849
  `) || words.includes(`
2129
2850
  dangerouslyAdapt
2851
+ `) || words.includes(`
2852
+ applyAdaptor
2130
2853
  `);
2131
2854
  }
2132
2855
  static #sourceKeyOf({ family, sourceKey, label, detail, span }) {
@@ -2807,6 +3530,11 @@ ${union.name}`)));
2807
3530
  const field = this.#memberOf(this.#classOf(receiver), receiver.name.text);
2808
3531
  return field && ts.isPropertyDeclaration(field) ? this.#namesIn(field.type) : new Set;
2809
3532
  }
3533
+ if (ts.isCallExpression(receiver) && ts.isPropertyAccessExpression(receiver.expression) && receiver.expression.expression.kind === ts.SyntaxKind.ThisKeyword) {
3534
+ const field = this.#memberOf(this.#classOf(receiver), receiver.expression.name.text);
3535
+ const type = field && ts.isPropertyDeclaration(field) ? field.type : undefined;
3536
+ return type && ts.isFunctionTypeNode(type) ? this.#namesIn(type.type) : new Set;
3537
+ }
2810
3538
  return this.#declaredTypeNames(receiver) ?? new Set;
2811
3539
  }
2812
3540
  #memberName(name) {