@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/server.js CHANGED
@@ -5,6 +5,239 @@ import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "n
5
5
  import path from "node:path";
6
6
  import { fileURLToPath, pathToFileURL } from "node:url";
7
7
 
8
+ // pkgs/@akanjs/language/guardFacts.ts
9
+ var guardLabelOf = (guard, labels) => {
10
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
11
+ const label = labels[local];
12
+ return label === undefined ? guard : role ? `${role} ${label}` : label;
13
+ };
14
+ var maxLabelArgs = 32;
15
+ var guardRefReaderOf = (ts, ast, unwrap) => {
16
+ const pathOf = (expression) => {
17
+ if (ts.isIdentifier(expression))
18
+ return expression.text;
19
+ if (!ts.isPropertyAccessExpression(expression))
20
+ return null;
21
+ const head = pathOf(expression.expression);
22
+ return head && `${head}.${expression.name.text}`;
23
+ };
24
+ const calleeOf = (call) => pathOf(unwrap(call.expression)) ?? "?";
25
+ const labelOf = (source, call) => {
26
+ const args = call.arguments.map((arg) => arg.getText(source)).join(", ");
27
+ return `${calleeOf(call)}(${args.length <= maxLabelArgs && !args.includes(`
28
+ `) ? args : "…"})`;
29
+ };
30
+ const guardRefOf = (expression, made, member) => {
31
+ const value = unwrap(expression);
32
+ if (!ts.isCallExpression(value))
33
+ return pathOf(value) ?? ast.expressionName(value);
34
+ const prefix = `${member}:${calleeOf(value)}@`;
35
+ const name = `${prefix}${made.filter((other) => other.name.startsWith(prefix)).length + 1}`;
36
+ made.push({ call: value, name });
37
+ return name;
38
+ };
39
+ const guardsIn = (source, object, made, member) => {
40
+ const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
41
+ if (!guards)
42
+ return [];
43
+ const refOf = (expression) => guardRefOf(expression, made, member);
44
+ if (ts.isArrayLiteralExpression(guards))
45
+ return guards.elements.map(refOf).filter((name) => name !== null);
46
+ if (ts.isObjectLiteralExpression(guards))
47
+ return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
48
+ const value = unwrap(property.initializer);
49
+ const names = ts.isArrayLiteralExpression(value) ? value.elements.map(refOf) : [refOf(value)];
50
+ return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
51
+ });
52
+ const single = refOf(guards);
53
+ return single ? [single] : [];
54
+ };
55
+ return { pathOf, labelOf, guardsIn };
56
+ };
57
+ var guardFactoryReaderOf = (ts, ast, { spanOf, extentOf, unwrap, docOf }, pathOf) => {
58
+ const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
59
+ const scopeOf = (node) => {
60
+ const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
61
+ const value = scope && unwrap(scope);
62
+ return value && ts.isStringLiteralLike(value) ? value.text : null;
63
+ };
64
+ const functionsOf = (source) => {
65
+ const functions = new Map;
66
+ for (const statement of source.statements) {
67
+ if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
68
+ functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
69
+ if (!ts.isVariableStatement(statement))
70
+ continue;
71
+ for (const declaration of statement.declarationList.declarations) {
72
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
73
+ if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
74
+ functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
75
+ }
76
+ }
77
+ return functions;
78
+ };
79
+ const returnedOf = (body, guardNames) => {
80
+ const locals = new Map;
81
+ const pending = [];
82
+ const visit = (node) => {
83
+ if (ts.isClassDeclaration(node) && node.name)
84
+ locals.set(node.name.text, node);
85
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
86
+ locals.set(node.name.text, node.initializer);
87
+ if (ts.isReturnStatement(node) && node.expression)
88
+ pending.push(node.expression);
89
+ if (!ts.isFunctionLike(node) && !ts.isClassLike(node))
90
+ ts.forEachChild(node, visit);
91
+ };
92
+ if (ts.isBlock(body))
93
+ ts.forEachChild(body, visit);
94
+ else
95
+ pending.push(body);
96
+ let guard = null;
97
+ const calls = [];
98
+ const seen = new Set;
99
+ const passed = new Set;
100
+ for (let node = pending.pop();node; node = pending.pop()) {
101
+ if (seen.has(node))
102
+ continue;
103
+ seen.add(node);
104
+ const value = ts.isExpression(node) ? unwrap(node) : node;
105
+ if ((ts.isClassExpression(value) || ts.isClassDeclaration(value)) && implementsGuard(value, guardNames))
106
+ guard ??= value;
107
+ else if (ts.isConditionalExpression(value))
108
+ pending.push(value.whenTrue, value.whenFalse);
109
+ else if (ts.isBinaryExpression(value))
110
+ pending.push(value.left, value.right);
111
+ else if (ts.isIdentifier(value)) {
112
+ const local = locals.get(value.text);
113
+ if (local)
114
+ pending.push(local);
115
+ } else if (ts.isCallExpression(value)) {
116
+ const callee = pathOf(unwrap(value.expression));
117
+ if (callee && !passed.has(node) && !calls.includes(callee))
118
+ calls.push(callee);
119
+ for (const arg of value.arguments)
120
+ passed.add(arg);
121
+ pending.push(...value.arguments);
122
+ }
123
+ }
124
+ return { guard, calls };
125
+ };
126
+ const guardFactoriesOf = (source, guardNames, isImported) => {
127
+ const functions = functionsOf(source);
128
+ const returned = new Map([...functions].map(([name, { body }]) => [name, returnedOf(body, guardNames)]));
129
+ const members = new Set;
130
+ const isFactoryCall = (callee) => members.has(callee) || !functions.has(callee) && isImported(callee);
131
+ for (let isGrowing = true;isGrowing; ) {
132
+ isGrowing = false;
133
+ for (const [name, { guard, calls }] of returned) {
134
+ if (members.has(name) || !guard && !calls.some(isFactoryCall))
135
+ continue;
136
+ members.add(name);
137
+ isGrowing = true;
138
+ }
139
+ }
140
+ const factories = new Map;
141
+ for (const name of members) {
142
+ const found = functions.get(name);
143
+ const made = returned.get(name);
144
+ if (!found || !made)
145
+ continue;
146
+ factories.set(name, {
147
+ name,
148
+ span: spanOf(source, found.name),
149
+ extent: extentOf(source, found.statement),
150
+ scope: made.guard ? scopeOf(made.guard) : null,
151
+ doc: docOf(source, found.statement),
152
+ direct: !!made.guard,
153
+ via: made.calls.filter(isFactoryCall)
154
+ });
155
+ }
156
+ return factories;
157
+ };
158
+ return { implementsGuard, scopeOf, guardFactoriesOf };
159
+ };
160
+ var createGuardFactReader = (ts, ast, base) => {
161
+ const { spanOf, extentOf, unwrap, docOf } = base;
162
+ const { pathOf, labelOf, guardsIn } = guardRefReaderOf(ts, ast, unwrap);
163
+ const { implementsGuard, scopeOf, guardFactoriesOf } = guardFactoryReaderOf(ts, ast, base, pathOf);
164
+ const guardsOf = (source, imports, madeInPlace) => {
165
+ const guardNames = new Set([
166
+ "Guard",
167
+ ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
168
+ ]);
169
+ const imported = new Set(imports.map(({ name }) => name));
170
+ const isImported = (callee) => imported.has(callee.split(".")[0] ?? callee);
171
+ const factories = guardFactoriesOf(source, guardNames, isImported);
172
+ const guards = [];
173
+ const calls = [];
174
+ const makeOf = (made, value, isInPlace = false) => {
175
+ if (ts.isClassExpression(value) && implementsGuard(value, guardNames)) {
176
+ guards.push({ ...made, scope: scopeOf(value), factory: null });
177
+ return;
178
+ }
179
+ const callee = ts.isCallExpression(value) ? pathOf(unwrap(value.expression)) : null;
180
+ const factory = callee && factories.get(callee);
181
+ if (factory)
182
+ guards.push({ ...made, scope: factory.scope, factory: factory.name });
183
+ else if (callee && (isImported(callee) || isInPlace))
184
+ calls.push({ ...made, callee, kind: "call", scope: null });
185
+ };
186
+ for (const statement of source.statements) {
187
+ if (ts.isClassDeclaration(statement) && statement.name) {
188
+ const declared = {
189
+ name: statement.name.text,
190
+ span: spanOf(source, statement.name),
191
+ extent: extentOf(source, statement),
192
+ scope: scopeOf(statement),
193
+ doc: docOf(source, statement)
194
+ };
195
+ const base = statement.heritageClauses?.find(({ token }) => token === ts.SyntaxKind.ExtendsKeyword)?.types[0];
196
+ const callee = base && pathOf(base.expression);
197
+ if (implementsGuard(statement, guardNames))
198
+ guards.push({ ...declared, factory: null });
199
+ else if (callee)
200
+ calls.push({ ...declared, callee, kind: "extends" });
201
+ }
202
+ if (!ts.isVariableStatement(statement))
203
+ continue;
204
+ for (const declaration of statement.declarationList.declarations) {
205
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
206
+ if (!initializer || !ts.isIdentifier(declaration.name))
207
+ continue;
208
+ const holder = declaration.name.text;
209
+ makeOf({
210
+ name: holder,
211
+ span: spanOf(source, declaration.name),
212
+ extent: extentOf(source, statement),
213
+ doc: docOf(source, statement)
214
+ }, initializer);
215
+ if (!ts.isObjectLiteralExpression(initializer))
216
+ continue;
217
+ for (const property of initializer.properties) {
218
+ const key = ts.isPropertyAssignment(property) ? ast.nodeName(property.name) : null;
219
+ if (key && ts.isPropertyAssignment(property))
220
+ makeOf({
221
+ name: `${holder}.${key}`,
222
+ span: spanOf(source, property.name),
223
+ extent: extentOf(source, property),
224
+ doc: docOf(source, property)
225
+ }, unwrap(property.initializer));
226
+ }
227
+ }
228
+ }
229
+ for (const { call, name } of madeInPlace)
230
+ makeOf({ name, span: spanOf(source, call), extent: extentOf(source, call), doc: null }, call, true);
231
+ const guardLabels = Object.fromEntries(madeInPlace.map(({ call, name }) => [name, labelOf(source, call)]));
232
+ return { guards, guardFactories: [...factories.values()], guardCalls: calls, guardLabels };
233
+ };
234
+ return { guardsIn, guardsOf };
235
+ };
236
+
237
+ // pkgs/@akanjs/language/escapeRegExp.ts
238
+ var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
239
+ var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
240
+
8
241
  // pkgs/@akanjs/language/moduleFacts.ts
9
242
  var testFilePattern = /\.(?:test|spec)\.tsx?$/;
10
243
  var moduleCallees = new Set([
@@ -73,21 +306,6 @@ var createFactReader = (ts, ast) => {
73
306
  const value = unwrap(expression);
74
307
  return ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) ? `${value.expression.text}.${value.name.text}` : ast.expressionName(value);
75
308
  };
76
- const guardsIn = (object) => {
77
- const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
78
- if (!guards)
79
- return [];
80
- if (ts.isArrayLiteralExpression(guards))
81
- return guards.elements.map(guardNameOf).filter((name) => name !== null);
82
- if (ts.isObjectLiteralExpression(guards))
83
- return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
84
- const value = unwrap(property.initializer);
85
- const names = ts.isArrayLiteralExpression(value) ? value.elements.map(guardNameOf) : [guardNameOf(value)];
86
- return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
87
- });
88
- const single = guardNameOf(guards);
89
- return single ? [single] : [];
90
- };
91
309
  const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
92
310
  const chainOf = (initializer) => {
93
311
  let root = unwrap(initializer);
@@ -104,7 +322,7 @@ var createFactReader = (ts, ast) => {
104
322
  const node = keyName(property);
105
323
  return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
106
324
  });
107
- const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
325
+ const sliceKeysOf = (source, builder, made, model) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
108
326
  if (!ts.isPropertyAssignment(property))
109
327
  return [];
110
328
  const name = ast.nodeName(property.name);
@@ -116,7 +334,7 @@ var createFactReader = (ts, ast) => {
116
334
  name,
117
335
  span: spanOf(source, property.name),
118
336
  extent: extentOf(source, property),
119
- guards: root ? guardsIn(optionObjectOf(root)) : [],
337
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
120
338
  live: methods.includes("live"),
121
339
  argCount: methods.filter((method) => argMethods.has(method)).length
122
340
  }
@@ -166,7 +384,7 @@ var createFactReader = (ts, ast) => {
166
384
  const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
167
385
  return `${ms / size}${unit}`;
168
386
  };
169
- const modesOf = (option) => {
387
+ const modesOf = (option, type) => {
170
388
  const object = option && unwrap(option);
171
389
  if (!object || !ts.isObjectLiteralExpression(object))
172
390
  return [];
@@ -176,9 +394,9 @@ var createFactReader = (ts, ast) => {
176
394
  if (name === "enabled")
177
395
  return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
178
396
  if (name === "once")
179
- return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
397
+ return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
180
398
  if (name === "lock")
181
- return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
399
+ return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
182
400
  if (name !== "serverMode" && name !== "operationMode")
183
401
  return [];
184
402
  const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
@@ -196,7 +414,8 @@ var createFactReader = (ts, ast) => {
196
414
  const { root } = chainOf(property.initializer);
197
415
  const type = root ? verbOf(root.expression) : null;
198
416
  const [first, second] = root?.arguments ?? [];
199
- const schedule = type === "cron" && first ? ts.isStringLiteralLike(first) ? first.text : first.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
417
+ const cron = type === "cron" && first ? unwrap(first) : null;
418
+ const schedule = cron ? ts.isStringLiteralLike(cron) ? cron.text : cron.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
200
419
  const option = type === "initialize" || type === "destroy" ? first : second;
201
420
  return [
202
421
  {
@@ -205,7 +424,7 @@ var createFactReader = (ts, ast) => {
205
424
  extent: extentOf(source, property),
206
425
  type,
207
426
  schedule,
208
- modes: modesOf(option)
427
+ modes: modesOf(option, type)
209
428
  }
210
429
  ];
211
430
  });
@@ -222,7 +441,20 @@ var createFactReader = (ts, ast) => {
222
441
  return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
223
442
  });
224
443
  };
225
- const endpointKeysOf = (source, builder) => {
444
+ const adaptorBindingsIn = (source) => {
445
+ const bindings = [];
446
+ const visit = (node) => {
447
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "applyAdaptor") {
448
+ const [role, adaptor] = node.arguments.map(guardNameOf);
449
+ if (role && adaptor)
450
+ bindings.push({ role, adaptor, span: spanOf(source, node.expression.name) });
451
+ }
452
+ ts.forEachChild(node, visit);
453
+ };
454
+ visit(source);
455
+ return bindings;
456
+ };
457
+ const endpointKeysOf = (source, builder, made, model) => {
226
458
  const verbOf = verbReaderOf(builder?.parameters[0]);
227
459
  return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
228
460
  if (!ts.isPropertyAssignment(property))
@@ -237,7 +469,7 @@ var createFactReader = (ts, ast) => {
237
469
  span: spanOf(source, property.name),
238
470
  extent: extentOf(source, property),
239
471
  verb: root ? verbOf(root.expression) : null,
240
- guards: root ? guardsIn(optionObjectOf(root)) : [],
472
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
241
473
  argCount: methods.filter((method) => argMethods.has(method)).length
242
474
  }
243
475
  ];
@@ -304,22 +536,6 @@ var createFactReader = (ts, ast) => {
304
536
  })) : []
305
537
  ];
306
538
  };
307
- const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
308
- const guardClassIn = (node, guardNames) => {
309
- if ((ts.isClassExpression(node) || ts.isClassDeclaration(node)) && implementsGuard(node, guardNames))
310
- return node;
311
- return ts.forEachChild(node, (child) => guardClassIn(child, guardNames)) ?? null;
312
- };
313
- const callsIn = (node, names) => {
314
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && names.has(node.expression.text))
315
- return node.expression.text;
316
- return ts.forEachChild(node, (child) => callsIn(child, names)) ?? null;
317
- };
318
- const scopeOf = (node) => {
319
- const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
320
- const value = scope && unwrap(scope);
321
- return value && ts.isStringLiteralLike(value) ? value.text : null;
322
- };
323
539
  const docOf = (source, node) => {
324
540
  const { text } = source;
325
541
  const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
@@ -335,108 +551,20 @@ var createFactReader = (ts, ast) => {
335
551
  `)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
336
552
  return lines.length ? lines.join(" ") : null;
337
553
  };
338
- const functionsOf = (source) => {
339
- const functions = new Map;
340
- for (const statement of source.statements) {
341
- if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
342
- functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
343
- if (!ts.isVariableStatement(statement))
344
- continue;
345
- for (const declaration of statement.declarationList.declarations) {
346
- const initializer = declaration.initializer && unwrap(declaration.initializer);
347
- if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
348
- functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
349
- }
350
- }
351
- return functions;
352
- };
353
- const guardFactoriesOf = (source, guardNames) => {
354
- const functions = functionsOf(source);
355
- const factories = new Map;
356
- const factoryOf = (name, scope, via) => {
357
- const found = functions.get(name);
358
- if (!found)
359
- return;
360
- factories.set(name, {
361
- name,
362
- span: spanOf(source, found.name),
363
- extent: extentOf(source, found.statement),
364
- scope,
365
- doc: docOf(source, found.statement),
366
- via
367
- });
368
- };
369
- for (const [name, { body }] of functions) {
370
- const guard = guardClassIn(body, guardNames);
371
- if (guard)
372
- factoryOf(name, scopeOf(guard), null);
373
- }
374
- for (let isGrowing = true;isGrowing; ) {
375
- isGrowing = false;
376
- for (const [name, { body }] of functions) {
377
- const called = factories.has(name) ? null : callsIn(body, new Set(factories.keys()));
378
- if (!called)
379
- continue;
380
- factoryOf(name, factories.get(called)?.scope ?? null, called);
381
- isGrowing = true;
382
- }
383
- }
384
- return factories;
385
- };
386
- const guardsOf = (source, imports) => {
387
- const guardNames = new Set([
388
- "Guard",
389
- ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
390
- ]);
391
- const imported = new Set(imports.map(({ name }) => name));
392
- const factories = guardFactoriesOf(source, guardNames);
393
- const guards = [];
394
- const calls = [];
395
- for (const statement of source.statements) {
396
- if (ts.isClassDeclaration(statement) && statement.name && implementsGuard(statement, guardNames))
397
- guards.push({
398
- name: statement.name.text,
399
- span: spanOf(source, statement.name),
400
- extent: extentOf(source, statement),
401
- scope: scopeOf(statement),
402
- doc: docOf(source, statement),
403
- factory: null
404
- });
405
- if (!ts.isVariableStatement(statement))
406
- continue;
407
- for (const declaration of statement.declarationList.declarations) {
408
- const initializer = declaration.initializer && unwrap(declaration.initializer);
409
- if (!initializer || !ts.isIdentifier(declaration.name))
410
- continue;
411
- const made = {
412
- name: declaration.name.text,
413
- span: spanOf(source, declaration.name),
414
- extent: extentOf(source, statement),
415
- doc: docOf(source, statement)
416
- };
417
- if (ts.isClassExpression(initializer) && implementsGuard(initializer, guardNames))
418
- guards.push({ ...made, scope: scopeOf(initializer), factory: null });
419
- if (!ts.isCallExpression(initializer) || !ts.isIdentifier(initializer.expression))
420
- continue;
421
- const callee = initializer.expression.text;
422
- const factory = factories.get(callee);
423
- if (factory)
424
- guards.push({ ...made, scope: factory.scope, factory: callee });
425
- else if (imported.has(callee))
426
- calls.push({ ...made, callee });
427
- }
428
- }
429
- return { guards, guardFactories: [...factories.values()], guardCalls: calls };
430
- };
554
+ const { guardsIn, guardsOf } = createGuardFactReader(ts, ast, { spanOf, extentOf, unwrap, docOf });
431
555
  const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
432
556
  const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
433
557
  const read = (source, owner) => {
434
558
  const imports = importsIn(source);
559
+ const madeInPlace = [];
435
560
  const facts = {
436
561
  fileName: source.fileName,
437
562
  owner,
438
563
  imports,
439
- ...guardsOf(source, imports),
564
+ guards: [],
565
+ guardFactories: [],
566
+ guardCalls: [],
567
+ guardLabels: {},
440
568
  constants: [],
441
569
  enums: [],
442
570
  filters: [],
@@ -446,6 +574,7 @@ var createFactReader = (ts, ast) => {
446
574
  endpoints: [],
447
575
  internals: [],
448
576
  adaptors: [],
577
+ adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
449
578
  stores: []
450
579
  };
451
580
  for (const statement of source.statements) {
@@ -470,16 +599,19 @@ var createFactReader = (ts, ast) => {
470
599
  ...name,
471
600
  model: moduleRefOf(call.arguments[0], "db"),
472
601
  methods: methodsOf(source, statement),
473
- plugs: plugsOf(source, builder)
602
+ plugs: plugsOf(source, builder),
603
+ fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
474
604
  });
475
- else if (callee === "slice")
605
+ else if (callee === "slice") {
606
+ const model = moduleRefOf(call.arguments[0], "srv");
607
+ const member = model ?? name.name;
476
608
  facts.slices.push({
477
609
  ...name,
478
- model: moduleRefOf(call.arguments[0], "srv"),
479
- guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
480
- keys: sliceKeysOf(source, builder)
610
+ model,
611
+ guards: guardsIn(source, call.arguments.find(ts.isObjectLiteralExpression), madeInPlace, member),
612
+ keys: sliceKeysOf(source, builder, madeInPlace, member)
481
613
  });
482
- else if (callee === "internal")
614
+ } else if (callee === "internal")
483
615
  facts.internals.push({
484
616
  ...name,
485
617
  model: moduleRefOf(call.arguments[0], "srv"),
@@ -494,13 +626,14 @@ var createFactReader = (ts, ast) => {
494
626
  plugs: plugsOf(source, builder),
495
627
  doc: docOf(source, statement)
496
628
  });
497
- } else if (callee === "endpoint")
629
+ } else if (callee === "endpoint") {
630
+ const model = moduleRefOf(call.arguments[0], "srv");
498
631
  facts.endpoints.push({
499
632
  ...name,
500
- model: moduleRefOf(call.arguments[0], "srv"),
501
- keys: endpointKeysOf(source, builder)
633
+ model,
634
+ keys: endpointKeysOf(source, builder, madeInPlace, model ?? name.name)
502
635
  });
503
- else if (callee === "store")
636
+ } else if (callee === "store")
504
637
  facts.stores.push({
505
638
  ...name,
506
639
  model: moduleRefOf(call.arguments[0], "sig"),
@@ -508,10 +641,439 @@ var createFactReader = (ts, ast) => {
508
641
  actions: methodsOf(source, statement)
509
642
  });
510
643
  }
511
- return facts;
512
- };
513
- return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
514
- };
644
+ return { ...facts, ...guardsOf(source, imports, madeInPlace) };
645
+ };
646
+ return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
647
+ };
648
+
649
+ // pkgs/@akanjs/language/guardResolver.ts
650
+ var frameworkOwner = "akanjs";
651
+ var frameworkPattern = /^akanjs(?:\/|$)/;
652
+ var aliasPattern = /^@(apps|libs)\/([^/]+)/;
653
+ var maxMapDepth = 4;
654
+ var starExportPattern = /export\s*\*\s*from\s*["']([^"']+)["']/g;
655
+ var declaringPatternOf = (name, isAnyBinding) => {
656
+ const escaped = escapeRegExp(name);
657
+ const braced = "\\{(?:[^}]*?[\\s,])?";
658
+ const leads = [
659
+ "function\\s*\\*?\\s*",
660
+ "(?:const|let|var)\\s+",
661
+ `export\\s*${braced}`,
662
+ ...isAnyBinding ? ["class\\s+", `(?:const|let|var)\\s*${braced}`] : []
663
+ ];
664
+ return new RegExp(`(?:${leads.join("|")})${escaped}(?![\\w$])|,\\s*${escaped}\\s*(?::[^=;,]+)?=(?![=>])`);
665
+ };
666
+
667
+ class GuardResolver {
668
+ #source;
669
+ #routes;
670
+ #guards = new Map;
671
+ #madeInPlace = new Map;
672
+ #mapEntries = new Map;
673
+ #reexports = new Map;
674
+ #declarations = new Map;
675
+ #starExports = new Map;
676
+ #ownerFiles = null;
677
+ #modules = null;
678
+ constructor(source, routes) {
679
+ this.#source = source;
680
+ this.#routes = routes;
681
+ }
682
+ find(local, site) {
683
+ return this.#findGuard(local, site, 0);
684
+ }
685
+ madeInPlace(fileName, name) {
686
+ return this.#madeInPlace.get(`${fileName}
687
+ ${name}`);
688
+ }
689
+ declare(facts) {
690
+ const factories = new Map;
691
+ const declaredFactories = [];
692
+ for (const file of facts)
693
+ for (const fact of file.guardFactories) {
694
+ const factory = {
695
+ fact,
696
+ owner: file.owner,
697
+ site: file,
698
+ made: [],
699
+ isReal: fact.direct,
700
+ scope: fact.scope,
701
+ targets: []
702
+ };
703
+ factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
704
+ declaredFactories.push(factory);
705
+ }
706
+ for (const factory of declaredFactories)
707
+ factory.targets = factory.fact.via.map((callee) => this.#factoryAt(factories, callee, factory.site)).filter((target) => target !== undefined);
708
+ for (let isGrowing = true;isGrowing; ) {
709
+ isGrowing = false;
710
+ for (const factory of declaredFactories) {
711
+ const target = factory.targets.find((other) => other.isReal);
712
+ if (factory.isReal || !target)
713
+ continue;
714
+ factory.isReal = true;
715
+ factory.scope ??= target.scope;
716
+ isGrowing = true;
717
+ }
718
+ }
719
+ const partsOf = (factory, seen = new Set) => {
720
+ if (seen.has(factory))
721
+ return [];
722
+ seen.add(factory);
723
+ return [
724
+ { fileName: factory.site.fileName, extent: factory.fact.extent },
725
+ ...factory.targets.filter((target) => target.isReal).flatMap((target) => partsOf(target, seen))
726
+ ];
727
+ };
728
+ const declare = (file, made, scope, parts) => {
729
+ const { name, span, extent, doc } = made;
730
+ const label = file.guardLabels[name];
731
+ const own = { fileName: file.fileName, extent };
732
+ const guard = { name, owner: file.owner, span, scope, doc, parts: [own, ...parts], file: null, label };
733
+ if (name.includes("@"))
734
+ this.#madeInPlace.set(`${file.fileName}
735
+ ${name}`, guard);
736
+ else
737
+ this.#guards.set(name, [...this.#guards.get(name) ?? [], guard]);
738
+ return guard;
739
+ };
740
+ const extending = [];
741
+ for (const file of facts) {
742
+ for (const made of file.guards) {
743
+ const factory = made.factory ? factories.get(made.factory)?.find((other) => other.site.fileName === file.fileName) : undefined;
744
+ if (!made.factory)
745
+ declare(file, made, made.scope, []);
746
+ else if (factory?.isReal)
747
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
748
+ }
749
+ for (const made of file.guardCalls) {
750
+ if (made.kind === "extends") {
751
+ extending.push([file, made]);
752
+ continue;
753
+ }
754
+ const factory = this.#factoryAt(factories, made.callee, file);
755
+ if (factory?.isReal)
756
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
757
+ }
758
+ }
759
+ for (let isGrowing = true;isGrowing; ) {
760
+ isGrowing = false;
761
+ for (const [idx, entry] of extending.entries()) {
762
+ const [file, made] = entry;
763
+ const found = this.#findGuard(made.callee, file, 0, true);
764
+ if (!("guard" in found))
765
+ continue;
766
+ declare(file, made, made.scope ?? found.guard.scope, found.guard.parts);
767
+ extending.splice(idx, 1);
768
+ isGrowing = true;
769
+ break;
770
+ }
771
+ }
772
+ for (const { fact, made, isReal } of declaredFactories)
773
+ if (isReal && made.every((guard) => !guard.doc))
774
+ for (const guard of made)
775
+ guard.doc = fact.doc;
776
+ for (const list of this.#guards.values())
777
+ for (const guard of list)
778
+ if (list.some((other) => other !== guard && other.owner === guard.owner))
779
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
780
+ for (const guard of this.#madeInPlace.values())
781
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
782
+ return [...[...this.#guards.values()].flat(), ...this.#madeInPlace.values()];
783
+ }
784
+ #factoryAt(factories, callee, site, depth = 0) {
785
+ const [head = callee, member] = callee.split(".");
786
+ const imported = site.imports.find((item) => item.name === head);
787
+ if (!imported)
788
+ return member ? undefined : factories.get(callee)?.find((other) => other.site.fileName === site.fileName);
789
+ if (member && imported.imported !== "*")
790
+ return;
791
+ const from = this.ownerOfSpecifier(site.fileName, imported.from);
792
+ if (from === undefined || from === frameworkOwner)
793
+ return;
794
+ const name = member ?? (imported.imported === "default" || imported.imported === "*" ? head : imported.imported);
795
+ const candidates = factories.get(name) ?? [];
796
+ const near = this.pathOfSpecifier(site.fileName, imported.from);
797
+ const own = this.#nearest(candidates.filter((other) => other.owner === from), ({ site }) => site.fileName, near);
798
+ if (own)
799
+ return own;
800
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
801
+ if (reexport)
802
+ return this.#factoryAt(factories, reexport.local, reexport.site, depth + 1);
803
+ return from && near ? this.#throughStar(candidates, from, name, near) : undefined;
804
+ }
805
+ #findGuard(local, site, depth, isBase = false) {
806
+ const segments = local.split(".");
807
+ const head = segments[0] ?? local;
808
+ const imported = site.imports.find((item) => item.name === head);
809
+ const from = imported ? this.ownerOfSpecifier(site.fileName, imported.from) : undefined;
810
+ if (imported && from === undefined)
811
+ return { external: true };
812
+ const near = imported ? this.pathOfSpecifier(site.fileName, imported.from) : this.#source.relative(site.fileName).replace(/\.tsx?$/, "");
813
+ const named = imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : null;
814
+ const path = imported?.imported === "*" ? segments.slice(1) : [named ?? head, ...segments.slice(1)];
815
+ const holder = imported ? from ?? null : site.owner;
816
+ const [objectName, member] = path;
817
+ if (path.length === 2 && objectName && member && holder !== frameworkOwner && depth < maxMapDepth) {
818
+ const made = this.#nearest((this.#guards.get(`${objectName}.${member}`) ?? []).filter((guard) => guard.owner === holder), ({ span }) => span.fileName, near);
819
+ if (made)
820
+ return { guard: made };
821
+ for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
822
+ const found = this.#findGuard(entry.name, entry.site, depth + 1, isBase);
823
+ if ("guard" in found)
824
+ return found;
825
+ }
826
+ }
827
+ const name = path[path.length - 1] ?? head;
828
+ if (from === frameworkOwner)
829
+ return { name, from };
830
+ const declared = this.#guards.get(name) ?? [];
831
+ const own = this.#nearest(declared.filter((other) => other.owner === (imported ? from : site.owner)), ({ span }) => span.fileName, near);
832
+ if (own)
833
+ return { guard: own };
834
+ if (!imported) {
835
+ const visible = isBase ? undefined : declared.find((other) => this.visible(other.owner, site.owner));
836
+ return visible ? { guard: visible } : { name, from };
837
+ }
838
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
839
+ if (reexport)
840
+ return this.#findGuard(reexport.local, reexport.site, depth + 1, isBase);
841
+ const reached = from && near ? this.#throughStar(declared, from, name, near) : undefined;
842
+ return reached ? { guard: reached } : { name, from };
843
+ }
844
+ reexportOf(owner, name, near) {
845
+ const key = `${owner}
846
+ ${name}
847
+ ${near ?? ""}`;
848
+ const cached = this.#reexports.get(key);
849
+ if (cached !== undefined)
850
+ return cached;
851
+ let found = null;
852
+ const exporting = new RegExp(`export\\s*\\{(?:[^}]*?[\\s,])?${escapeRegExp(name)}(?![\\w$])`);
853
+ for (const fileName of this.#filesOf(owner, name, near)) {
854
+ const text = this.#source.readText(fileName);
855
+ if (text === undefined || !exporting.test(text))
856
+ continue;
857
+ const reexport = this.#reexportIn(this.#source.parse(fileName, text), name);
858
+ if (!reexport)
859
+ continue;
860
+ const site = { fileName, owner, imports: [] };
861
+ found = reexport.from ? {
862
+ local: reexport.name,
863
+ site: { ...site, imports: [{ ...reexport, imported: reexport.name, from: reexport.from }] }
864
+ } : { local: reexport.name, site };
865
+ break;
866
+ }
867
+ this.#reexports.set(key, found);
868
+ return found;
869
+ }
870
+ #reexportIn(source, name) {
871
+ const { ts, reader } = this.#source;
872
+ for (const statement of source.statements) {
873
+ if (!ts.isExportDeclaration(statement) || statement.isTypeOnly)
874
+ continue;
875
+ const { exportClause, moduleSpecifier } = statement;
876
+ if (!exportClause || !ts.isNamedExports(exportClause))
877
+ continue;
878
+ const element = exportClause.elements.find((item) => !item.isTypeOnly && item.name.text === name);
879
+ if (!element)
880
+ continue;
881
+ const original = (element.propertyName ?? element.name).text;
882
+ if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier))
883
+ return { name: original, from: moduleSpecifier.text };
884
+ const imported = reader.importsIn(source).find((item) => item.name === original);
885
+ if (!imported)
886
+ return { name: original, from: null };
887
+ return imported.imported === "default" || imported.imported === "*" ? null : { name: imported.imported, from: imported.from };
888
+ }
889
+ return null;
890
+ }
891
+ #filesOf(owner, name, near) {
892
+ const isNear = (fileName) => {
893
+ const relative = this.#source.relative(fileName);
894
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
895
+ };
896
+ const candidates = this.filesIn(owner).filter(([, words]) => words.includes(`
897
+ ${name}
898
+ `)).map(([fileName]) => fileName);
899
+ return [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))];
900
+ }
901
+ #mapEntriesOf(objectName, member, fileName, owner) {
902
+ const key = `${objectName}
903
+ ${member}
904
+ ${fileName ?? ""}
905
+ ${owner ?? ""}`;
906
+ const cached = this.#mapEntries.get(key);
907
+ if (cached)
908
+ return cached;
909
+ const { ts, reader } = this.#source;
910
+ const entries = [];
911
+ const fileNames = [
912
+ ...fileName ? [fileName] : [],
913
+ ...(owner === null ? [] : this.filesIn(owner)).filter(([other, words]) => other !== fileName && words.includes(`
914
+ ${objectName}
915
+ `) && words.includes(`
916
+ ${member}
917
+ `)).map(([other]) => other)
918
+ ];
919
+ for (const holder of fileNames) {
920
+ const text = this.#source.readText(holder);
921
+ if (text === undefined)
922
+ continue;
923
+ const source = this.#source.parse(holder, text);
924
+ const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
925
+ for (const statement of source.statements) {
926
+ if (!ts.isVariableStatement(statement))
927
+ continue;
928
+ const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
929
+ const object = map && reader.unwrap(map);
930
+ if (!object || !ts.isObjectLiteralExpression(object))
931
+ continue;
932
+ for (const property of object.properties) {
933
+ if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
934
+ entries.push({ name: member, site });
935
+ if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
936
+ continue;
937
+ const value = reader.unwrap(property.initializer);
938
+ if (ts.isIdentifier(value))
939
+ entries.push({ name: value.text, site });
940
+ else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
941
+ entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
942
+ }
943
+ }
944
+ }
945
+ this.#mapEntries.set(key, entries);
946
+ return entries;
947
+ }
948
+ declarationOf(owner, name, near, depth = 0) {
949
+ const key = `${owner}
950
+ ${name}
951
+ ${near ?? ""}`;
952
+ const cached = this.#declarations.get(key);
953
+ if (cached !== undefined)
954
+ return cached;
955
+ this.#declarations.set(key, null);
956
+ const declaring = declaringPatternOf(name, false);
957
+ let found = null;
958
+ for (const fileName of this.#filesOf(owner, name, near)) {
959
+ const text = this.#source.readText(fileName);
960
+ if (text === undefined || !declaring.test(text))
961
+ continue;
962
+ const source = this.#source.parse(fileName, text);
963
+ const declared = this.#routes.declarationIn(source, name);
964
+ if (declared) {
965
+ found = { source, ...declared, name };
966
+ break;
967
+ }
968
+ const reexport = this.#reexportIn(source, name);
969
+ if (!reexport)
970
+ continue;
971
+ if (!reexport.from) {
972
+ const local = this.#routes.declarationIn(source, reexport.name);
973
+ found = local ? { source, ...local, name: reexport.name } : null;
974
+ break;
975
+ }
976
+ const next = this.ownerOfSpecifier(fileName, reexport.from);
977
+ if (next && next !== frameworkOwner && depth < maxMapDepth)
978
+ found = this.declarationOf(next, reexport.name, this.pathOfSpecifier(fileName, reexport.from), depth + 1);
979
+ break;
980
+ }
981
+ this.#declarations.set(key, found);
982
+ return found;
983
+ }
984
+ #throughStar(declared, owner, name, near) {
985
+ if (!declared.length)
986
+ return;
987
+ const { owners, isOpaque } = this.#starExportsOf(owner, near);
988
+ const reached = declared.filter((other) => other.owner !== null && owners.includes(other.owner));
989
+ return reached.length === 1 && !isOpaque && !this.declares(owner, name) ? reached[0] : undefined;
990
+ }
991
+ #starExportsOf(owner, near) {
992
+ const cached = this.#starExports.get(near);
993
+ if (cached)
994
+ return cached;
995
+ const found = { owners: [], isOpaque: false };
996
+ this.#starExports.set(near, found);
997
+ const paths = [near];
998
+ for (let idx = 0;idx < paths.length && !found.isOpaque; idx++)
999
+ for (const fileName of this.#modulesAt(paths[idx] ?? "")) {
1000
+ for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? []) {
1001
+ const target = this.ownerOfSpecifier(fileName, specifier);
1002
+ const path = this.pathOfSpecifier(fileName, specifier);
1003
+ if (target === undefined || target === frameworkOwner || path === null)
1004
+ found.isOpaque = true;
1005
+ else if (!paths.includes(path))
1006
+ paths.push(path);
1007
+ if (target && target !== frameworkOwner && target !== owner && !found.owners.includes(target))
1008
+ found.owners.push(target);
1009
+ }
1010
+ }
1011
+ return found;
1012
+ }
1013
+ #modulesAt(path) {
1014
+ if (!this.#modules) {
1015
+ this.#modules = new Map;
1016
+ for (const [fileName] of this.#source.files) {
1017
+ const module = this.#source.relative(fileName).replace(/\.tsx?$/, "");
1018
+ for (const key of module.endsWith("/index") ? [module, module.slice(0, -6)] : [module])
1019
+ this.#modules.set(key, [...this.#modules.get(key) ?? [], fileName]);
1020
+ }
1021
+ }
1022
+ return this.#modules.get(path) ?? [];
1023
+ }
1024
+ declares(owner, name) {
1025
+ const declaring = declaringPatternOf(name, true);
1026
+ return this.#filesOf(owner, name, null).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
1027
+ }
1028
+ filesIn(owner) {
1029
+ if (!this.#ownerFiles) {
1030
+ this.#ownerFiles = new Map;
1031
+ for (const entry of this.#source.files) {
1032
+ const fileOwner = testFilePattern.test(entry[0]) ? null : this.#source.ownerOf(entry[0]);
1033
+ const files = fileOwner === null ? null : this.#ownerFiles.get(fileOwner);
1034
+ if (files)
1035
+ files.push(entry);
1036
+ else if (fileOwner !== null)
1037
+ this.#ownerFiles.set(fileOwner, [entry]);
1038
+ }
1039
+ }
1040
+ return this.#ownerFiles.get(owner) ?? [];
1041
+ }
1042
+ #nearest(items, fileOf, near) {
1043
+ const isNear = (fileName) => {
1044
+ const relative = this.#source.relative(fileName);
1045
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1046
+ };
1047
+ return items.find((item) => isNear(fileOf(item))) ?? items[0];
1048
+ }
1049
+ inOwner(fileName, owner) {
1050
+ const relative = this.#source.relative(fileName);
1051
+ return owner && relative.startsWith(`${owner}/`) ? relative.slice(owner.length + 1) : relative;
1052
+ }
1053
+ ownerOfSpecifier(fileName, specifier) {
1054
+ if (frameworkPattern.test(specifier))
1055
+ return frameworkOwner;
1056
+ const path = this.pathOfSpecifier(fileName, specifier);
1057
+ return path === null ? undefined : ownerOf(path) ?? undefined;
1058
+ }
1059
+ pathOfSpecifier(fileName, specifier) {
1060
+ const alias = specifier.match(aliasPattern);
1061
+ if (alias)
1062
+ return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1063
+ if (!specifier.startsWith("."))
1064
+ return null;
1065
+ const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1066
+ for (const segment of specifier.split("/"))
1067
+ if (segment === "..")
1068
+ segments.pop();
1069
+ else if (segment !== ".")
1070
+ segments.push(segment);
1071
+ return segments.join("/");
1072
+ }
1073
+ visible(target, from) {
1074
+ return !from || !target || target === from || target.startsWith("libs/");
1075
+ }
1076
+ }
515
1077
 
516
1078
  // pkgs/akanjs/common/trimSlashes.ts
517
1079
  var trimTrailingSlashes = (value) => {
@@ -660,24 +1222,23 @@ rootLayout
660
1222
  }
661
1223
  declarationIn(source, name) {
662
1224
  const ts = this.#ts;
1225
+ const overloads = source.statements.filter((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === name);
1226
+ const [first] = overloads;
1227
+ const last = overloads.at(-1);
1228
+ if (first?.name && last)
1229
+ return { statement: first, declared: first.name, extent: { start: first.getStart(source), end: last.getEnd() } };
663
1230
  for (const statement of source.statements) {
664
- if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
665
- return { statement, declared: statement.name, initializer: null };
666
1231
  if (!ts.isVariableStatement(statement))
667
1232
  continue;
668
1233
  const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
669
1234
  if (declaration)
670
- return { statement, declared: declaration.name, initializer: declaration.initializer ?? null };
1235
+ return { statement, declared: declaration.name, extent: this.#reader.extentOf(source, statement) };
671
1236
  }
672
1237
  return null;
673
1238
  }
674
1239
  #guardOf(source, exported) {
675
1240
  const ts = this.#ts;
676
1241
  let current = ts.isExportAssignment(exported) ? this.#reader.unwrap(exported.expression) : null;
677
- if (current && ts.isIdentifier(current)) {
678
- const initializer = this.declarationIn(source, current.text)?.initializer;
679
- current = initializer ? this.#reader.unwrap(initializer) : null;
680
- }
681
1242
  while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
682
1243
  const { name, expression } = current.expression;
683
1244
  const [stage] = current.arguments;
@@ -689,9 +1250,20 @@ rootLayout
689
1250
  }
690
1251
  #callsIn(source, stage) {
691
1252
  const ts = this.#ts;
692
- const imported = new Set(this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from)).map(({ name }) => name));
1253
+ const imports = this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from));
1254
+ const imported = new Set(imports.filter(({ imported }) => imported !== "*").map(({ name }) => name));
1255
+ const namespaces = new Set(imports.filter(({ imported }) => imported === "*").map(({ name }) => name));
693
1256
  const calls = new Map;
694
- const callOf = (name) => {
1257
+ const callOf = (callee, shadowed) => {
1258
+ if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text) && !shadowed.has(callee.expression.text)) {
1259
+ const name = `${callee.expression.text}.${callee.name.text}`;
1260
+ if (!calls.has(name))
1261
+ calls.set(name, { name, local: null });
1262
+ return true;
1263
+ }
1264
+ if (!ts.isIdentifier(callee) || shadowed.has(callee.text))
1265
+ return false;
1266
+ const name = callee.text;
695
1267
  if (calls.has(name))
696
1268
  return true;
697
1269
  const declared = this.declarationIn(source, name);
@@ -701,23 +1273,71 @@ rootLayout
701
1273
  name,
702
1274
  local: declared && {
703
1275
  span: this.#reader.spanOf(source, declared.declared),
704
- extent: this.#reader.extentOf(source, declared.statement),
1276
+ extent: declared.extent,
705
1277
  doc: this.#reader.docOf(source, declared.statement)
706
1278
  }
707
1279
  });
708
1280
  return true;
709
1281
  };
710
- const visit = (node) => {
711
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && callOf(node.expression.text))
1282
+ const visit = (node, outer) => {
1283
+ const shadowed = this.#scopeOf(node, outer);
1284
+ if (ts.isCallExpression(node) && callOf(node.expression, shadowed))
712
1285
  return;
713
- ts.forEachChild(node, visit);
1286
+ ts.forEachChild(node, (child) => visit(child, shadowed));
714
1287
  };
715
1288
  if (ts.isIdentifier(stage))
716
- callOf(stage.text);
1289
+ callOf(stage, new Set);
717
1290
  else
718
- visit(stage);
1291
+ visit(stage, new Set);
719
1292
  return [...calls.values()];
720
1293
  }
1294
+ #scopeOf(node, outer) {
1295
+ const ts = this.#ts;
1296
+ const names = [];
1297
+ const bind = (name) => {
1298
+ if (ts.isIdentifier(name))
1299
+ names.push(name.text);
1300
+ else
1301
+ for (const element of name.elements)
1302
+ if (!ts.isOmittedExpression(element))
1303
+ bind(element.name);
1304
+ };
1305
+ const declare = (statement) => {
1306
+ if (ts.isVariableStatement(statement))
1307
+ for (const declaration of statement.declarationList.declarations)
1308
+ bind(declaration.name);
1309
+ else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
1310
+ names.push(statement.name.text);
1311
+ };
1312
+ const hoist = (child) => {
1313
+ if (ts.isFunctionLike(child) || ts.isClassLike(child))
1314
+ return;
1315
+ if (ts.isVariableDeclarationList(child) && !(child.flags & ts.NodeFlags.BlockScoped))
1316
+ for (const declaration of child.declarations)
1317
+ bind(declaration.name);
1318
+ ts.forEachChild(child, hoist);
1319
+ };
1320
+ if (ts.isFunctionLike(node)) {
1321
+ for (const parameter of node.parameters)
1322
+ bind(parameter.name);
1323
+ if (ts.isFunctionExpression(node) && node.name)
1324
+ names.push(node.name.text);
1325
+ if ("body" in node && node.body)
1326
+ ts.forEachChild(node.body, hoist);
1327
+ } else if (ts.isClassExpression(node) && node.name)
1328
+ names.push(node.name.text);
1329
+ else if (ts.isBlock(node))
1330
+ node.statements.forEach(declare);
1331
+ else if (ts.isCaseBlock(node))
1332
+ for (const clause of node.clauses)
1333
+ clause.statements.forEach(declare);
1334
+ else if (ts.isCatchClause(node) && node.variableDeclaration)
1335
+ bind(node.variableDeclaration.name);
1336
+ else if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer))
1337
+ for (const declaration of node.initializer.declarations)
1338
+ bind(declaration.name);
1339
+ return names.length ? new Set([...outer, ...names]) : outer;
1340
+ }
721
1341
  }
722
1342
 
723
1343
  // pkgs/@akanjs/language/akanGraph.ts
@@ -733,10 +1353,6 @@ var builtinGuards = {
733
1353
  Public: { scope: "account", doc: "Passes every caller, signed in or not." },
734
1354
  None: { scope: "account", doc: "Refuses every caller." }
735
1355
  };
736
- var frameworkOwner = "akanjs";
737
- var frameworkPattern = /^akanjs(?:\/|$)/;
738
- var aliasPattern = /^@(apps|libs)\/([^/]+)/;
739
- var maxMapDepth = 4;
740
1356
 
741
1357
  class GraphBuilder {
742
1358
  #source;
@@ -744,16 +1360,16 @@ class GraphBuilder {
744
1360
  #edges = new Map;
745
1361
  #contexts = new Map;
746
1362
  #members = new Map;
747
- #guards = new Map;
748
- #mapEntries = new Map;
749
1363
  #routeFiles = new Map;
750
- #declarations = new Map;
751
1364
  #routes;
1365
+ #resolver;
752
1366
  #adaptors = new Map;
753
1367
  #plugs = new Map;
1368
+ #serviceFields = new Set;
754
1369
  constructor(source) {
755
1370
  this.#source = source;
756
1371
  this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
1372
+ this.#resolver = new GuardResolver(source, this.#routes);
757
1373
  }
758
1374
  build() {
759
1375
  const { facts } = this.#source;
@@ -765,17 +1381,28 @@ class GraphBuilder {
765
1381
  ...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
766
1382
  ];
767
1383
  const typed = [];
1384
+ const adaptorCounts = new Map;
768
1385
  for (const file of facts)
769
1386
  for (const fact of file.adaptors) {
770
- const declared = { fact, owner: file.owner, fileName: file.fileName };
771
- this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
772
- this.#adaptorNodeOf(declared);
1387
+ const key = this.#key("adaptor", file.owner, fact.name);
1388
+ adaptorCounts.set(key, (adaptorCounts.get(key) ?? 0) + 1);
773
1389
  }
1390
+ const declaredAdaptors = facts.flatMap((file) => file.adaptors.map((fact) => {
1391
+ const key = this.#key("adaptor", file.owner, fact.name);
1392
+ const id = (adaptorCounts.get(key) ?? 0) > 1 ? this.#key("adaptor", file.owner, `${this.#resolver.inOwner(file.fileName, file.owner)}#${fact.name}`) : key;
1393
+ const declared = { fact, owner: file.owner, fileName: file.fileName, id };
1394
+ this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
1395
+ this.#adaptorNodeOf(declared);
1396
+ return { declared, file };
1397
+ }));
1398
+ for (const { declared, file } of declaredAdaptors)
1399
+ for (const plug of declared.fact.plugs)
1400
+ this.#addEdge(declared.id, this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
774
1401
  for (const file of facts)
775
- for (const fact of file.adaptors)
776
- for (const plug of fact.plugs)
777
- this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
778
- this.#declareGuards(facts);
1402
+ for (const binding of file.adaptorBindings)
1403
+ this.#addEdge(this.#adaptorIdOf(binding.role, file), this.#adaptorIdOf(binding.adaptor, file), "binds", file.owner, this.#location(binding.span));
1404
+ for (const guard of this.#resolver.declare(facts))
1405
+ this.#guardNodeOf(guard);
779
1406
  for (const file of facts) {
780
1407
  const { owner } = file;
781
1408
  const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
@@ -806,10 +1433,18 @@ class GraphBuilder {
806
1433
  }
807
1434
  for (const method of service.methods)
808
1435
  this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
809
- for (const plug of service.plugs)
810
- this.#plugs.set(`${owner ?? ""}
1436
+ for (const plug of service.plugs) {
1437
+ const key = `${refName}
1438
+ ${plug.name}`;
1439
+ this.#plugs.set(key, [
1440
+ ...this.#plugs.get(key) ?? [],
1441
+ { owner, target: this.#adaptorIdOf(plug.target, file) }
1442
+ ]);
1443
+ }
1444
+ for (const field of service.fields)
1445
+ this.#serviceFields.add(`${owner ?? ""}
811
1446
  ${refName}
812
- ${plug.name}`, this.#adaptorIdOf(plug.target, file));
1447
+ ${field}`);
813
1448
  }
814
1449
  for (const filter of file.filters) {
815
1450
  if (!filter.name.endsWith("Filter"))
@@ -819,31 +1454,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
819
1454
  for (const query of filter.queries)
820
1455
  this.#digest(this.#addNode(this.#key("query", owner, refName, query.name), "query", owner, refName, query.name, query.span), this.#source.readText(file.fileName), query.extent);
821
1456
  }
822
- for (const slice of file.slices) {
823
- const refName = slice.model;
824
- if (!refName)
825
- continue;
826
- const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards);
827
- for (const guard of slice.guards)
828
- this.#guard(fallback, guard, file);
829
- for (const key of slice.keys) {
830
- const detail = [...key.guards, ...key.live ? ["live"] : []];
831
- const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
832
- for (const guard of key.guards)
833
- this.#guard(node, guard, file);
834
- }
835
- }
836
- for (const endpoint of file.endpoints) {
837
- const refName = endpoint.model;
838
- if (!refName)
839
- continue;
840
- for (const key of endpoint.keys) {
841
- const detail = [...key.verb ? [key.verb] : [], ...key.guards];
842
- const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
843
- for (const guard of key.guards)
844
- this.#guard(node, guard, file);
845
- }
846
- }
1457
+ this.#addGuarded(file);
847
1458
  for (const internal of file.internals)
848
1459
  for (const key of internal.keys)
849
1460
  if (internal.model && key.type !== "resolveField")
@@ -859,7 +1470,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
859
1470
  types.set(node.label, [...types.get(node.label) ?? [], node]);
860
1471
  for (const node of typed)
861
1472
  for (const field of node.fields ?? []) {
862
- const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#visible(type.owner, node.owner));
1473
+ const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#resolver.visible(type.owner, node.owner));
863
1474
  field.ref = (candidates.find((type) => type.owner === node.owner) ?? candidates[0])?.id ?? null;
864
1475
  if (field.ref)
865
1476
  this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
@@ -941,61 +1552,52 @@ ${label ?? ""}`;
941
1552
  else
942
1553
  this.#edges.set(key, { from, to, kind, label, count: 1, at });
943
1554
  }
944
- #guard(node, guard, file) {
945
- const [role, local] = guard.includes(" ") ? guard.split(" ") : [null, guard];
946
- if (!local || local === "?")
947
- return;
948
- this.#addEdge(node.id, this.#guardIdOf(local, file), "guard", role, node.at);
949
- }
950
- #declareGuards(facts) {
951
- const factories = new Map;
952
- for (const file of facts)
953
- for (const fact of file.guardFactories) {
954
- const factory = { fact, owner: file.owner, fileName: file.fileName, made: [] };
955
- factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
956
- }
957
- const sameFile = (factory, name) => name ? factories.get(name)?.find((other) => other.fileName === factory.fileName) : undefined;
958
- const declare = (guard, factory) => {
959
- this.#guards.set(guard.name, [...this.#guards.get(guard.name) ?? [], guard]);
960
- factory?.made.push(guard);
961
- };
962
- const madeBy = (factory, fileName, extent) => {
963
- const parts = [{ fileName, extent }];
964
- const seen = new Set;
965
- for (let current = factory;current && !seen.has(current); current = sameFile(current, current.fact.via)) {
966
- seen.add(current);
967
- parts.push({ fileName: current.fileName, extent: current.fact.extent });
968
- }
969
- return parts;
970
- };
971
- for (const file of facts) {
972
- const { owner, fileName } = file;
973
- for (const { name, span, extent, scope, doc, factory: callee } of file.guards) {
974
- const factory = callee ? factories.get(callee)?.find((other) => other.fileName === fileName) : undefined;
975
- declare({ name, owner, span, scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
1555
+ #addGuarded(file) {
1556
+ const { owner } = file;
1557
+ for (const slice of file.slices) {
1558
+ const refName = slice.model;
1559
+ if (!refName)
1560
+ continue;
1561
+ const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards.map((guard) => guardLabelOf(guard, file.guardLabels)));
1562
+ for (const guard of slice.guards)
1563
+ this.#guard(fallback, guard, file);
1564
+ for (const key of slice.keys) {
1565
+ const detail = [
1566
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels)),
1567
+ ...key.live ? ["live"] : []
1568
+ ];
1569
+ const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1570
+ for (const guard of key.guards)
1571
+ this.#guard(node, guard, file);
976
1572
  }
977
- for (const { name, span, extent, doc, callee } of file.guardCalls) {
978
- const imported = file.imports.find((item) => item.name === callee);
979
- if (!imported)
980
- continue;
981
- const from = this.#ownerOfSpecifier(fileName, imported.from);
982
- const candidates = factories.get(imported.imported === "default" || imported.imported === "*" ? callee : imported.imported) ?? [];
983
- const factory = candidates.find((other) => other.owner === from) ?? this.#soleVisible(candidates, from ?? owner);
984
- if (factory)
985
- declare({ name, owner, span, scope: factory.fact.scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
1573
+ }
1574
+ for (const endpoint of file.endpoints) {
1575
+ const refName = endpoint.model;
1576
+ if (!refName)
1577
+ continue;
1578
+ for (const key of endpoint.keys) {
1579
+ const detail = [
1580
+ ...key.verb ? [key.verb] : [],
1581
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels))
1582
+ ];
1583
+ const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1584
+ for (const guard of key.guards)
1585
+ this.#guard(node, guard, file);
986
1586
  }
987
1587
  }
988
- for (const list of factories.values())
989
- for (const { fact, made } of list)
990
- if (made.every((guard) => !guard.doc))
991
- for (const guard of made)
992
- guard.doc = fact.doc;
993
- for (const list of this.#guards.values())
994
- for (const guard of list)
995
- this.#guardNodeOf(guard);
1588
+ }
1589
+ #guard(node, guard, file) {
1590
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
1591
+ if (!local || local === "?")
1592
+ return;
1593
+ const id = local.includes("@") ? this.#madeInPlaceIdOf(local, file) : this.#guardIdOf(local, file);
1594
+ if (id)
1595
+ this.#addEdge(node.id, id, "guard", role, node.at);
996
1596
  }
997
1597
  #guardIdOf(local, site) {
998
- const found = this.#findGuard(local, site, 0);
1598
+ const found = this.#resolver.find(local, site);
1599
+ if ("external" in found)
1600
+ return null;
999
1601
  if ("guard" in found)
1000
1602
  return this.#guardNodeOf(found.guard);
1001
1603
  const { name, from } = found;
@@ -1008,99 +1610,18 @@ ${label ?? ""}`;
1008
1610
  node.doc = builtin.doc;
1009
1611
  return node.id;
1010
1612
  }
1011
- #findGuard(local, site, depth) {
1012
- const [head = local, member] = local.split(".");
1013
- const imported = site.imports.find((item) => item.name === head);
1014
- const from = imported ? this.#ownerOfSpecifier(site.fileName, imported.from) : undefined;
1015
- const holder = imported ? from : site.owner;
1016
- if (member && imported?.imported !== "*" && holder !== undefined && holder !== frameworkOwner && depth < maxMapDepth) {
1017
- const objectName = imported && imported.imported !== "default" ? imported.imported : head;
1018
- for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
1019
- const found = this.#findGuard(entry.name, entry.site, depth + 1);
1020
- if ("guard" in found)
1021
- return found;
1022
- }
1023
- }
1024
- const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1025
- const declared = this.#guards.get(name) ?? [];
1026
- const guard = from === undefined ? declared.find((other) => other.owner === site.owner) ?? declared.find((other) => this.#visible(other.owner, site.owner)) : declared.find((other) => other.owner === from) ?? (from === frameworkOwner ? undefined : this.#soleVisible(declared, from));
1027
- return guard ? { guard } : { name, from };
1028
- }
1029
- #mapEntriesOf(objectName, member, fileName, owner) {
1030
- const key = `${objectName}
1031
- ${member}
1032
- ${fileName ?? ""}
1033
- ${owner ?? ""}`;
1034
- const cached = this.#mapEntries.get(key);
1035
- if (cached)
1036
- return cached;
1037
- const { ts, reader } = this.#source;
1038
- const entries = [];
1039
- const fileNames = [
1040
- ...fileName ? [fileName] : [],
1041
- ...this.#source.files.filter(([other, words]) => other !== fileName && owner !== null && !testFilePattern.test(other) && words.includes(`
1042
- ${objectName}
1043
- `) && words.includes(`
1044
- ${member}
1045
- `) && this.#source.ownerOf(other) === owner).map(([other]) => other)
1046
- ];
1047
- for (const holder of fileNames) {
1048
- const text = this.#source.readText(holder);
1049
- if (text === undefined)
1050
- continue;
1051
- const source = this.#source.parse(holder, text);
1052
- const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
1053
- for (const statement of source.statements) {
1054
- if (!ts.isVariableStatement(statement))
1055
- continue;
1056
- const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
1057
- const object = map && reader.unwrap(map);
1058
- if (!object || !ts.isObjectLiteralExpression(object))
1059
- continue;
1060
- for (const property of object.properties) {
1061
- if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
1062
- entries.push({ name: member, site });
1063
- if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
1064
- continue;
1065
- const value = reader.unwrap(property.initializer);
1066
- if (ts.isIdentifier(value))
1067
- entries.push({ name: value.text, site });
1068
- else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
1069
- entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
1070
- }
1071
- }
1072
- }
1073
- this.#mapEntries.set(key, entries);
1074
- return entries;
1075
- }
1076
- #declarationOf(owner, name, near) {
1077
- const key = `${owner}
1078
- ${name}
1079
- ${near ?? ""}`;
1080
- const cached = this.#declarations.get(key);
1081
- if (cached !== undefined)
1082
- return cached;
1083
- const isNear = (fileName) => {
1084
- const relative = this.#source.relative(fileName);
1085
- return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1086
- };
1087
- const candidates = this.#source.files.filter(([fileName, words]) => !testFilePattern.test(fileName) && words.includes(`
1088
- ${name}
1089
- `) && this.#source.ownerOf(fileName) === owner).map(([fileName]) => fileName);
1090
- let found = null;
1091
- for (const fileName of [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))]) {
1092
- const text = this.#source.readText(fileName);
1093
- if (text === undefined)
1094
- continue;
1095
- const source = this.#source.parse(fileName, text);
1096
- const declared = this.#routes.declarationIn(source, name);
1097
- if (!declared)
1098
- continue;
1099
- found = { source, statement: declared.statement, declared: declared.declared };
1100
- break;
1101
- }
1102
- this.#declarations.set(key, found);
1103
- return found;
1613
+ #madeInPlaceIdOf(name, file) {
1614
+ const made = this.#resolver.madeInPlace(file.fileName, name);
1615
+ if (made)
1616
+ return this.#guardNodeOf(made);
1617
+ const head = name.slice(name.indexOf(":") + 1, name.indexOf("@")).split(".")[0] ?? name;
1618
+ const imported = file.imports.find((item) => item.name === head);
1619
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : file.owner;
1620
+ if (from === undefined || from === frameworkOwner)
1621
+ return null;
1622
+ const scope = `${this.#resolver.inOwner(file.fileName, file.owner)}#${name}`;
1623
+ const label = guardLabelOf(name, file.guardLabels);
1624
+ return this.#addNode(this.#key("guard", file.owner, scope), "guard", file.owner, null, name, null, [], label).id;
1104
1625
  }
1105
1626
  #addRoutes() {
1106
1627
  const routes = [];
@@ -1153,22 +1674,23 @@ ${dir}`, node);
1153
1674
  }
1154
1675
  #routeGuardIdOf({ name: local, local: declared }, fact, text) {
1155
1676
  if (declared) {
1156
- const file = this.#source.relative(fact.fileName).slice(fact.owner.length + 1);
1157
- const id = this.#key("guard", fact.owner, `${file}#${local}`);
1677
+ const id = this.#key("guard", fact.owner, `${this.#resolver.inOwner(fact.fileName, fact.owner)}#${local}`);
1158
1678
  return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
1159
1679
  }
1160
- const imported = fact.imports.find((item) => item.name === local);
1161
- if (!imported)
1680
+ const [head = local, member] = local.split(".");
1681
+ const imported = fact.imports.find((item) => item.name === head);
1682
+ if (!imported || member && imported.imported !== "*")
1162
1683
  return null;
1163
- const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
1164
- const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
1684
+ const name = member ?? (imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local);
1685
+ const from = this.#resolver.ownerOfSpecifier(fact.fileName, imported.from);
1165
1686
  if (!from || from === frameworkOwner)
1166
1687
  return null;
1167
- const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
1688
+ const found = this.#resolver.declarationOf(from, name, this.#resolver.pathOfSpecifier(fact.fileName, imported.from));
1168
1689
  if (!found)
1169
1690
  return null;
1170
1691
  const { reader } = this.#source;
1171
- return this.#routeGuardNodeOf(this.#key("guard", from, name), from, name, reader.spanOf(found.source, found.declared), reader.docOf(found.source, found.statement), found.source.text, reader.extentOf(found.source, found.statement));
1692
+ const owner = this.#source.ownerOf(found.source.fileName) ?? from;
1693
+ return this.#routeGuardNodeOf(this.#key("guard", owner, `${this.#resolver.inOwner(found.source.fileName, owner)}#${found.name}`), owner, found.name, reader.spanOf(found.source, found.declared), reader.docOf(found.source, found.statement), found.source.text, found.extent);
1172
1694
  }
1173
1695
  #routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
1174
1696
  const isNew = !this.#nodes.has(id);
@@ -1181,18 +1703,70 @@ ${dir}`, node);
1181
1703
  #adaptorIdOf(local, file) {
1182
1704
  const [head, member] = local.split(".");
1183
1705
  const imported = file.imports.find((item) => item.name === head);
1184
- const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
1185
1706
  const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1707
+ const path = imported ? this.#resolver.pathOfSpecifier(file.fileName, imported.from) : null;
1708
+ if (imported && path === null && !frameworkPattern.test(imported.from))
1709
+ return this.#packageAdaptorOf(name, imported.from);
1710
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
1186
1711
  const declared = this.#adaptors.get(name) ?? [];
1187
- const found = from === undefined ? declared.find((adaptor) => adaptor.owner === file.owner) ?? declared.find((adaptor) => this.#visible(adaptor.owner, file.owner)) : declared.find((adaptor) => adaptor.owner === from) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#visible(adaptor.owner, from)));
1712
+ 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)));
1188
1713
  if (found)
1189
- return this.#adaptorNodeOf(found);
1714
+ return found.id;
1715
+ const reexported = from && from !== frameworkOwner ? this.#packageShimOf(from, name, path) : null;
1716
+ if (reexported)
1717
+ return this.#packageAdaptorOf(reexported.name, reexported.from);
1190
1718
  const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
1191
1719
  const detail = from === frameworkOwner ? [frameworkOwner] : [];
1192
1720
  return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
1193
1721
  }
1194
- #adaptorNodeOf({ fact, owner, fileName }) {
1195
- const id = this.#key("adaptor", owner, fact.name);
1722
+ #adaptorIn(declared, owner, path, fileName) {
1723
+ const own = declared.filter((adaptor) => adaptor.owner === owner);
1724
+ if (own.length < 2)
1725
+ return own.at(0);
1726
+ const pathOf = (adaptor) => this.#source.relative(adaptor.fileName).replace(/\.tsx?$/, "");
1727
+ 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}/`));
1728
+ return named ?? own.at(0);
1729
+ }
1730
+ #pluggedOf({ owner, module }, field) {
1731
+ const plugs = this.#plugs.get(`${module}
1732
+ ${field}`) ?? [];
1733
+ const own = plugs.filter((plug) => plug.owner === owner);
1734
+ if (!own.length && this.#serviceFields.has(`${owner ?? ""}
1735
+ ${module}
1736
+ ${field}`))
1737
+ return [];
1738
+ const inherited = own.length ? own : plugs.filter((plug) => this.#resolver.visible(plug.owner, owner));
1739
+ return [...new Set(inherited.map(({ target }) => target))];
1740
+ }
1741
+ #packageAdaptorOf(name, specifier) {
1742
+ if (frameworkPattern.test(specifier))
1743
+ return this.#addNode(this.#key("adaptor", null, name), "adaptor", null, null, name, null, [frameworkOwner]).id;
1744
+ const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
1745
+ return this.#addNode(this.#key("adaptor", pkg, name), "adaptor", null, null, name, null, [pkg]).id;
1746
+ }
1747
+ #packageShimOf(owner, name, near) {
1748
+ const reexport = this.#resolver.reexportOf(owner, name, near);
1749
+ const [shim] = reexport?.site.imports ?? [];
1750
+ if (reexport && shim && this.#resolver.pathOfSpecifier(reexport.site.fileName, shim.from) === null)
1751
+ return { name: reexport.local === "default" ? name : reexport.local, from: shim.from };
1752
+ if (reexport || !near)
1753
+ return null;
1754
+ const packages = new Set;
1755
+ for (const [fileName, words] of this.#resolver.filesIn(owner)) {
1756
+ const relative = this.#source.relative(fileName);
1757
+ const isNear = relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near;
1758
+ if (!isNear || !words.includes(`
1759
+ export
1760
+ `))
1761
+ continue;
1762
+ for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? [])
1763
+ if (this.#resolver.pathOfSpecifier(fileName, specifier) === null)
1764
+ packages.add(specifier);
1765
+ }
1766
+ const [only] = packages;
1767
+ return packages.size === 1 && only && !this.#resolver.declares(owner, name) ? { name, from: only } : null;
1768
+ }
1769
+ #adaptorNodeOf({ fact, owner, fileName, id }) {
1196
1770
  const detail = fact.refName ? [fact.refName] : [];
1197
1771
  const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
1198
1772
  if (fact.doc && !node.doc)
@@ -1200,38 +1774,22 @@ ${dir}`, node);
1200
1774
  this.#digest(node, this.#source.readText(fileName), fact.extent);
1201
1775
  return node.id;
1202
1776
  }
1203
- #guardNodeOf({ name, owner, span, scope, doc, parts }) {
1204
- const node = this.#addNode(this.#key("guard", owner, name), "guard", owner, null, name, span, scope ? [scope] : []);
1777
+ #guardNodeOf({ name, owner, span, scope, doc, parts, file, label }) {
1778
+ const id = this.#key("guard", owner, file ? `${file}#${name}` : name);
1779
+ const node = this.#addNode(id, "guard", owner, null, name, span, scope ? [scope] : [], label ?? name);
1205
1780
  if (doc && !node.doc)
1206
1781
  node.doc = doc;
1207
- const digest = node.digest ? null : this.#hashOf(...parts.map(({ fileName, extent }) => [
1208
- this.#source.readText(fileName),
1209
- extent
1210
- ]));
1211
- if (digest)
1212
- node.digest = digest;
1782
+ if (node.digest)
1783
+ return node.id;
1784
+ const digest = this.#hashOf(...parts.map(({ fileName, extent }) => [this.#source.readText(fileName), extent]));
1785
+ if (!digest)
1786
+ return node.id;
1787
+ node.digest = digest;
1788
+ const sources = [...new Set(parts.map(({ fileName }) => this.#source.relative(fileName)))].filter((source) => source !== node.at?.file);
1789
+ if (sources.length)
1790
+ node.sources = sources;
1213
1791
  return node.id;
1214
1792
  }
1215
- #ownerOfSpecifier(fileName, specifier) {
1216
- if (frameworkPattern.test(specifier))
1217
- return frameworkOwner;
1218
- const path = this.#pathOfSpecifier(fileName, specifier);
1219
- return path === null ? undefined : ownerOf(path) ?? undefined;
1220
- }
1221
- #pathOfSpecifier(fileName, specifier) {
1222
- const alias = specifier.match(aliasPattern);
1223
- if (alias)
1224
- return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1225
- if (!specifier.startsWith("."))
1226
- return null;
1227
- const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1228
- for (const segment of specifier.split("/"))
1229
- if (segment === "..")
1230
- segments.pop();
1231
- else if (segment !== ".")
1232
- segments.push(segment);
1233
- return segments.join("/");
1234
- }
1235
1793
  #moduleDirOf(fileName) {
1236
1794
  const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
1237
1795
  return dir?.startsWith("_") && !dir.startsWith("__") ? dir.slice(1) : dir;
@@ -1267,20 +1825,13 @@ ${dir}`, node);
1267
1825
  visit(constant);
1268
1826
  return [...fields.values()];
1269
1827
  }
1270
- #visible(target, from) {
1271
- return !from || !target || target === from || target.startsWith("libs/");
1272
- }
1273
- #soleVisible(declared, from) {
1274
- const visible = declared.filter((other) => this.#visible(other.owner, from));
1275
- return visible.length === 1 ? visible[0] : undefined;
1276
- }
1277
1828
  #targetNodeOf({ family, owner, refName, sourceKey }) {
1278
1829
  const isDefault = /^[A-Z]/.test(sourceKey);
1279
1830
  const id = family === "slice" || family === "query" ? this.#key(family, owner, refName, isDefault ? "" : sourceKey) : family === "model" || family === "formSetter" ? this.#key("model", owner, refName) : family === "endpoint" ? this.#key("endpoint", owner, refName, sourceKey) : null;
1280
1831
  return id && this.#nodes.has(id) ? id : null;
1281
1832
  }
1282
1833
  #membersNamed(kind, name, owner, refName) {
1283
- const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#visible(node.owner, owner));
1834
+ const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#resolver.visible(node.owner, owner));
1284
1835
  const own = nodes.filter((node) => node.owner === owner);
1285
1836
  return (own.length ? own : nodes).map((node) => node.id);
1286
1837
  }
@@ -1325,10 +1876,8 @@ st
1325
1876
  visit(source);
1326
1877
  }
1327
1878
  #link(fileName, source, identifier, chain, context, owner) {
1328
- const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#plugs.get(`${context.owner ?? ""}
1329
- ${context.module}
1330
- ${chain[1]}`) : undefined;
1331
- const resolved = plugged ? [plugged] : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
1879
+ const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#pluggedOf(context, chain[1]) : [];
1880
+ const resolved = plugged.length ? plugged : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
1332
1881
  const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
1333
1882
  if (!targets.length)
1334
1883
  return;
@@ -1634,6 +2183,10 @@ var endpointNames = (key) => {
1634
2183
  };
1635
2184
 
1636
2185
  // pkgs/@akanjs/language/nameTable.ts
2186
+ var guardDetailOf = (facts, guards) => {
2187
+ const labels = Object.assign({}, ...facts.map((file) => file.guardLabels));
2188
+ return guards.length ? [`guards: ${guards.map((guard) => guardLabelOf(guard, labels)).join(", ")}`] : [];
2189
+ };
1637
2190
  var capitalize3 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
1638
2191
 
1639
2192
  class NameTable {
@@ -1733,7 +2286,7 @@ class NameTable {
1733
2286
  role: "sig",
1734
2287
  gate: "sig",
1735
2288
  label: sliceClass.name,
1736
- detail: sliceClass.guards.length ? [`guards: ${sliceClass.guards.join(", ")}`] : []
2289
+ detail: guardDetailOf(facts, sliceClass.guards)
1737
2290
  });
1738
2291
  if (modelDoc)
1739
2292
  this.#add({
@@ -1798,7 +2351,7 @@ class NameTable {
1798
2351
  sourceKey: slice.name,
1799
2352
  span: slice.span,
1800
2353
  label: `slice ${refName} (default)`,
1801
- detail: slice.guards.length ? [`guards: ${slice.guards.join(", ")}`] : []
2354
+ detail: guardDetailOf(facts, slice.guards)
1802
2355
  };
1803
2356
  this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
1804
2357
  for (const key of slice.keys)
@@ -1808,7 +2361,7 @@ class NameTable {
1808
2361
  sourceKey: key.name,
1809
2362
  span: key.span,
1810
2363
  label: `slice ${key.name}`,
1811
- detail: [...key.guards.length ? [`guards: ${key.guards.join(", ")}`] : [], ...key.live ? ["live"] : []]
2364
+ detail: [...guardDetailOf(facts, key.guards), ...key.live ? ["live"] : []]
1812
2365
  }, refName, key.name, modelNameSet, slice.name);
1813
2366
  }
1814
2367
  if (filterClass) {
@@ -1865,7 +2418,7 @@ class NameTable {
1865
2418
  role: key.verb === "pubsub" ? "subscribe" : "listen",
1866
2419
  gate: null,
1867
2420
  label: `${key.verb} ${key.name}`,
1868
- detail: key.guards.length ? [`guards: ${key.guards.join(", ")}`] : []
2421
+ detail: guardDetailOf(facts, key.guards)
1869
2422
  });
1870
2423
  }
1871
2424
  }
@@ -1923,14 +2476,12 @@ var dictionaryPattern = /\.dictionary\.ts$/;
1923
2476
  var srvkitPattern = /\/srvkit\//;
1924
2477
  var wordPattern = /[A-Za-z_$][\w$]*/g;
1925
2478
  var identifierPattern = /^[A-Za-z_$][\w$]*$/;
1926
- var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
1927
2479
  var stateReceivers = new Set(["use", "sel", "get", "do"]);
1928
2480
  var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
1929
2481
  var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
1930
2482
  var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
1931
2483
  var parseCacheSize = 64;
1932
2484
  var pathOf = (fileName) => fileName.replace(/\\/g, "/");
1933
- var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
1934
2485
  var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
1935
2486
 
1936
2487
  class AkanIndex {
@@ -2061,6 +2612,8 @@ canPass
2061
2612
  adapt
2062
2613
  `) || words.includes(`
2063
2614
  dangerouslyAdapt
2615
+ `) || words.includes(`
2616
+ applyAdaptor
2064
2617
  `);
2065
2618
  }
2066
2619
  static #sourceKeyOf({ family, sourceKey, label, detail, span }) {