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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs CHANGED
@@ -60,7 +60,8 @@ var isEdited = (before, after) => before.digest !== undefined && after.digest !=
60
60
  var byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
61
61
 
62
62
  class AkanGraphDiff {
63
- static between(base, head) {
63
+ static between(given, head) {
64
+ const base = AkanGraphDiff.#renamed(given, head);
64
65
  const baseNodes = new Map(base.nodes.map((node) => [node.id, node]));
65
66
  const headNodes = new Map(head.nodes.map((node) => [node.id, node]));
66
67
  const marked = new Map;
@@ -79,6 +80,9 @@ class AkanGraphDiff {
79
80
  ...head.edges.flatMap((edge) => !baseKeys.has(edgeKeyOf(edge)) ? [{ ...edge, change: "added" }] : marked.has(edge.from) || marked.has(edge.to) ? [edge] : []),
80
81
  ...base.edges.filter((edge) => !headKeys.has(edgeKeyOf(edge))).map((edge) => ({ ...edge, change: "removed" }))
81
82
  ];
83
+ const shown = new Set([...marked.keys(), ...edges.flatMap((edge) => [edge.from, edge.to])]);
84
+ const listed = new Set(edges.map(edgeKeyOf));
85
+ edges.push(...head.edges.filter((edge) => edge.kind === "binds" && shown.has(edge.from) && !listed.has(edgeKeyOf(edge))));
82
86
  const context = edges.flatMap((edge) => [edge.from, edge.to]).filter((id) => !marked.has(id)).flatMap((id) => {
83
87
  const node = headNodes.get(id) ?? baseNodes.get(id);
84
88
  return node ? [node] : [];
@@ -90,6 +94,40 @@ class AkanGraphDiff {
90
94
  summary[node.change] += 1;
91
95
  return { version: 1, nodes, edges, summary };
92
96
  }
97
+ static #renamed(base, head) {
98
+ const baseIds = new Set(base.nodes.map((node) => node.id));
99
+ const headIds = new Set(head.nodes.map((node) => node.id));
100
+ const pairKeyOf = ({ kind, owner, name, at }) => at ? `${kind}
101
+ ${owner ?? ""}
102
+ ${name}
103
+ ${at.file}` : null;
104
+ const shortIdOf = ({ kind, owner, name }) => `${kind}:${owner ?? ""}:${name}`;
105
+ const isFileKeyed = (node) => node.id !== shortIdOf(node) && node.id.startsWith(`${node.kind}:${node.owner ?? ""}:`) && node.id.endsWith(`#${node.name}`);
106
+ const fromBase = new Map(base.nodes.flatMap((node) => {
107
+ const key = headIds.has(node.id) ? null : pairKeyOf(node);
108
+ return key ? [[key, node]] : [];
109
+ }));
110
+ const renames = new Map;
111
+ for (const node of head.nodes) {
112
+ if (baseIds.has(node.id))
113
+ continue;
114
+ const key = pairKeyOf(node);
115
+ const before = key ? fromBase.get(key) : undefined;
116
+ if (!before)
117
+ continue;
118
+ const isPair = isFileKeyed(node) ? before.id === shortIdOf(node) : isFileKeyed(before) && node.id === shortIdOf(before);
119
+ if (isPair)
120
+ renames.set(before.id, node.id);
121
+ }
122
+ if (!renames.size)
123
+ return base;
124
+ const rename = (id) => renames.get(id) ?? id;
125
+ return {
126
+ ...base,
127
+ nodes: base.nodes.map((node) => renames.has(node.id) ? { ...node, id: rename(node.id) } : node),
128
+ edges: base.edges.map((edge) => ({ ...edge, from: rename(edge.from), to: rename(edge.to) }))
129
+ };
130
+ }
93
131
  static #mark(node, change) {
94
132
  return node.fields ? { ...node, change, fields: node.fields.map((field) => ({ ...field, change })) } : { ...node, change };
95
133
  }
@@ -123,6 +161,239 @@ class AkanGraphDiff {
123
161
  return node.fields ? { ...node, fields: node.fields.map(({ digest: _field, ...field }) => field) } : node;
124
162
  }
125
163
  }
164
+ // pkgs/@akanjs/language/guardFacts.ts
165
+ var guardLabelOf = (guard, labels) => {
166
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
167
+ const label = labels[local];
168
+ return label === undefined ? guard : role ? `${role} ${label}` : label;
169
+ };
170
+ var maxLabelArgs = 32;
171
+ var guardRefReaderOf = (ts, ast, unwrap) => {
172
+ const pathOf = (expression) => {
173
+ if (ts.isIdentifier(expression))
174
+ return expression.text;
175
+ if (!ts.isPropertyAccessExpression(expression))
176
+ return null;
177
+ const head = pathOf(expression.expression);
178
+ return head && `${head}.${expression.name.text}`;
179
+ };
180
+ const calleeOf = (call) => pathOf(unwrap(call.expression)) ?? "?";
181
+ const labelOf = (source, call) => {
182
+ const args = call.arguments.map((arg) => arg.getText(source)).join(", ");
183
+ return `${calleeOf(call)}(${args.length <= maxLabelArgs && !args.includes(`
184
+ `) ? args : "…"})`;
185
+ };
186
+ const guardRefOf = (expression, made, member) => {
187
+ const value = unwrap(expression);
188
+ if (!ts.isCallExpression(value))
189
+ return pathOf(value) ?? ast.expressionName(value);
190
+ const prefix = `${member}:${calleeOf(value)}@`;
191
+ const name = `${prefix}${made.filter((other) => other.name.startsWith(prefix)).length + 1}`;
192
+ made.push({ call: value, name });
193
+ return name;
194
+ };
195
+ const guardsIn = (source, object, made, member) => {
196
+ const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
197
+ if (!guards)
198
+ return [];
199
+ const refOf = (expression) => guardRefOf(expression, made, member);
200
+ if (ts.isArrayLiteralExpression(guards))
201
+ return guards.elements.map(refOf).filter((name) => name !== null);
202
+ if (ts.isObjectLiteralExpression(guards))
203
+ return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
204
+ const value = unwrap(property.initializer);
205
+ const names = ts.isArrayLiteralExpression(value) ? value.elements.map(refOf) : [refOf(value)];
206
+ return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
207
+ });
208
+ const single = refOf(guards);
209
+ return single ? [single] : [];
210
+ };
211
+ return { pathOf, labelOf, guardsIn };
212
+ };
213
+ var guardFactoryReaderOf = (ts, ast, { spanOf, extentOf, unwrap, docOf }, pathOf) => {
214
+ const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
215
+ const scopeOf = (node) => {
216
+ const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
217
+ const value = scope && unwrap(scope);
218
+ return value && ts.isStringLiteralLike(value) ? value.text : null;
219
+ };
220
+ const functionsOf = (source) => {
221
+ const functions = new Map;
222
+ for (const statement of source.statements) {
223
+ if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
224
+ functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
225
+ if (!ts.isVariableStatement(statement))
226
+ continue;
227
+ for (const declaration of statement.declarationList.declarations) {
228
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
229
+ if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
230
+ functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
231
+ }
232
+ }
233
+ return functions;
234
+ };
235
+ const returnedOf = (body, guardNames) => {
236
+ const locals = new Map;
237
+ const pending = [];
238
+ const visit = (node) => {
239
+ if (ts.isClassDeclaration(node) && node.name)
240
+ locals.set(node.name.text, node);
241
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
242
+ locals.set(node.name.text, node.initializer);
243
+ if (ts.isReturnStatement(node) && node.expression)
244
+ pending.push(node.expression);
245
+ if (!ts.isFunctionLike(node) && !ts.isClassLike(node))
246
+ ts.forEachChild(node, visit);
247
+ };
248
+ if (ts.isBlock(body))
249
+ ts.forEachChild(body, visit);
250
+ else
251
+ pending.push(body);
252
+ let guard = null;
253
+ const calls = [];
254
+ const seen = new Set;
255
+ const passed = new Set;
256
+ for (let node = pending.pop();node; node = pending.pop()) {
257
+ if (seen.has(node))
258
+ continue;
259
+ seen.add(node);
260
+ const value = ts.isExpression(node) ? unwrap(node) : node;
261
+ if ((ts.isClassExpression(value) || ts.isClassDeclaration(value)) && implementsGuard(value, guardNames))
262
+ guard ??= value;
263
+ else if (ts.isConditionalExpression(value))
264
+ pending.push(value.whenTrue, value.whenFalse);
265
+ else if (ts.isBinaryExpression(value))
266
+ pending.push(value.left, value.right);
267
+ else if (ts.isIdentifier(value)) {
268
+ const local = locals.get(value.text);
269
+ if (local)
270
+ pending.push(local);
271
+ } else if (ts.isCallExpression(value)) {
272
+ const callee = pathOf(unwrap(value.expression));
273
+ if (callee && !passed.has(node) && !calls.includes(callee))
274
+ calls.push(callee);
275
+ for (const arg of value.arguments)
276
+ passed.add(arg);
277
+ pending.push(...value.arguments);
278
+ }
279
+ }
280
+ return { guard, calls };
281
+ };
282
+ const guardFactoriesOf = (source, guardNames, isImported) => {
283
+ const functions = functionsOf(source);
284
+ const returned = new Map([...functions].map(([name, { body }]) => [name, returnedOf(body, guardNames)]));
285
+ const members = new Set;
286
+ const isFactoryCall = (callee) => members.has(callee) || !functions.has(callee) && isImported(callee);
287
+ for (let isGrowing = true;isGrowing; ) {
288
+ isGrowing = false;
289
+ for (const [name, { guard, calls }] of returned) {
290
+ if (members.has(name) || !guard && !calls.some(isFactoryCall))
291
+ continue;
292
+ members.add(name);
293
+ isGrowing = true;
294
+ }
295
+ }
296
+ const factories = new Map;
297
+ for (const name of members) {
298
+ const found = functions.get(name);
299
+ const made = returned.get(name);
300
+ if (!found || !made)
301
+ continue;
302
+ factories.set(name, {
303
+ name,
304
+ span: spanOf(source, found.name),
305
+ extent: extentOf(source, found.statement),
306
+ scope: made.guard ? scopeOf(made.guard) : null,
307
+ doc: docOf(source, found.statement),
308
+ direct: !!made.guard,
309
+ via: made.calls.filter(isFactoryCall)
310
+ });
311
+ }
312
+ return factories;
313
+ };
314
+ return { implementsGuard, scopeOf, guardFactoriesOf };
315
+ };
316
+ var createGuardFactReader = (ts, ast, base) => {
317
+ const { spanOf, extentOf, unwrap, docOf } = base;
318
+ const { pathOf, labelOf, guardsIn } = guardRefReaderOf(ts, ast, unwrap);
319
+ const { implementsGuard, scopeOf, guardFactoriesOf } = guardFactoryReaderOf(ts, ast, base, pathOf);
320
+ const guardsOf = (source, imports, madeInPlace) => {
321
+ const guardNames = new Set([
322
+ "Guard",
323
+ ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
324
+ ]);
325
+ const imported = new Set(imports.map(({ name }) => name));
326
+ const isImported = (callee) => imported.has(callee.split(".")[0] ?? callee);
327
+ const factories = guardFactoriesOf(source, guardNames, isImported);
328
+ const guards = [];
329
+ const calls = [];
330
+ const makeOf = (made, value, isInPlace = false) => {
331
+ if (ts.isClassExpression(value) && implementsGuard(value, guardNames)) {
332
+ guards.push({ ...made, scope: scopeOf(value), factory: null });
333
+ return;
334
+ }
335
+ const callee = ts.isCallExpression(value) ? pathOf(unwrap(value.expression)) : null;
336
+ const factory = callee && factories.get(callee);
337
+ if (factory)
338
+ guards.push({ ...made, scope: factory.scope, factory: factory.name });
339
+ else if (callee && (isImported(callee) || isInPlace))
340
+ calls.push({ ...made, callee, kind: "call", scope: null });
341
+ };
342
+ for (const statement of source.statements) {
343
+ if (ts.isClassDeclaration(statement) && statement.name) {
344
+ const declared = {
345
+ name: statement.name.text,
346
+ span: spanOf(source, statement.name),
347
+ extent: extentOf(source, statement),
348
+ scope: scopeOf(statement),
349
+ doc: docOf(source, statement)
350
+ };
351
+ const base = statement.heritageClauses?.find(({ token }) => token === ts.SyntaxKind.ExtendsKeyword)?.types[0];
352
+ const callee = base && pathOf(base.expression);
353
+ if (implementsGuard(statement, guardNames))
354
+ guards.push({ ...declared, factory: null });
355
+ else if (callee)
356
+ calls.push({ ...declared, callee, kind: "extends" });
357
+ }
358
+ if (!ts.isVariableStatement(statement))
359
+ continue;
360
+ for (const declaration of statement.declarationList.declarations) {
361
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
362
+ if (!initializer || !ts.isIdentifier(declaration.name))
363
+ continue;
364
+ const holder = declaration.name.text;
365
+ makeOf({
366
+ name: holder,
367
+ span: spanOf(source, declaration.name),
368
+ extent: extentOf(source, statement),
369
+ doc: docOf(source, statement)
370
+ }, initializer);
371
+ if (!ts.isObjectLiteralExpression(initializer))
372
+ continue;
373
+ for (const property of initializer.properties) {
374
+ const key = ts.isPropertyAssignment(property) ? ast.nodeName(property.name) : null;
375
+ if (key && ts.isPropertyAssignment(property))
376
+ makeOf({
377
+ name: `${holder}.${key}`,
378
+ span: spanOf(source, property.name),
379
+ extent: extentOf(source, property),
380
+ doc: docOf(source, property)
381
+ }, unwrap(property.initializer));
382
+ }
383
+ }
384
+ }
385
+ for (const { call, name } of madeInPlace)
386
+ makeOf({ name, span: spanOf(source, call), extent: extentOf(source, call), doc: null }, call, true);
387
+ const guardLabels = Object.fromEntries(madeInPlace.map(({ call, name }) => [name, labelOf(source, call)]));
388
+ return { guards, guardFactories: [...factories.values()], guardCalls: calls, guardLabels };
389
+ };
390
+ return { guardsIn, guardsOf };
391
+ };
392
+
393
+ // pkgs/@akanjs/language/escapeRegExp.ts
394
+ var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
395
+ var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
396
+
126
397
  // pkgs/@akanjs/language/moduleFacts.ts
127
398
  var testFilePattern = /\.(?:test|spec)\.tsx?$/;
128
399
  var moduleCallees = new Set([
@@ -191,21 +462,6 @@ var createFactReader = (ts, ast) => {
191
462
  const value = unwrap(expression);
192
463
  return ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) ? `${value.expression.text}.${value.name.text}` : ast.expressionName(value);
193
464
  };
194
- const guardsIn = (object) => {
195
- const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
196
- if (!guards)
197
- return [];
198
- if (ts.isArrayLiteralExpression(guards))
199
- return guards.elements.map(guardNameOf).filter((name) => name !== null);
200
- if (ts.isObjectLiteralExpression(guards))
201
- return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
202
- const value = unwrap(property.initializer);
203
- const names = ts.isArrayLiteralExpression(value) ? value.elements.map(guardNameOf) : [guardNameOf(value)];
204
- return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
205
- });
206
- const single = guardNameOf(guards);
207
- return single ? [single] : [];
208
- };
209
465
  const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
210
466
  const chainOf = (initializer) => {
211
467
  let root = unwrap(initializer);
@@ -222,7 +478,7 @@ var createFactReader = (ts, ast) => {
222
478
  const node = keyName(property);
223
479
  return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
224
480
  });
225
- const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
481
+ const sliceKeysOf = (source, builder, made, model) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
226
482
  if (!ts.isPropertyAssignment(property))
227
483
  return [];
228
484
  const name = ast.nodeName(property.name);
@@ -234,7 +490,7 @@ var createFactReader = (ts, ast) => {
234
490
  name,
235
491
  span: spanOf(source, property.name),
236
492
  extent: extentOf(source, property),
237
- guards: root ? guardsIn(optionObjectOf(root)) : [],
493
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
238
494
  live: methods.includes("live"),
239
495
  argCount: methods.filter((method) => argMethods.has(method)).length
240
496
  }
@@ -284,7 +540,7 @@ var createFactReader = (ts, ast) => {
284
540
  const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
285
541
  return `${ms / size}${unit}`;
286
542
  };
287
- const modesOf = (option) => {
543
+ const modesOf = (option, type) => {
288
544
  const object = option && unwrap(option);
289
545
  if (!object || !ts.isObjectLiteralExpression(object))
290
546
  return [];
@@ -294,9 +550,9 @@ var createFactReader = (ts, ast) => {
294
550
  if (name === "enabled")
295
551
  return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
296
552
  if (name === "once")
297
- return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
553
+ return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
298
554
  if (name === "lock")
299
- return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
555
+ return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
300
556
  if (name !== "serverMode" && name !== "operationMode")
301
557
  return [];
302
558
  const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
@@ -314,7 +570,8 @@ var createFactReader = (ts, ast) => {
314
570
  const { root } = chainOf(property.initializer);
315
571
  const type = root ? verbOf(root.expression) : null;
316
572
  const [first, second] = root?.arguments ?? [];
317
- const schedule = type === "cron" && first ? ts.isStringLiteralLike(first) ? first.text : first.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
573
+ const cron = type === "cron" && first ? unwrap(first) : null;
574
+ const schedule = cron ? ts.isStringLiteralLike(cron) ? cron.text : cron.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
318
575
  const option = type === "initialize" || type === "destroy" ? first : second;
319
576
  return [
320
577
  {
@@ -323,7 +580,7 @@ var createFactReader = (ts, ast) => {
323
580
  extent: extentOf(source, property),
324
581
  type,
325
582
  schedule,
326
- modes: modesOf(option)
583
+ modes: modesOf(option, type)
327
584
  }
328
585
  ];
329
586
  });
@@ -340,7 +597,20 @@ var createFactReader = (ts, ast) => {
340
597
  return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
341
598
  });
342
599
  };
343
- const endpointKeysOf = (source, builder) => {
600
+ const adaptorBindingsIn = (source) => {
601
+ const bindings = [];
602
+ const visit = (node) => {
603
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "applyAdaptor") {
604
+ const [role, adaptor] = node.arguments.map(guardNameOf);
605
+ if (role && adaptor)
606
+ bindings.push({ role, adaptor, span: spanOf(source, node.expression.name) });
607
+ }
608
+ ts.forEachChild(node, visit);
609
+ };
610
+ visit(source);
611
+ return bindings;
612
+ };
613
+ const endpointKeysOf = (source, builder, made, model) => {
344
614
  const verbOf = verbReaderOf(builder?.parameters[0]);
345
615
  return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
346
616
  if (!ts.isPropertyAssignment(property))
@@ -355,7 +625,7 @@ var createFactReader = (ts, ast) => {
355
625
  span: spanOf(source, property.name),
356
626
  extent: extentOf(source, property),
357
627
  verb: root ? verbOf(root.expression) : null,
358
- guards: root ? guardsIn(optionObjectOf(root)) : [],
628
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
359
629
  argCount: methods.filter((method) => argMethods.has(method)).length
360
630
  }
361
631
  ];
@@ -422,22 +692,6 @@ var createFactReader = (ts, ast) => {
422
692
  })) : []
423
693
  ];
424
694
  };
425
- const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
426
- const guardClassIn = (node, guardNames) => {
427
- if ((ts.isClassExpression(node) || ts.isClassDeclaration(node)) && implementsGuard(node, guardNames))
428
- return node;
429
- return ts.forEachChild(node, (child) => guardClassIn(child, guardNames)) ?? null;
430
- };
431
- const callsIn = (node, names) => {
432
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && names.has(node.expression.text))
433
- return node.expression.text;
434
- return ts.forEachChild(node, (child) => callsIn(child, names)) ?? null;
435
- };
436
- const scopeOf = (node) => {
437
- const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
438
- const value = scope && unwrap(scope);
439
- return value && ts.isStringLiteralLike(value) ? value.text : null;
440
- };
441
695
  const docOf = (source, node) => {
442
696
  const { text } = source;
443
697
  const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
@@ -453,108 +707,20 @@ var createFactReader = (ts, ast) => {
453
707
  `)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
454
708
  return lines.length ? lines.join(" ") : null;
455
709
  };
456
- const functionsOf = (source) => {
457
- const functions = new Map;
458
- for (const statement of source.statements) {
459
- if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
460
- functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
461
- if (!ts.isVariableStatement(statement))
462
- continue;
463
- for (const declaration of statement.declarationList.declarations) {
464
- const initializer = declaration.initializer && unwrap(declaration.initializer);
465
- if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
466
- functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
467
- }
468
- }
469
- return functions;
470
- };
471
- const guardFactoriesOf = (source, guardNames) => {
472
- const functions = functionsOf(source);
473
- const factories = new Map;
474
- const factoryOf = (name, scope, via) => {
475
- const found = functions.get(name);
476
- if (!found)
477
- return;
478
- factories.set(name, {
479
- name,
480
- span: spanOf(source, found.name),
481
- extent: extentOf(source, found.statement),
482
- scope,
483
- doc: docOf(source, found.statement),
484
- via
485
- });
486
- };
487
- for (const [name, { body }] of functions) {
488
- const guard = guardClassIn(body, guardNames);
489
- if (guard)
490
- factoryOf(name, scopeOf(guard), null);
491
- }
492
- for (let isGrowing = true;isGrowing; ) {
493
- isGrowing = false;
494
- for (const [name, { body }] of functions) {
495
- const called = factories.has(name) ? null : callsIn(body, new Set(factories.keys()));
496
- if (!called)
497
- continue;
498
- factoryOf(name, factories.get(called)?.scope ?? null, called);
499
- isGrowing = true;
500
- }
501
- }
502
- return factories;
503
- };
504
- const guardsOf = (source, imports) => {
505
- const guardNames = new Set([
506
- "Guard",
507
- ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
508
- ]);
509
- const imported = new Set(imports.map(({ name }) => name));
510
- const factories = guardFactoriesOf(source, guardNames);
511
- const guards = [];
512
- const calls = [];
513
- for (const statement of source.statements) {
514
- if (ts.isClassDeclaration(statement) && statement.name && implementsGuard(statement, guardNames))
515
- guards.push({
516
- name: statement.name.text,
517
- span: spanOf(source, statement.name),
518
- extent: extentOf(source, statement),
519
- scope: scopeOf(statement),
520
- doc: docOf(source, statement),
521
- factory: null
522
- });
523
- if (!ts.isVariableStatement(statement))
524
- continue;
525
- for (const declaration of statement.declarationList.declarations) {
526
- const initializer = declaration.initializer && unwrap(declaration.initializer);
527
- if (!initializer || !ts.isIdentifier(declaration.name))
528
- continue;
529
- const made = {
530
- name: declaration.name.text,
531
- span: spanOf(source, declaration.name),
532
- extent: extentOf(source, statement),
533
- doc: docOf(source, statement)
534
- };
535
- if (ts.isClassExpression(initializer) && implementsGuard(initializer, guardNames))
536
- guards.push({ ...made, scope: scopeOf(initializer), factory: null });
537
- if (!ts.isCallExpression(initializer) || !ts.isIdentifier(initializer.expression))
538
- continue;
539
- const callee = initializer.expression.text;
540
- const factory = factories.get(callee);
541
- if (factory)
542
- guards.push({ ...made, scope: factory.scope, factory: callee });
543
- else if (imported.has(callee))
544
- calls.push({ ...made, callee });
545
- }
546
- }
547
- return { guards, guardFactories: [...factories.values()], guardCalls: calls };
548
- };
710
+ const { guardsIn, guardsOf } = createGuardFactReader(ts, ast, { spanOf, extentOf, unwrap, docOf });
549
711
  const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
550
712
  const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
551
713
  const read = (source, owner) => {
552
714
  const imports = importsIn(source);
715
+ const madeInPlace = [];
553
716
  const facts = {
554
717
  fileName: source.fileName,
555
718
  owner,
556
719
  imports,
557
- ...guardsOf(source, imports),
720
+ guards: [],
721
+ guardFactories: [],
722
+ guardCalls: [],
723
+ guardLabels: {},
558
724
  constants: [],
559
725
  enums: [],
560
726
  filters: [],
@@ -564,6 +730,7 @@ var createFactReader = (ts, ast) => {
564
730
  endpoints: [],
565
731
  internals: [],
566
732
  adaptors: [],
733
+ adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
567
734
  stores: []
568
735
  };
569
736
  for (const statement of source.statements) {
@@ -588,16 +755,19 @@ var createFactReader = (ts, ast) => {
588
755
  ...name,
589
756
  model: moduleRefOf(call.arguments[0], "db"),
590
757
  methods: methodsOf(source, statement),
591
- plugs: plugsOf(source, builder)
758
+ plugs: plugsOf(source, builder),
759
+ fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
592
760
  });
593
- else if (callee === "slice")
761
+ else if (callee === "slice") {
762
+ const model = moduleRefOf(call.arguments[0], "srv");
763
+ const member = model ?? name.name;
594
764
  facts.slices.push({
595
765
  ...name,
596
- model: moduleRefOf(call.arguments[0], "srv"),
597
- guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
598
- keys: sliceKeysOf(source, builder)
766
+ model,
767
+ guards: guardsIn(source, call.arguments.find(ts.isObjectLiteralExpression), madeInPlace, member),
768
+ keys: sliceKeysOf(source, builder, madeInPlace, member)
599
769
  });
600
- else if (callee === "internal")
770
+ } else if (callee === "internal")
601
771
  facts.internals.push({
602
772
  ...name,
603
773
  model: moduleRefOf(call.arguments[0], "srv"),
@@ -612,13 +782,14 @@ var createFactReader = (ts, ast) => {
612
782
  plugs: plugsOf(source, builder),
613
783
  doc: docOf(source, statement)
614
784
  });
615
- } else if (callee === "endpoint")
785
+ } else if (callee === "endpoint") {
786
+ const model = moduleRefOf(call.arguments[0], "srv");
616
787
  facts.endpoints.push({
617
788
  ...name,
618
- model: moduleRefOf(call.arguments[0], "srv"),
619
- keys: endpointKeysOf(source, builder)
789
+ model,
790
+ keys: endpointKeysOf(source, builder, madeInPlace, model ?? name.name)
620
791
  });
621
- else if (callee === "store")
792
+ } else if (callee === "store")
622
793
  facts.stores.push({
623
794
  ...name,
624
795
  model: moduleRefOf(call.arguments[0], "sig"),
@@ -626,10 +797,439 @@ var createFactReader = (ts, ast) => {
626
797
  actions: methodsOf(source, statement)
627
798
  });
628
799
  }
629
- return facts;
630
- };
631
- return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
632
- };
800
+ return { ...facts, ...guardsOf(source, imports, madeInPlace) };
801
+ };
802
+ return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
803
+ };
804
+
805
+ // pkgs/@akanjs/language/guardResolver.ts
806
+ var frameworkOwner = "akanjs";
807
+ var frameworkPattern = /^akanjs(?:\/|$)/;
808
+ var aliasPattern = /^@(apps|libs)\/([^/]+)/;
809
+ var maxMapDepth = 4;
810
+ var starExportPattern = /export\s*\*\s*from\s*["']([^"']+)["']/g;
811
+ var declaringPatternOf = (name, isAnyBinding) => {
812
+ const escaped = escapeRegExp(name);
813
+ const braced = "\\{(?:[^}]*?[\\s,])?";
814
+ const leads = [
815
+ "function\\s*\\*?\\s*",
816
+ "(?:const|let|var)\\s+",
817
+ `export\\s*${braced}`,
818
+ ...isAnyBinding ? ["class\\s+", `(?:const|let|var)\\s*${braced}`] : []
819
+ ];
820
+ return new RegExp(`(?:${leads.join("|")})${escaped}(?![\\w$])|,\\s*${escaped}\\s*(?::[^=;,]+)?=(?![=>])`);
821
+ };
822
+
823
+ class GuardResolver {
824
+ #source;
825
+ #routes;
826
+ #guards = new Map;
827
+ #madeInPlace = new Map;
828
+ #mapEntries = new Map;
829
+ #reexports = new Map;
830
+ #declarations = new Map;
831
+ #starExports = new Map;
832
+ #ownerFiles = null;
833
+ #modules = null;
834
+ constructor(source, routes) {
835
+ this.#source = source;
836
+ this.#routes = routes;
837
+ }
838
+ find(local, site) {
839
+ return this.#findGuard(local, site, 0);
840
+ }
841
+ madeInPlace(fileName, name) {
842
+ return this.#madeInPlace.get(`${fileName}
843
+ ${name}`);
844
+ }
845
+ declare(facts) {
846
+ const factories = new Map;
847
+ const declaredFactories = [];
848
+ for (const file of facts)
849
+ for (const fact of file.guardFactories) {
850
+ const factory = {
851
+ fact,
852
+ owner: file.owner,
853
+ site: file,
854
+ made: [],
855
+ isReal: fact.direct,
856
+ scope: fact.scope,
857
+ targets: []
858
+ };
859
+ factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
860
+ declaredFactories.push(factory);
861
+ }
862
+ for (const factory of declaredFactories)
863
+ factory.targets = factory.fact.via.map((callee) => this.#factoryAt(factories, callee, factory.site)).filter((target) => target !== undefined);
864
+ for (let isGrowing = true;isGrowing; ) {
865
+ isGrowing = false;
866
+ for (const factory of declaredFactories) {
867
+ const target = factory.targets.find((other) => other.isReal);
868
+ if (factory.isReal || !target)
869
+ continue;
870
+ factory.isReal = true;
871
+ factory.scope ??= target.scope;
872
+ isGrowing = true;
873
+ }
874
+ }
875
+ const partsOf = (factory, seen = new Set) => {
876
+ if (seen.has(factory))
877
+ return [];
878
+ seen.add(factory);
879
+ return [
880
+ { fileName: factory.site.fileName, extent: factory.fact.extent },
881
+ ...factory.targets.filter((target) => target.isReal).flatMap((target) => partsOf(target, seen))
882
+ ];
883
+ };
884
+ const declare = (file, made, scope, parts) => {
885
+ const { name, span, extent, doc } = made;
886
+ const label = file.guardLabels[name];
887
+ const own = { fileName: file.fileName, extent };
888
+ const guard = { name, owner: file.owner, span, scope, doc, parts: [own, ...parts], file: null, label };
889
+ if (name.includes("@"))
890
+ this.#madeInPlace.set(`${file.fileName}
891
+ ${name}`, guard);
892
+ else
893
+ this.#guards.set(name, [...this.#guards.get(name) ?? [], guard]);
894
+ return guard;
895
+ };
896
+ const extending = [];
897
+ for (const file of facts) {
898
+ for (const made of file.guards) {
899
+ const factory = made.factory ? factories.get(made.factory)?.find((other) => other.site.fileName === file.fileName) : undefined;
900
+ if (!made.factory)
901
+ declare(file, made, made.scope, []);
902
+ else if (factory?.isReal)
903
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
904
+ }
905
+ for (const made of file.guardCalls) {
906
+ if (made.kind === "extends") {
907
+ extending.push([file, made]);
908
+ continue;
909
+ }
910
+ const factory = this.#factoryAt(factories, made.callee, file);
911
+ if (factory?.isReal)
912
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
913
+ }
914
+ }
915
+ for (let isGrowing = true;isGrowing; ) {
916
+ isGrowing = false;
917
+ for (const [idx, entry] of extending.entries()) {
918
+ const [file, made] = entry;
919
+ const found = this.#findGuard(made.callee, file, 0, true);
920
+ if (!("guard" in found))
921
+ continue;
922
+ declare(file, made, made.scope ?? found.guard.scope, found.guard.parts);
923
+ extending.splice(idx, 1);
924
+ isGrowing = true;
925
+ break;
926
+ }
927
+ }
928
+ for (const { fact, made, isReal } of declaredFactories)
929
+ if (isReal && made.every((guard) => !guard.doc))
930
+ for (const guard of made)
931
+ guard.doc = fact.doc;
932
+ for (const list of this.#guards.values())
933
+ for (const guard of list)
934
+ if (list.some((other) => other !== guard && other.owner === guard.owner))
935
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
936
+ for (const guard of this.#madeInPlace.values())
937
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
938
+ return [...[...this.#guards.values()].flat(), ...this.#madeInPlace.values()];
939
+ }
940
+ #factoryAt(factories, callee, site, depth = 0) {
941
+ const [head = callee, member] = callee.split(".");
942
+ const imported = site.imports.find((item) => item.name === head);
943
+ if (!imported)
944
+ return member ? undefined : factories.get(callee)?.find((other) => other.site.fileName === site.fileName);
945
+ if (member && imported.imported !== "*")
946
+ return;
947
+ const from = this.ownerOfSpecifier(site.fileName, imported.from);
948
+ if (from === undefined || from === frameworkOwner)
949
+ return;
950
+ const name = member ?? (imported.imported === "default" || imported.imported === "*" ? head : imported.imported);
951
+ const candidates = factories.get(name) ?? [];
952
+ const near = this.pathOfSpecifier(site.fileName, imported.from);
953
+ const own = this.#nearest(candidates.filter((other) => other.owner === from), ({ site }) => site.fileName, near);
954
+ if (own)
955
+ return own;
956
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
957
+ if (reexport)
958
+ return this.#factoryAt(factories, reexport.local, reexport.site, depth + 1);
959
+ return from && near ? this.#throughStar(candidates, from, name, near) : undefined;
960
+ }
961
+ #findGuard(local, site, depth, isBase = false) {
962
+ const segments = local.split(".");
963
+ const head = segments[0] ?? local;
964
+ const imported = site.imports.find((item) => item.name === head);
965
+ const from = imported ? this.ownerOfSpecifier(site.fileName, imported.from) : undefined;
966
+ if (imported && from === undefined)
967
+ return { external: true };
968
+ const near = imported ? this.pathOfSpecifier(site.fileName, imported.from) : this.#source.relative(site.fileName).replace(/\.tsx?$/, "");
969
+ const named = imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : null;
970
+ const path = imported?.imported === "*" ? segments.slice(1) : [named ?? head, ...segments.slice(1)];
971
+ const holder = imported ? from ?? null : site.owner;
972
+ const [objectName, member] = path;
973
+ if (path.length === 2 && objectName && member && holder !== frameworkOwner && depth < maxMapDepth) {
974
+ const made = this.#nearest((this.#guards.get(`${objectName}.${member}`) ?? []).filter((guard) => guard.owner === holder), ({ span }) => span.fileName, near);
975
+ if (made)
976
+ return { guard: made };
977
+ for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
978
+ const found = this.#findGuard(entry.name, entry.site, depth + 1, isBase);
979
+ if ("guard" in found)
980
+ return found;
981
+ }
982
+ }
983
+ const name = path[path.length - 1] ?? head;
984
+ if (from === frameworkOwner)
985
+ return { name, from };
986
+ const declared = this.#guards.get(name) ?? [];
987
+ const own = this.#nearest(declared.filter((other) => other.owner === (imported ? from : site.owner)), ({ span }) => span.fileName, near);
988
+ if (own)
989
+ return { guard: own };
990
+ if (!imported) {
991
+ const visible = isBase ? undefined : declared.find((other) => this.visible(other.owner, site.owner));
992
+ return visible ? { guard: visible } : { name, from };
993
+ }
994
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
995
+ if (reexport)
996
+ return this.#findGuard(reexport.local, reexport.site, depth + 1, isBase);
997
+ const reached = from && near ? this.#throughStar(declared, from, name, near) : undefined;
998
+ return reached ? { guard: reached } : { name, from };
999
+ }
1000
+ reexportOf(owner, name, near) {
1001
+ const key = `${owner}
1002
+ ${name}
1003
+ ${near ?? ""}`;
1004
+ const cached = this.#reexports.get(key);
1005
+ if (cached !== undefined)
1006
+ return cached;
1007
+ let found = null;
1008
+ const exporting = new RegExp(`export\\s*\\{(?:[^}]*?[\\s,])?${escapeRegExp(name)}(?![\\w$])`);
1009
+ for (const fileName of this.#filesOf(owner, name, near)) {
1010
+ const text = this.#source.readText(fileName);
1011
+ if (text === undefined || !exporting.test(text))
1012
+ continue;
1013
+ const reexport = this.#reexportIn(this.#source.parse(fileName, text), name);
1014
+ if (!reexport)
1015
+ continue;
1016
+ const site = { fileName, owner, imports: [] };
1017
+ found = reexport.from ? {
1018
+ local: reexport.name,
1019
+ site: { ...site, imports: [{ ...reexport, imported: reexport.name, from: reexport.from }] }
1020
+ } : { local: reexport.name, site };
1021
+ break;
1022
+ }
1023
+ this.#reexports.set(key, found);
1024
+ return found;
1025
+ }
1026
+ #reexportIn(source, name) {
1027
+ const { ts, reader } = this.#source;
1028
+ for (const statement of source.statements) {
1029
+ if (!ts.isExportDeclaration(statement) || statement.isTypeOnly)
1030
+ continue;
1031
+ const { exportClause, moduleSpecifier } = statement;
1032
+ if (!exportClause || !ts.isNamedExports(exportClause))
1033
+ continue;
1034
+ const element = exportClause.elements.find((item) => !item.isTypeOnly && item.name.text === name);
1035
+ if (!element)
1036
+ continue;
1037
+ const original = (element.propertyName ?? element.name).text;
1038
+ if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier))
1039
+ return { name: original, from: moduleSpecifier.text };
1040
+ const imported = reader.importsIn(source).find((item) => item.name === original);
1041
+ if (!imported)
1042
+ return { name: original, from: null };
1043
+ return imported.imported === "default" || imported.imported === "*" ? null : { name: imported.imported, from: imported.from };
1044
+ }
1045
+ return null;
1046
+ }
1047
+ #filesOf(owner, name, near) {
1048
+ const isNear = (fileName) => {
1049
+ const relative = this.#source.relative(fileName);
1050
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1051
+ };
1052
+ const candidates = this.filesIn(owner).filter(([, words]) => words.includes(`
1053
+ ${name}
1054
+ `)).map(([fileName]) => fileName);
1055
+ return [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))];
1056
+ }
1057
+ #mapEntriesOf(objectName, member, fileName, owner) {
1058
+ const key = `${objectName}
1059
+ ${member}
1060
+ ${fileName ?? ""}
1061
+ ${owner ?? ""}`;
1062
+ const cached = this.#mapEntries.get(key);
1063
+ if (cached)
1064
+ return cached;
1065
+ const { ts, reader } = this.#source;
1066
+ const entries = [];
1067
+ const fileNames = [
1068
+ ...fileName ? [fileName] : [],
1069
+ ...(owner === null ? [] : this.filesIn(owner)).filter(([other, words]) => other !== fileName && words.includes(`
1070
+ ${objectName}
1071
+ `) && words.includes(`
1072
+ ${member}
1073
+ `)).map(([other]) => other)
1074
+ ];
1075
+ for (const holder of fileNames) {
1076
+ const text = this.#source.readText(holder);
1077
+ if (text === undefined)
1078
+ continue;
1079
+ const source = this.#source.parse(holder, text);
1080
+ const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
1081
+ for (const statement of source.statements) {
1082
+ if (!ts.isVariableStatement(statement))
1083
+ continue;
1084
+ const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
1085
+ const object = map && reader.unwrap(map);
1086
+ if (!object || !ts.isObjectLiteralExpression(object))
1087
+ continue;
1088
+ for (const property of object.properties) {
1089
+ if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
1090
+ entries.push({ name: member, site });
1091
+ if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
1092
+ continue;
1093
+ const value = reader.unwrap(property.initializer);
1094
+ if (ts.isIdentifier(value))
1095
+ entries.push({ name: value.text, site });
1096
+ else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
1097
+ entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
1098
+ }
1099
+ }
1100
+ }
1101
+ this.#mapEntries.set(key, entries);
1102
+ return entries;
1103
+ }
1104
+ declarationOf(owner, name, near, depth = 0) {
1105
+ const key = `${owner}
1106
+ ${name}
1107
+ ${near ?? ""}`;
1108
+ const cached = this.#declarations.get(key);
1109
+ if (cached !== undefined)
1110
+ return cached;
1111
+ this.#declarations.set(key, null);
1112
+ const declaring = declaringPatternOf(name, false);
1113
+ let found = null;
1114
+ for (const fileName of this.#filesOf(owner, name, near)) {
1115
+ const text = this.#source.readText(fileName);
1116
+ if (text === undefined || !declaring.test(text))
1117
+ continue;
1118
+ const source = this.#source.parse(fileName, text);
1119
+ const declared = this.#routes.declarationIn(source, name);
1120
+ if (declared) {
1121
+ found = { source, ...declared, name };
1122
+ break;
1123
+ }
1124
+ const reexport = this.#reexportIn(source, name);
1125
+ if (!reexport)
1126
+ continue;
1127
+ if (!reexport.from) {
1128
+ const local = this.#routes.declarationIn(source, reexport.name);
1129
+ found = local ? { source, ...local, name: reexport.name } : null;
1130
+ break;
1131
+ }
1132
+ const next = this.ownerOfSpecifier(fileName, reexport.from);
1133
+ if (next && next !== frameworkOwner && depth < maxMapDepth)
1134
+ found = this.declarationOf(next, reexport.name, this.pathOfSpecifier(fileName, reexport.from), depth + 1);
1135
+ break;
1136
+ }
1137
+ this.#declarations.set(key, found);
1138
+ return found;
1139
+ }
1140
+ #throughStar(declared, owner, name, near) {
1141
+ if (!declared.length)
1142
+ return;
1143
+ const { owners, isOpaque } = this.#starExportsOf(owner, near);
1144
+ const reached = declared.filter((other) => other.owner !== null && owners.includes(other.owner));
1145
+ return reached.length === 1 && !isOpaque && !this.declares(owner, name) ? reached[0] : undefined;
1146
+ }
1147
+ #starExportsOf(owner, near) {
1148
+ const cached = this.#starExports.get(near);
1149
+ if (cached)
1150
+ return cached;
1151
+ const found = { owners: [], isOpaque: false };
1152
+ this.#starExports.set(near, found);
1153
+ const paths = [near];
1154
+ for (let idx = 0;idx < paths.length && !found.isOpaque; idx++)
1155
+ for (const fileName of this.#modulesAt(paths[idx] ?? "")) {
1156
+ for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? []) {
1157
+ const target = this.ownerOfSpecifier(fileName, specifier);
1158
+ const path = this.pathOfSpecifier(fileName, specifier);
1159
+ if (target === undefined || target === frameworkOwner || path === null)
1160
+ found.isOpaque = true;
1161
+ else if (!paths.includes(path))
1162
+ paths.push(path);
1163
+ if (target && target !== frameworkOwner && target !== owner && !found.owners.includes(target))
1164
+ found.owners.push(target);
1165
+ }
1166
+ }
1167
+ return found;
1168
+ }
1169
+ #modulesAt(path) {
1170
+ if (!this.#modules) {
1171
+ this.#modules = new Map;
1172
+ for (const [fileName] of this.#source.files) {
1173
+ const module2 = this.#source.relative(fileName).replace(/\.tsx?$/, "");
1174
+ for (const key of module2.endsWith("/index") ? [module2, module2.slice(0, -6)] : [module2])
1175
+ this.#modules.set(key, [...this.#modules.get(key) ?? [], fileName]);
1176
+ }
1177
+ }
1178
+ return this.#modules.get(path) ?? [];
1179
+ }
1180
+ declares(owner, name) {
1181
+ const declaring = declaringPatternOf(name, true);
1182
+ return this.#filesOf(owner, name, null).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
1183
+ }
1184
+ filesIn(owner) {
1185
+ if (!this.#ownerFiles) {
1186
+ this.#ownerFiles = new Map;
1187
+ for (const entry of this.#source.files) {
1188
+ const fileOwner = testFilePattern.test(entry[0]) ? null : this.#source.ownerOf(entry[0]);
1189
+ const files = fileOwner === null ? null : this.#ownerFiles.get(fileOwner);
1190
+ if (files)
1191
+ files.push(entry);
1192
+ else if (fileOwner !== null)
1193
+ this.#ownerFiles.set(fileOwner, [entry]);
1194
+ }
1195
+ }
1196
+ return this.#ownerFiles.get(owner) ?? [];
1197
+ }
1198
+ #nearest(items, fileOf, near) {
1199
+ const isNear = (fileName) => {
1200
+ const relative = this.#source.relative(fileName);
1201
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1202
+ };
1203
+ return items.find((item) => isNear(fileOf(item))) ?? items[0];
1204
+ }
1205
+ inOwner(fileName, owner) {
1206
+ const relative = this.#source.relative(fileName);
1207
+ return owner && relative.startsWith(`${owner}/`) ? relative.slice(owner.length + 1) : relative;
1208
+ }
1209
+ ownerOfSpecifier(fileName, specifier) {
1210
+ if (frameworkPattern.test(specifier))
1211
+ return frameworkOwner;
1212
+ const path = this.pathOfSpecifier(fileName, specifier);
1213
+ return path === null ? undefined : ownerOf(path) ?? undefined;
1214
+ }
1215
+ pathOfSpecifier(fileName, specifier) {
1216
+ const alias = specifier.match(aliasPattern);
1217
+ if (alias)
1218
+ return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1219
+ if (!specifier.startsWith("."))
1220
+ return null;
1221
+ const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1222
+ for (const segment of specifier.split("/"))
1223
+ if (segment === "..")
1224
+ segments.pop();
1225
+ else if (segment !== ".")
1226
+ segments.push(segment);
1227
+ return segments.join("/");
1228
+ }
1229
+ visible(target, from) {
1230
+ return !from || !target || target === from || target.startsWith("libs/");
1231
+ }
1232
+ }
633
1233
 
634
1234
  // pkgs/akanjs/common/trimSlashes.ts
635
1235
  var trimTrailingSlashes = (value) => {
@@ -778,24 +1378,23 @@ rootLayout
778
1378
  }
779
1379
  declarationIn(source, name) {
780
1380
  const ts = this.#ts;
1381
+ const overloads = source.statements.filter((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === name);
1382
+ const [first] = overloads;
1383
+ const last = overloads.at(-1);
1384
+ if (first?.name && last)
1385
+ return { statement: first, declared: first.name, extent: { start: first.getStart(source), end: last.getEnd() } };
781
1386
  for (const statement of source.statements) {
782
- if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
783
- return { statement, declared: statement.name, initializer: null };
784
1387
  if (!ts.isVariableStatement(statement))
785
1388
  continue;
786
1389
  const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
787
1390
  if (declaration)
788
- return { statement, declared: declaration.name, initializer: declaration.initializer ?? null };
1391
+ return { statement, declared: declaration.name, extent: this.#reader.extentOf(source, statement) };
789
1392
  }
790
1393
  return null;
791
1394
  }
792
1395
  #guardOf(source, exported) {
793
1396
  const ts = this.#ts;
794
1397
  let current = ts.isExportAssignment(exported) ? this.#reader.unwrap(exported.expression) : null;
795
- if (current && ts.isIdentifier(current)) {
796
- const initializer = this.declarationIn(source, current.text)?.initializer;
797
- current = initializer ? this.#reader.unwrap(initializer) : null;
798
- }
799
1398
  while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
800
1399
  const { name, expression } = current.expression;
801
1400
  const [stage] = current.arguments;
@@ -807,9 +1406,20 @@ rootLayout
807
1406
  }
808
1407
  #callsIn(source, stage) {
809
1408
  const ts = this.#ts;
810
- const imported = new Set(this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from)).map(({ name }) => name));
1409
+ const imports = this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from));
1410
+ const imported = new Set(imports.filter(({ imported }) => imported !== "*").map(({ name }) => name));
1411
+ const namespaces = new Set(imports.filter(({ imported }) => imported === "*").map(({ name }) => name));
811
1412
  const calls = new Map;
812
- const callOf = (name) => {
1413
+ const callOf = (callee, shadowed) => {
1414
+ if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text) && !shadowed.has(callee.expression.text)) {
1415
+ const name = `${callee.expression.text}.${callee.name.text}`;
1416
+ if (!calls.has(name))
1417
+ calls.set(name, { name, local: null });
1418
+ return true;
1419
+ }
1420
+ if (!ts.isIdentifier(callee) || shadowed.has(callee.text))
1421
+ return false;
1422
+ const name = callee.text;
813
1423
  if (calls.has(name))
814
1424
  return true;
815
1425
  const declared = this.declarationIn(source, name);
@@ -819,23 +1429,71 @@ rootLayout
819
1429
  name,
820
1430
  local: declared && {
821
1431
  span: this.#reader.spanOf(source, declared.declared),
822
- extent: this.#reader.extentOf(source, declared.statement),
1432
+ extent: declared.extent,
823
1433
  doc: this.#reader.docOf(source, declared.statement)
824
1434
  }
825
1435
  });
826
1436
  return true;
827
1437
  };
828
- const visit = (node) => {
829
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && callOf(node.expression.text))
1438
+ const visit = (node, outer) => {
1439
+ const shadowed = this.#scopeOf(node, outer);
1440
+ if (ts.isCallExpression(node) && callOf(node.expression, shadowed))
830
1441
  return;
831
- ts.forEachChild(node, visit);
1442
+ ts.forEachChild(node, (child) => visit(child, shadowed));
832
1443
  };
833
1444
  if (ts.isIdentifier(stage))
834
- callOf(stage.text);
1445
+ callOf(stage, new Set);
835
1446
  else
836
- visit(stage);
1447
+ visit(stage, new Set);
837
1448
  return [...calls.values()];
838
1449
  }
1450
+ #scopeOf(node, outer) {
1451
+ const ts = this.#ts;
1452
+ const names = [];
1453
+ const bind = (name) => {
1454
+ if (ts.isIdentifier(name))
1455
+ names.push(name.text);
1456
+ else
1457
+ for (const element of name.elements)
1458
+ if (!ts.isOmittedExpression(element))
1459
+ bind(element.name);
1460
+ };
1461
+ const declare = (statement) => {
1462
+ if (ts.isVariableStatement(statement))
1463
+ for (const declaration of statement.declarationList.declarations)
1464
+ bind(declaration.name);
1465
+ else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
1466
+ names.push(statement.name.text);
1467
+ };
1468
+ const hoist = (child) => {
1469
+ if (ts.isFunctionLike(child) || ts.isClassLike(child))
1470
+ return;
1471
+ if (ts.isVariableDeclarationList(child) && !(child.flags & ts.NodeFlags.BlockScoped))
1472
+ for (const declaration of child.declarations)
1473
+ bind(declaration.name);
1474
+ ts.forEachChild(child, hoist);
1475
+ };
1476
+ if (ts.isFunctionLike(node)) {
1477
+ for (const parameter of node.parameters)
1478
+ bind(parameter.name);
1479
+ if (ts.isFunctionExpression(node) && node.name)
1480
+ names.push(node.name.text);
1481
+ if ("body" in node && node.body)
1482
+ ts.forEachChild(node.body, hoist);
1483
+ } else if (ts.isClassExpression(node) && node.name)
1484
+ names.push(node.name.text);
1485
+ else if (ts.isBlock(node))
1486
+ node.statements.forEach(declare);
1487
+ else if (ts.isCaseBlock(node))
1488
+ for (const clause of node.clauses)
1489
+ clause.statements.forEach(declare);
1490
+ else if (ts.isCatchClause(node) && node.variableDeclaration)
1491
+ bind(node.variableDeclaration.name);
1492
+ else if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer))
1493
+ for (const declaration of node.initializer.declarations)
1494
+ bind(declaration.name);
1495
+ return names.length ? new Set([...outer, ...names]) : outer;
1496
+ }
839
1497
  }
840
1498
 
841
1499
  // pkgs/@akanjs/language/akanGraph.ts
@@ -851,10 +1509,6 @@ var builtinGuards = {
851
1509
  Public: { scope: "account", doc: "Passes every caller, signed in or not." },
852
1510
  None: { scope: "account", doc: "Refuses every caller." }
853
1511
  };
854
- var frameworkOwner = "akanjs";
855
- var frameworkPattern = /^akanjs(?:\/|$)/;
856
- var aliasPattern = /^@(apps|libs)\/([^/]+)/;
857
- var maxMapDepth = 4;
858
1512
 
859
1513
  class GraphBuilder {
860
1514
  #source;
@@ -862,16 +1516,16 @@ class GraphBuilder {
862
1516
  #edges = new Map;
863
1517
  #contexts = new Map;
864
1518
  #members = new Map;
865
- #guards = new Map;
866
- #mapEntries = new Map;
867
1519
  #routeFiles = new Map;
868
- #declarations = new Map;
869
1520
  #routes;
1521
+ #resolver;
870
1522
  #adaptors = new Map;
871
1523
  #plugs = new Map;
1524
+ #serviceFields = new Set;
872
1525
  constructor(source) {
873
1526
  this.#source = source;
874
1527
  this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
1528
+ this.#resolver = new GuardResolver(source, this.#routes);
875
1529
  }
876
1530
  build() {
877
1531
  const { facts } = this.#source;
@@ -883,17 +1537,28 @@ class GraphBuilder {
883
1537
  ...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
884
1538
  ];
885
1539
  const typed = [];
1540
+ const adaptorCounts = new Map;
886
1541
  for (const file of facts)
887
1542
  for (const fact of file.adaptors) {
888
- const declared = { fact, owner: file.owner, fileName: file.fileName };
889
- this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
890
- this.#adaptorNodeOf(declared);
1543
+ const key = this.#key("adaptor", file.owner, fact.name);
1544
+ adaptorCounts.set(key, (adaptorCounts.get(key) ?? 0) + 1);
891
1545
  }
1546
+ const declaredAdaptors = facts.flatMap((file) => file.adaptors.map((fact) => {
1547
+ const key = this.#key("adaptor", file.owner, fact.name);
1548
+ const id = (adaptorCounts.get(key) ?? 0) > 1 ? this.#key("adaptor", file.owner, `${this.#resolver.inOwner(file.fileName, file.owner)}#${fact.name}`) : key;
1549
+ const declared = { fact, owner: file.owner, fileName: file.fileName, id };
1550
+ this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
1551
+ this.#adaptorNodeOf(declared);
1552
+ return { declared, file };
1553
+ }));
1554
+ for (const { declared, file } of declaredAdaptors)
1555
+ for (const plug of declared.fact.plugs)
1556
+ this.#addEdge(declared.id, this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
892
1557
  for (const file of facts)
893
- for (const fact of file.adaptors)
894
- for (const plug of fact.plugs)
895
- this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
896
- this.#declareGuards(facts);
1558
+ for (const binding of file.adaptorBindings)
1559
+ this.#addEdge(this.#adaptorIdOf(binding.role, file), this.#adaptorIdOf(binding.adaptor, file), "binds", file.owner, this.#location(binding.span));
1560
+ for (const guard of this.#resolver.declare(facts))
1561
+ this.#guardNodeOf(guard);
897
1562
  for (const file of facts) {
898
1563
  const { owner } = file;
899
1564
  const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
@@ -924,10 +1589,18 @@ class GraphBuilder {
924
1589
  }
925
1590
  for (const method of service.methods)
926
1591
  this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
927
- for (const plug of service.plugs)
928
- this.#plugs.set(`${owner ?? ""}
1592
+ for (const plug of service.plugs) {
1593
+ const key = `${refName}
1594
+ ${plug.name}`;
1595
+ this.#plugs.set(key, [
1596
+ ...this.#plugs.get(key) ?? [],
1597
+ { owner, target: this.#adaptorIdOf(plug.target, file) }
1598
+ ]);
1599
+ }
1600
+ for (const field of service.fields)
1601
+ this.#serviceFields.add(`${owner ?? ""}
929
1602
  ${refName}
930
- ${plug.name}`, this.#adaptorIdOf(plug.target, file));
1603
+ ${field}`);
931
1604
  }
932
1605
  for (const filter of file.filters) {
933
1606
  if (!filter.name.endsWith("Filter"))
@@ -937,31 +1610,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
937
1610
  for (const query of filter.queries)
938
1611
  this.#digest(this.#addNode(this.#key("query", owner, refName, query.name), "query", owner, refName, query.name, query.span), this.#source.readText(file.fileName), query.extent);
939
1612
  }
940
- for (const slice of file.slices) {
941
- const refName = slice.model;
942
- if (!refName)
943
- continue;
944
- const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards);
945
- for (const guard of slice.guards)
946
- this.#guard(fallback, guard, file);
947
- for (const key of slice.keys) {
948
- const detail = [...key.guards, ...key.live ? ["live"] : []];
949
- const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
950
- for (const guard of key.guards)
951
- this.#guard(node, guard, file);
952
- }
953
- }
954
- for (const endpoint of file.endpoints) {
955
- const refName = endpoint.model;
956
- if (!refName)
957
- continue;
958
- for (const key of endpoint.keys) {
959
- const detail = [...key.verb ? [key.verb] : [], ...key.guards];
960
- const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
961
- for (const guard of key.guards)
962
- this.#guard(node, guard, file);
963
- }
964
- }
1613
+ this.#addGuarded(file);
965
1614
  for (const internal of file.internals)
966
1615
  for (const key of internal.keys)
967
1616
  if (internal.model && key.type !== "resolveField")
@@ -977,7 +1626,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
977
1626
  types.set(node.label, [...types.get(node.label) ?? [], node]);
978
1627
  for (const node of typed)
979
1628
  for (const field of node.fields ?? []) {
980
- const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#visible(type.owner, node.owner));
1629
+ const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#resolver.visible(type.owner, node.owner));
981
1630
  field.ref = (candidates.find((type) => type.owner === node.owner) ?? candidates[0])?.id ?? null;
982
1631
  if (field.ref)
983
1632
  this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
@@ -1059,61 +1708,52 @@ ${label ?? ""}`;
1059
1708
  else
1060
1709
  this.#edges.set(key, { from, to, kind, label, count: 1, at });
1061
1710
  }
1062
- #guard(node, guard, file) {
1063
- const [role, local] = guard.includes(" ") ? guard.split(" ") : [null, guard];
1064
- if (!local || local === "?")
1065
- return;
1066
- this.#addEdge(node.id, this.#guardIdOf(local, file), "guard", role, node.at);
1067
- }
1068
- #declareGuards(facts) {
1069
- const factories = new Map;
1070
- for (const file of facts)
1071
- for (const fact of file.guardFactories) {
1072
- const factory = { fact, owner: file.owner, fileName: file.fileName, made: [] };
1073
- factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
1074
- }
1075
- const sameFile = (factory, name) => name ? factories.get(name)?.find((other) => other.fileName === factory.fileName) : undefined;
1076
- const declare = (guard, factory) => {
1077
- this.#guards.set(guard.name, [...this.#guards.get(guard.name) ?? [], guard]);
1078
- factory?.made.push(guard);
1079
- };
1080
- const madeBy = (factory, fileName, extent) => {
1081
- const parts = [{ fileName, extent }];
1082
- const seen = new Set;
1083
- for (let current = factory;current && !seen.has(current); current = sameFile(current, current.fact.via)) {
1084
- seen.add(current);
1085
- parts.push({ fileName: current.fileName, extent: current.fact.extent });
1086
- }
1087
- return parts;
1088
- };
1089
- for (const file of facts) {
1090
- const { owner, fileName } = file;
1091
- for (const { name, span, extent, scope, doc, factory: callee } of file.guards) {
1092
- const factory = callee ? factories.get(callee)?.find((other) => other.fileName === fileName) : undefined;
1093
- declare({ name, owner, span, scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
1711
+ #addGuarded(file) {
1712
+ const { owner } = file;
1713
+ for (const slice of file.slices) {
1714
+ const refName = slice.model;
1715
+ if (!refName)
1716
+ continue;
1717
+ const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards.map((guard) => guardLabelOf(guard, file.guardLabels)));
1718
+ for (const guard of slice.guards)
1719
+ this.#guard(fallback, guard, file);
1720
+ for (const key of slice.keys) {
1721
+ const detail = [
1722
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels)),
1723
+ ...key.live ? ["live"] : []
1724
+ ];
1725
+ const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1726
+ for (const guard of key.guards)
1727
+ this.#guard(node, guard, file);
1094
1728
  }
1095
- for (const { name, span, extent, doc, callee } of file.guardCalls) {
1096
- const imported = file.imports.find((item) => item.name === callee);
1097
- if (!imported)
1098
- continue;
1099
- const from = this.#ownerOfSpecifier(fileName, imported.from);
1100
- const candidates = factories.get(imported.imported === "default" || imported.imported === "*" ? callee : imported.imported) ?? [];
1101
- const factory = candidates.find((other) => other.owner === from) ?? this.#soleVisible(candidates, from ?? owner);
1102
- if (factory)
1103
- declare({ name, owner, span, scope: factory.fact.scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
1729
+ }
1730
+ for (const endpoint of file.endpoints) {
1731
+ const refName = endpoint.model;
1732
+ if (!refName)
1733
+ continue;
1734
+ for (const key of endpoint.keys) {
1735
+ const detail = [
1736
+ ...key.verb ? [key.verb] : [],
1737
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels))
1738
+ ];
1739
+ const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1740
+ for (const guard of key.guards)
1741
+ this.#guard(node, guard, file);
1104
1742
  }
1105
1743
  }
1106
- for (const list of factories.values())
1107
- for (const { fact, made } of list)
1108
- if (made.every((guard) => !guard.doc))
1109
- for (const guard of made)
1110
- guard.doc = fact.doc;
1111
- for (const list of this.#guards.values())
1112
- for (const guard of list)
1113
- this.#guardNodeOf(guard);
1744
+ }
1745
+ #guard(node, guard, file) {
1746
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
1747
+ if (!local || local === "?")
1748
+ return;
1749
+ const id = local.includes("@") ? this.#madeInPlaceIdOf(local, file) : this.#guardIdOf(local, file);
1750
+ if (id)
1751
+ this.#addEdge(node.id, id, "guard", role, node.at);
1114
1752
  }
1115
1753
  #guardIdOf(local, site) {
1116
- const found = this.#findGuard(local, site, 0);
1754
+ const found = this.#resolver.find(local, site);
1755
+ if ("external" in found)
1756
+ return null;
1117
1757
  if ("guard" in found)
1118
1758
  return this.#guardNodeOf(found.guard);
1119
1759
  const { name, from } = found;
@@ -1126,99 +1766,18 @@ ${label ?? ""}`;
1126
1766
  node.doc = builtin.doc;
1127
1767
  return node.id;
1128
1768
  }
1129
- #findGuard(local, site, depth) {
1130
- const [head = local, member] = local.split(".");
1131
- const imported = site.imports.find((item) => item.name === head);
1132
- const from = imported ? this.#ownerOfSpecifier(site.fileName, imported.from) : undefined;
1133
- const holder = imported ? from : site.owner;
1134
- if (member && imported?.imported !== "*" && holder !== undefined && holder !== frameworkOwner && depth < maxMapDepth) {
1135
- const objectName = imported && imported.imported !== "default" ? imported.imported : head;
1136
- for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
1137
- const found = this.#findGuard(entry.name, entry.site, depth + 1);
1138
- if ("guard" in found)
1139
- return found;
1140
- }
1141
- }
1142
- const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1143
- const declared = this.#guards.get(name) ?? [];
1144
- const guard = from === undefined ? declared.find((other) => other.owner === site.owner) ?? declared.find((other) => this.#visible(other.owner, site.owner)) : declared.find((other) => other.owner === from) ?? (from === frameworkOwner ? undefined : this.#soleVisible(declared, from));
1145
- return guard ? { guard } : { name, from };
1146
- }
1147
- #mapEntriesOf(objectName, member, fileName, owner) {
1148
- const key = `${objectName}
1149
- ${member}
1150
- ${fileName ?? ""}
1151
- ${owner ?? ""}`;
1152
- const cached = this.#mapEntries.get(key);
1153
- if (cached)
1154
- return cached;
1155
- const { ts, reader } = this.#source;
1156
- const entries = [];
1157
- const fileNames = [
1158
- ...fileName ? [fileName] : [],
1159
- ...this.#source.files.filter(([other, words]) => other !== fileName && owner !== null && !testFilePattern.test(other) && words.includes(`
1160
- ${objectName}
1161
- `) && words.includes(`
1162
- ${member}
1163
- `) && this.#source.ownerOf(other) === owner).map(([other]) => other)
1164
- ];
1165
- for (const holder of fileNames) {
1166
- const text = this.#source.readText(holder);
1167
- if (text === undefined)
1168
- continue;
1169
- const source = this.#source.parse(holder, text);
1170
- const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
1171
- for (const statement of source.statements) {
1172
- if (!ts.isVariableStatement(statement))
1173
- continue;
1174
- const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
1175
- const object = map && reader.unwrap(map);
1176
- if (!object || !ts.isObjectLiteralExpression(object))
1177
- continue;
1178
- for (const property of object.properties) {
1179
- if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
1180
- entries.push({ name: member, site });
1181
- if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
1182
- continue;
1183
- const value = reader.unwrap(property.initializer);
1184
- if (ts.isIdentifier(value))
1185
- entries.push({ name: value.text, site });
1186
- else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
1187
- entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
1188
- }
1189
- }
1190
- }
1191
- this.#mapEntries.set(key, entries);
1192
- return entries;
1193
- }
1194
- #declarationOf(owner, name, near) {
1195
- const key = `${owner}
1196
- ${name}
1197
- ${near ?? ""}`;
1198
- const cached = this.#declarations.get(key);
1199
- if (cached !== undefined)
1200
- return cached;
1201
- const isNear = (fileName) => {
1202
- const relative = this.#source.relative(fileName);
1203
- return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1204
- };
1205
- const candidates = this.#source.files.filter(([fileName, words]) => !testFilePattern.test(fileName) && words.includes(`
1206
- ${name}
1207
- `) && this.#source.ownerOf(fileName) === owner).map(([fileName]) => fileName);
1208
- let found = null;
1209
- for (const fileName of [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))]) {
1210
- const text = this.#source.readText(fileName);
1211
- if (text === undefined)
1212
- continue;
1213
- const source = this.#source.parse(fileName, text);
1214
- const declared = this.#routes.declarationIn(source, name);
1215
- if (!declared)
1216
- continue;
1217
- found = { source, statement: declared.statement, declared: declared.declared };
1218
- break;
1219
- }
1220
- this.#declarations.set(key, found);
1221
- return found;
1769
+ #madeInPlaceIdOf(name, file) {
1770
+ const made = this.#resolver.madeInPlace(file.fileName, name);
1771
+ if (made)
1772
+ return this.#guardNodeOf(made);
1773
+ const head = name.slice(name.indexOf(":") + 1, name.indexOf("@")).split(".")[0] ?? name;
1774
+ const imported = file.imports.find((item) => item.name === head);
1775
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : file.owner;
1776
+ if (from === undefined || from === frameworkOwner)
1777
+ return null;
1778
+ const scope = `${this.#resolver.inOwner(file.fileName, file.owner)}#${name}`;
1779
+ const label = guardLabelOf(name, file.guardLabels);
1780
+ return this.#addNode(this.#key("guard", file.owner, scope), "guard", file.owner, null, name, null, [], label).id;
1222
1781
  }
1223
1782
  #addRoutes() {
1224
1783
  const routes = [];
@@ -1271,22 +1830,23 @@ ${dir}`, node);
1271
1830
  }
1272
1831
  #routeGuardIdOf({ name: local, local: declared }, fact, text) {
1273
1832
  if (declared) {
1274
- const file = this.#source.relative(fact.fileName).slice(fact.owner.length + 1);
1275
- const id = this.#key("guard", fact.owner, `${file}#${local}`);
1833
+ const id = this.#key("guard", fact.owner, `${this.#resolver.inOwner(fact.fileName, fact.owner)}#${local}`);
1276
1834
  return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
1277
1835
  }
1278
- const imported = fact.imports.find((item) => item.name === local);
1279
- if (!imported)
1836
+ const [head = local, member] = local.split(".");
1837
+ const imported = fact.imports.find((item) => item.name === head);
1838
+ if (!imported || member && imported.imported !== "*")
1280
1839
  return null;
1281
- const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
1282
- const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
1840
+ const name = member ?? (imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local);
1841
+ const from = this.#resolver.ownerOfSpecifier(fact.fileName, imported.from);
1283
1842
  if (!from || from === frameworkOwner)
1284
1843
  return null;
1285
- const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
1844
+ const found = this.#resolver.declarationOf(from, name, this.#resolver.pathOfSpecifier(fact.fileName, imported.from));
1286
1845
  if (!found)
1287
1846
  return null;
1288
1847
  const { reader } = this.#source;
1289
- return this.#routeGuardNodeOf(this.#key("guard", from, name), from, name, reader.spanOf(found.source, found.declared), reader.docOf(found.source, found.statement), found.source.text, reader.extentOf(found.source, found.statement));
1848
+ const owner = this.#source.ownerOf(found.source.fileName) ?? from;
1849
+ return this.#routeGuardNodeOf(this.#key("guard", owner, `${this.#resolver.inOwner(found.source.fileName, owner)}#${found.name}`), owner, found.name, reader.spanOf(found.source, found.declared), reader.docOf(found.source, found.statement), found.source.text, found.extent);
1290
1850
  }
1291
1851
  #routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
1292
1852
  const isNew = !this.#nodes.has(id);
@@ -1299,18 +1859,70 @@ ${dir}`, node);
1299
1859
  #adaptorIdOf(local, file) {
1300
1860
  const [head, member] = local.split(".");
1301
1861
  const imported = file.imports.find((item) => item.name === head);
1302
- const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
1303
1862
  const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1863
+ const path = imported ? this.#resolver.pathOfSpecifier(file.fileName, imported.from) : null;
1864
+ if (imported && path === null && !frameworkPattern.test(imported.from))
1865
+ return this.#packageAdaptorOf(name, imported.from);
1866
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
1304
1867
  const declared = this.#adaptors.get(name) ?? [];
1305
- const found = from === undefined ? declared.find((adaptor) => adaptor.owner === file.owner) ?? declared.find((adaptor) => this.#visible(adaptor.owner, file.owner)) : declared.find((adaptor) => adaptor.owner === from) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#visible(adaptor.owner, from)));
1868
+ const found = from === undefined ? this.#adaptorIn(declared, file.owner, path, file.fileName) ?? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, file.owner)) : this.#adaptorIn(declared, from, path, file.fileName) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#resolver.visible(adaptor.owner, from)));
1306
1869
  if (found)
1307
- return this.#adaptorNodeOf(found);
1870
+ return found.id;
1871
+ const reexported = from && from !== frameworkOwner ? this.#packageShimOf(from, name, path) : null;
1872
+ if (reexported)
1873
+ return this.#packageAdaptorOf(reexported.name, reexported.from);
1308
1874
  const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
1309
1875
  const detail = from === frameworkOwner ? [frameworkOwner] : [];
1310
1876
  return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
1311
1877
  }
1312
- #adaptorNodeOf({ fact, owner, fileName }) {
1313
- const id = this.#key("adaptor", owner, fact.name);
1878
+ #adaptorIn(declared, owner, path, fileName) {
1879
+ const own = declared.filter((adaptor) => adaptor.owner === owner);
1880
+ if (own.length < 2)
1881
+ return own.at(0);
1882
+ const pathOf = (adaptor) => this.#source.relative(adaptor.fileName).replace(/\.tsx?$/, "");
1883
+ const named = path === null ? own.find((adaptor) => adaptor.fileName === fileName) : own.find((adaptor) => pathOf(adaptor) === path || pathOf(adaptor) === `${path}/index`) ?? own.find((adaptor) => pathOf(adaptor).startsWith(`${path}/`));
1884
+ return named ?? own.at(0);
1885
+ }
1886
+ #pluggedOf({ owner, module: module2 }, field) {
1887
+ const plugs = this.#plugs.get(`${module2}
1888
+ ${field}`) ?? [];
1889
+ const own = plugs.filter((plug) => plug.owner === owner);
1890
+ if (!own.length && this.#serviceFields.has(`${owner ?? ""}
1891
+ ${module2}
1892
+ ${field}`))
1893
+ return [];
1894
+ const inherited = own.length ? own : plugs.filter((plug) => this.#resolver.visible(plug.owner, owner));
1895
+ return [...new Set(inherited.map(({ target }) => target))];
1896
+ }
1897
+ #packageAdaptorOf(name, specifier) {
1898
+ if (frameworkPattern.test(specifier))
1899
+ return this.#addNode(this.#key("adaptor", null, name), "adaptor", null, null, name, null, [frameworkOwner]).id;
1900
+ const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
1901
+ return this.#addNode(this.#key("adaptor", pkg, name), "adaptor", null, null, name, null, [pkg]).id;
1902
+ }
1903
+ #packageShimOf(owner, name, near) {
1904
+ const reexport = this.#resolver.reexportOf(owner, name, near);
1905
+ const [shim] = reexport?.site.imports ?? [];
1906
+ if (reexport && shim && this.#resolver.pathOfSpecifier(reexport.site.fileName, shim.from) === null)
1907
+ return { name: reexport.local === "default" ? name : reexport.local, from: shim.from };
1908
+ if (reexport || !near)
1909
+ return null;
1910
+ const packages = new Set;
1911
+ for (const [fileName, words] of this.#resolver.filesIn(owner)) {
1912
+ const relative = this.#source.relative(fileName);
1913
+ const isNear = relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near;
1914
+ if (!isNear || !words.includes(`
1915
+ export
1916
+ `))
1917
+ continue;
1918
+ for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? [])
1919
+ if (this.#resolver.pathOfSpecifier(fileName, specifier) === null)
1920
+ packages.add(specifier);
1921
+ }
1922
+ const [only] = packages;
1923
+ return packages.size === 1 && only && !this.#resolver.declares(owner, name) ? { name, from: only } : null;
1924
+ }
1925
+ #adaptorNodeOf({ fact, owner, fileName, id }) {
1314
1926
  const detail = fact.refName ? [fact.refName] : [];
1315
1927
  const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
1316
1928
  if (fact.doc && !node.doc)
@@ -1318,38 +1930,22 @@ ${dir}`, node);
1318
1930
  this.#digest(node, this.#source.readText(fileName), fact.extent);
1319
1931
  return node.id;
1320
1932
  }
1321
- #guardNodeOf({ name, owner, span, scope, doc, parts }) {
1322
- const node = this.#addNode(this.#key("guard", owner, name), "guard", owner, null, name, span, scope ? [scope] : []);
1933
+ #guardNodeOf({ name, owner, span, scope, doc, parts, file, label }) {
1934
+ const id = this.#key("guard", owner, file ? `${file}#${name}` : name);
1935
+ const node = this.#addNode(id, "guard", owner, null, name, span, scope ? [scope] : [], label ?? name);
1323
1936
  if (doc && !node.doc)
1324
1937
  node.doc = doc;
1325
- const digest = node.digest ? null : this.#hashOf(...parts.map(({ fileName, extent }) => [
1326
- this.#source.readText(fileName),
1327
- extent
1328
- ]));
1329
- if (digest)
1330
- node.digest = digest;
1938
+ if (node.digest)
1939
+ return node.id;
1940
+ const digest = this.#hashOf(...parts.map(({ fileName, extent }) => [this.#source.readText(fileName), extent]));
1941
+ if (!digest)
1942
+ return node.id;
1943
+ node.digest = digest;
1944
+ const sources = [...new Set(parts.map(({ fileName }) => this.#source.relative(fileName)))].filter((source) => source !== node.at?.file);
1945
+ if (sources.length)
1946
+ node.sources = sources;
1331
1947
  return node.id;
1332
1948
  }
1333
- #ownerOfSpecifier(fileName, specifier) {
1334
- if (frameworkPattern.test(specifier))
1335
- return frameworkOwner;
1336
- const path = this.#pathOfSpecifier(fileName, specifier);
1337
- return path === null ? undefined : ownerOf(path) ?? undefined;
1338
- }
1339
- #pathOfSpecifier(fileName, specifier) {
1340
- const alias = specifier.match(aliasPattern);
1341
- if (alias)
1342
- return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1343
- if (!specifier.startsWith("."))
1344
- return null;
1345
- const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1346
- for (const segment of specifier.split("/"))
1347
- if (segment === "..")
1348
- segments.pop();
1349
- else if (segment !== ".")
1350
- segments.push(segment);
1351
- return segments.join("/");
1352
- }
1353
1949
  #moduleDirOf(fileName) {
1354
1950
  const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
1355
1951
  return dir?.startsWith("_") && !dir.startsWith("__") ? dir.slice(1) : dir;
@@ -1385,20 +1981,13 @@ ${dir}`, node);
1385
1981
  visit(constant);
1386
1982
  return [...fields.values()];
1387
1983
  }
1388
- #visible(target, from) {
1389
- return !from || !target || target === from || target.startsWith("libs/");
1390
- }
1391
- #soleVisible(declared, from) {
1392
- const visible = declared.filter((other) => this.#visible(other.owner, from));
1393
- return visible.length === 1 ? visible[0] : undefined;
1394
- }
1395
1984
  #targetNodeOf({ family, owner, refName, sourceKey }) {
1396
1985
  const isDefault = /^[A-Z]/.test(sourceKey);
1397
1986
  const id = family === "slice" || family === "query" ? this.#key(family, owner, refName, isDefault ? "" : sourceKey) : family === "model" || family === "formSetter" ? this.#key("model", owner, refName) : family === "endpoint" ? this.#key("endpoint", owner, refName, sourceKey) : null;
1398
1987
  return id && this.#nodes.has(id) ? id : null;
1399
1988
  }
1400
1989
  #membersNamed(kind, name, owner, refName) {
1401
- const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#visible(node.owner, owner));
1990
+ const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#resolver.visible(node.owner, owner));
1402
1991
  const own = nodes.filter((node) => node.owner === owner);
1403
1992
  return (own.length ? own : nodes).map((node) => node.id);
1404
1993
  }
@@ -1443,10 +2032,8 @@ st
1443
2032
  visit(source);
1444
2033
  }
1445
2034
  #link(fileName, source, identifier, chain, context, owner) {
1446
- const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#plugs.get(`${context.owner ?? ""}
1447
- ${context.module}
1448
- ${chain[1]}`) : undefined;
1449
- const resolved = plugged ? [plugged] : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
2035
+ const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#pluggedOf(context, chain[1]) : [];
2036
+ const resolved = plugged.length ? plugged : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
1450
2037
  const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
1451
2038
  if (!targets.length)
1452
2039
  return;
@@ -1752,6 +2339,10 @@ var endpointNames = (key) => {
1752
2339
  };
1753
2340
 
1754
2341
  // pkgs/@akanjs/language/nameTable.ts
2342
+ var guardDetailOf = (facts, guards) => {
2343
+ const labels = Object.assign({}, ...facts.map((file) => file.guardLabels));
2344
+ return guards.length ? [`guards: ${guards.map((guard) => guardLabelOf(guard, labels)).join(", ")}`] : [];
2345
+ };
1755
2346
  var capitalize3 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
1756
2347
 
1757
2348
  class NameTable {
@@ -1851,7 +2442,7 @@ class NameTable {
1851
2442
  role: "sig",
1852
2443
  gate: "sig",
1853
2444
  label: sliceClass.name,
1854
- detail: sliceClass.guards.length ? [`guards: ${sliceClass.guards.join(", ")}`] : []
2445
+ detail: guardDetailOf(facts, sliceClass.guards)
1855
2446
  });
1856
2447
  if (modelDoc)
1857
2448
  this.#add({
@@ -1916,7 +2507,7 @@ class NameTable {
1916
2507
  sourceKey: slice.name,
1917
2508
  span: slice.span,
1918
2509
  label: `slice ${refName} (default)`,
1919
- detail: slice.guards.length ? [`guards: ${slice.guards.join(", ")}`] : []
2510
+ detail: guardDetailOf(facts, slice.guards)
1920
2511
  };
1921
2512
  this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
1922
2513
  for (const key of slice.keys)
@@ -1926,7 +2517,7 @@ class NameTable {
1926
2517
  sourceKey: key.name,
1927
2518
  span: key.span,
1928
2519
  label: `slice ${key.name}`,
1929
- detail: [...key.guards.length ? [`guards: ${key.guards.join(", ")}`] : [], ...key.live ? ["live"] : []]
2520
+ detail: [...guardDetailOf(facts, key.guards), ...key.live ? ["live"] : []]
1930
2521
  }, refName, key.name, modelNameSet, slice.name);
1931
2522
  }
1932
2523
  if (filterClass) {
@@ -1983,7 +2574,7 @@ class NameTable {
1983
2574
  role: key.verb === "pubsub" ? "subscribe" : "listen",
1984
2575
  gate: null,
1985
2576
  label: `${key.verb} ${key.name}`,
1986
- detail: key.guards.length ? [`guards: ${key.guards.join(", ")}`] : []
2577
+ detail: guardDetailOf(facts, key.guards)
1987
2578
  });
1988
2579
  }
1989
2580
  }
@@ -2041,14 +2632,12 @@ var dictionaryPattern = /\.dictionary\.ts$/;
2041
2632
  var srvkitPattern = /\/srvkit\//;
2042
2633
  var wordPattern = /[A-Za-z_$][\w$]*/g;
2043
2634
  var identifierPattern = /^[A-Za-z_$][\w$]*$/;
2044
- var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
2045
2635
  var stateReceivers = new Set(["use", "sel", "get", "do"]);
2046
2636
  var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
2047
2637
  var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
2048
2638
  var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
2049
2639
  var parseCacheSize = 64;
2050
2640
  var pathOf = (fileName) => fileName.replace(/\\/g, "/");
2051
- var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
2052
2641
  var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
2053
2642
 
2054
2643
  class AkanIndex {
@@ -2179,6 +2768,8 @@ canPass
2179
2768
  adapt
2180
2769
  `) || words.includes(`
2181
2770
  dangerouslyAdapt
2771
+ `) || words.includes(`
2772
+ applyAdaptor
2182
2773
  `);
2183
2774
  }
2184
2775
  static #sourceKeyOf({ family, sourceKey, label, detail, span }) {