@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.js CHANGED
@@ -8,7 +8,8 @@ var isEdited = (before, after) => before.digest !== undefined && after.digest !=
8
8
  var byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
9
9
 
10
10
  class AkanGraphDiff {
11
- static between(base, head) {
11
+ static between(given, head) {
12
+ const base = AkanGraphDiff.#renamed(given, head);
12
13
  const baseNodes = new Map(base.nodes.map((node) => [node.id, node]));
13
14
  const headNodes = new Map(head.nodes.map((node) => [node.id, node]));
14
15
  const marked = new Map;
@@ -27,6 +28,9 @@ class AkanGraphDiff {
27
28
  ...head.edges.flatMap((edge) => !baseKeys.has(edgeKeyOf(edge)) ? [{ ...edge, change: "added" }] : marked.has(edge.from) || marked.has(edge.to) ? [edge] : []),
28
29
  ...base.edges.filter((edge) => !headKeys.has(edgeKeyOf(edge))).map((edge) => ({ ...edge, change: "removed" }))
29
30
  ];
31
+ const shown = new Set([...marked.keys(), ...edges.flatMap((edge) => [edge.from, edge.to])]);
32
+ const listed = new Set(edges.map(edgeKeyOf));
33
+ edges.push(...head.edges.filter((edge) => edge.kind === "binds" && shown.has(edge.from) && !listed.has(edgeKeyOf(edge))));
30
34
  const context = edges.flatMap((edge) => [edge.from, edge.to]).filter((id) => !marked.has(id)).flatMap((id) => {
31
35
  const node = headNodes.get(id) ?? baseNodes.get(id);
32
36
  return node ? [node] : [];
@@ -38,6 +42,40 @@ class AkanGraphDiff {
38
42
  summary[node.change] += 1;
39
43
  return { version: 1, nodes, edges, summary };
40
44
  }
45
+ static #renamed(base, head) {
46
+ const baseIds = new Set(base.nodes.map((node) => node.id));
47
+ const headIds = new Set(head.nodes.map((node) => node.id));
48
+ const pairKeyOf = ({ kind, owner, name, at }) => at ? `${kind}
49
+ ${owner ?? ""}
50
+ ${name}
51
+ ${at.file}` : null;
52
+ const shortIdOf = ({ kind, owner, name }) => `${kind}:${owner ?? ""}:${name}`;
53
+ const isFileKeyed = (node) => node.id !== shortIdOf(node) && node.id.startsWith(`${node.kind}:${node.owner ?? ""}:`) && node.id.endsWith(`#${node.name}`);
54
+ const fromBase = new Map(base.nodes.flatMap((node) => {
55
+ const key = headIds.has(node.id) ? null : pairKeyOf(node);
56
+ return key ? [[key, node]] : [];
57
+ }));
58
+ const renames = new Map;
59
+ for (const node of head.nodes) {
60
+ if (baseIds.has(node.id))
61
+ continue;
62
+ const key = pairKeyOf(node);
63
+ const before = key ? fromBase.get(key) : undefined;
64
+ if (!before)
65
+ continue;
66
+ const isPair = isFileKeyed(node) ? before.id === shortIdOf(node) : isFileKeyed(before) && node.id === shortIdOf(before);
67
+ if (isPair)
68
+ renames.set(before.id, node.id);
69
+ }
70
+ if (!renames.size)
71
+ return base;
72
+ const rename = (id) => renames.get(id) ?? id;
73
+ return {
74
+ ...base,
75
+ nodes: base.nodes.map((node) => renames.has(node.id) ? { ...node, id: rename(node.id) } : node),
76
+ edges: base.edges.map((edge) => ({ ...edge, from: rename(edge.from), to: rename(edge.to) }))
77
+ };
78
+ }
41
79
  static #mark(node, change) {
42
80
  return node.fields ? { ...node, change, fields: node.fields.map((field) => ({ ...field, change })) } : { ...node, change };
43
81
  }
@@ -71,6 +109,239 @@ class AkanGraphDiff {
71
109
  return node.fields ? { ...node, fields: node.fields.map(({ digest: _field, ...field }) => field) } : node;
72
110
  }
73
111
  }
112
+ // pkgs/@akanjs/language/guardFacts.ts
113
+ var guardLabelOf = (guard, labels) => {
114
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
115
+ const label = labels[local];
116
+ return label === undefined ? guard : role ? `${role} ${label}` : label;
117
+ };
118
+ var maxLabelArgs = 32;
119
+ var guardRefReaderOf = (ts, ast, unwrap) => {
120
+ const pathOf = (expression) => {
121
+ if (ts.isIdentifier(expression))
122
+ return expression.text;
123
+ if (!ts.isPropertyAccessExpression(expression))
124
+ return null;
125
+ const head = pathOf(expression.expression);
126
+ return head && `${head}.${expression.name.text}`;
127
+ };
128
+ const calleeOf = (call) => pathOf(unwrap(call.expression)) ?? "?";
129
+ const labelOf = (source, call) => {
130
+ const args = call.arguments.map((arg) => arg.getText(source)).join(", ");
131
+ return `${calleeOf(call)}(${args.length <= maxLabelArgs && !args.includes(`
132
+ `) ? args : "…"})`;
133
+ };
134
+ const guardRefOf = (expression, made, member) => {
135
+ const value = unwrap(expression);
136
+ if (!ts.isCallExpression(value))
137
+ return pathOf(value) ?? ast.expressionName(value);
138
+ const prefix = `${member}:${calleeOf(value)}@`;
139
+ const name = `${prefix}${made.filter((other) => other.name.startsWith(prefix)).length + 1}`;
140
+ made.push({ call: value, name });
141
+ return name;
142
+ };
143
+ const guardsIn = (source, object, made, member) => {
144
+ const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
145
+ if (!guards)
146
+ return [];
147
+ const refOf = (expression) => guardRefOf(expression, made, member);
148
+ if (ts.isArrayLiteralExpression(guards))
149
+ return guards.elements.map(refOf).filter((name) => name !== null);
150
+ if (ts.isObjectLiteralExpression(guards))
151
+ return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
152
+ const value = unwrap(property.initializer);
153
+ const names = ts.isArrayLiteralExpression(value) ? value.elements.map(refOf) : [refOf(value)];
154
+ return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
155
+ });
156
+ const single = refOf(guards);
157
+ return single ? [single] : [];
158
+ };
159
+ return { pathOf, labelOf, guardsIn };
160
+ };
161
+ var guardFactoryReaderOf = (ts, ast, { spanOf, extentOf, unwrap, docOf }, pathOf) => {
162
+ const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
163
+ const scopeOf = (node) => {
164
+ const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
165
+ const value = scope && unwrap(scope);
166
+ return value && ts.isStringLiteralLike(value) ? value.text : null;
167
+ };
168
+ const functionsOf = (source) => {
169
+ const functions = new Map;
170
+ for (const statement of source.statements) {
171
+ if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
172
+ functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
173
+ if (!ts.isVariableStatement(statement))
174
+ continue;
175
+ for (const declaration of statement.declarationList.declarations) {
176
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
177
+ if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
178
+ functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
179
+ }
180
+ }
181
+ return functions;
182
+ };
183
+ const returnedOf = (body, guardNames) => {
184
+ const locals = new Map;
185
+ const pending = [];
186
+ const visit = (node) => {
187
+ if (ts.isClassDeclaration(node) && node.name)
188
+ locals.set(node.name.text, node);
189
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
190
+ locals.set(node.name.text, node.initializer);
191
+ if (ts.isReturnStatement(node) && node.expression)
192
+ pending.push(node.expression);
193
+ if (!ts.isFunctionLike(node) && !ts.isClassLike(node))
194
+ ts.forEachChild(node, visit);
195
+ };
196
+ if (ts.isBlock(body))
197
+ ts.forEachChild(body, visit);
198
+ else
199
+ pending.push(body);
200
+ let guard = null;
201
+ const calls = [];
202
+ const seen = new Set;
203
+ const passed = new Set;
204
+ for (let node = pending.pop();node; node = pending.pop()) {
205
+ if (seen.has(node))
206
+ continue;
207
+ seen.add(node);
208
+ const value = ts.isExpression(node) ? unwrap(node) : node;
209
+ if ((ts.isClassExpression(value) || ts.isClassDeclaration(value)) && implementsGuard(value, guardNames))
210
+ guard ??= value;
211
+ else if (ts.isConditionalExpression(value))
212
+ pending.push(value.whenTrue, value.whenFalse);
213
+ else if (ts.isBinaryExpression(value))
214
+ pending.push(value.left, value.right);
215
+ else if (ts.isIdentifier(value)) {
216
+ const local = locals.get(value.text);
217
+ if (local)
218
+ pending.push(local);
219
+ } else if (ts.isCallExpression(value)) {
220
+ const callee = pathOf(unwrap(value.expression));
221
+ if (callee && !passed.has(node) && !calls.includes(callee))
222
+ calls.push(callee);
223
+ for (const arg of value.arguments)
224
+ passed.add(arg);
225
+ pending.push(...value.arguments);
226
+ }
227
+ }
228
+ return { guard, calls };
229
+ };
230
+ const guardFactoriesOf = (source, guardNames, isImported) => {
231
+ const functions = functionsOf(source);
232
+ const returned = new Map([...functions].map(([name, { body }]) => [name, returnedOf(body, guardNames)]));
233
+ const members = new Set;
234
+ const isFactoryCall = (callee) => members.has(callee) || !functions.has(callee) && isImported(callee);
235
+ for (let isGrowing = true;isGrowing; ) {
236
+ isGrowing = false;
237
+ for (const [name, { guard, calls }] of returned) {
238
+ if (members.has(name) || !guard && !calls.some(isFactoryCall))
239
+ continue;
240
+ members.add(name);
241
+ isGrowing = true;
242
+ }
243
+ }
244
+ const factories = new Map;
245
+ for (const name of members) {
246
+ const found = functions.get(name);
247
+ const made = returned.get(name);
248
+ if (!found || !made)
249
+ continue;
250
+ factories.set(name, {
251
+ name,
252
+ span: spanOf(source, found.name),
253
+ extent: extentOf(source, found.statement),
254
+ scope: made.guard ? scopeOf(made.guard) : null,
255
+ doc: docOf(source, found.statement),
256
+ direct: !!made.guard,
257
+ via: made.calls.filter(isFactoryCall)
258
+ });
259
+ }
260
+ return factories;
261
+ };
262
+ return { implementsGuard, scopeOf, guardFactoriesOf };
263
+ };
264
+ var createGuardFactReader = (ts, ast, base) => {
265
+ const { spanOf, extentOf, unwrap, docOf } = base;
266
+ const { pathOf, labelOf, guardsIn } = guardRefReaderOf(ts, ast, unwrap);
267
+ const { implementsGuard, scopeOf, guardFactoriesOf } = guardFactoryReaderOf(ts, ast, base, pathOf);
268
+ const guardsOf = (source, imports, madeInPlace) => {
269
+ const guardNames = new Set([
270
+ "Guard",
271
+ ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
272
+ ]);
273
+ const imported = new Set(imports.map(({ name }) => name));
274
+ const isImported = (callee) => imported.has(callee.split(".")[0] ?? callee);
275
+ const factories = guardFactoriesOf(source, guardNames, isImported);
276
+ const guards = [];
277
+ const calls = [];
278
+ const makeOf = (made, value, isInPlace = false) => {
279
+ if (ts.isClassExpression(value) && implementsGuard(value, guardNames)) {
280
+ guards.push({ ...made, scope: scopeOf(value), factory: null });
281
+ return;
282
+ }
283
+ const callee = ts.isCallExpression(value) ? pathOf(unwrap(value.expression)) : null;
284
+ const factory = callee && factories.get(callee);
285
+ if (factory)
286
+ guards.push({ ...made, scope: factory.scope, factory: factory.name });
287
+ else if (callee && (isImported(callee) || isInPlace))
288
+ calls.push({ ...made, callee, kind: "call", scope: null });
289
+ };
290
+ for (const statement of source.statements) {
291
+ if (ts.isClassDeclaration(statement) && statement.name) {
292
+ const declared = {
293
+ name: statement.name.text,
294
+ span: spanOf(source, statement.name),
295
+ extent: extentOf(source, statement),
296
+ scope: scopeOf(statement),
297
+ doc: docOf(source, statement)
298
+ };
299
+ const base = statement.heritageClauses?.find(({ token }) => token === ts.SyntaxKind.ExtendsKeyword)?.types[0];
300
+ const callee = base && pathOf(base.expression);
301
+ if (implementsGuard(statement, guardNames))
302
+ guards.push({ ...declared, factory: null });
303
+ else if (callee)
304
+ calls.push({ ...declared, callee, kind: "extends" });
305
+ }
306
+ if (!ts.isVariableStatement(statement))
307
+ continue;
308
+ for (const declaration of statement.declarationList.declarations) {
309
+ const initializer = declaration.initializer && unwrap(declaration.initializer);
310
+ if (!initializer || !ts.isIdentifier(declaration.name))
311
+ continue;
312
+ const holder = declaration.name.text;
313
+ makeOf({
314
+ name: holder,
315
+ span: spanOf(source, declaration.name),
316
+ extent: extentOf(source, statement),
317
+ doc: docOf(source, statement)
318
+ }, initializer);
319
+ if (!ts.isObjectLiteralExpression(initializer))
320
+ continue;
321
+ for (const property of initializer.properties) {
322
+ const key = ts.isPropertyAssignment(property) ? ast.nodeName(property.name) : null;
323
+ if (key && ts.isPropertyAssignment(property))
324
+ makeOf({
325
+ name: `${holder}.${key}`,
326
+ span: spanOf(source, property.name),
327
+ extent: extentOf(source, property),
328
+ doc: docOf(source, property)
329
+ }, unwrap(property.initializer));
330
+ }
331
+ }
332
+ }
333
+ for (const { call, name } of madeInPlace)
334
+ makeOf({ name, span: spanOf(source, call), extent: extentOf(source, call), doc: null }, call, true);
335
+ const guardLabels = Object.fromEntries(madeInPlace.map(({ call, name }) => [name, labelOf(source, call)]));
336
+ return { guards, guardFactories: [...factories.values()], guardCalls: calls, guardLabels };
337
+ };
338
+ return { guardsIn, guardsOf };
339
+ };
340
+
341
+ // pkgs/@akanjs/language/escapeRegExp.ts
342
+ var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
343
+ var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
344
+
74
345
  // pkgs/@akanjs/language/moduleFacts.ts
75
346
  var testFilePattern = /\.(?:test|spec)\.tsx?$/;
76
347
  var moduleCallees = new Set([
@@ -139,21 +410,6 @@ var createFactReader = (ts, ast) => {
139
410
  const value = unwrap(expression);
140
411
  return ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression) ? `${value.expression.text}.${value.name.text}` : ast.expressionName(value);
141
412
  };
142
- const guardsIn = (object) => {
143
- const guards = object?.properties.find((property) => ts.isPropertyAssignment(property) && ast.nodeName(property.name) === "guards")?.initializer;
144
- if (!guards)
145
- return [];
146
- if (ts.isArrayLiteralExpression(guards))
147
- return guards.elements.map(guardNameOf).filter((name) => name !== null);
148
- if (ts.isObjectLiteralExpression(guards))
149
- return guards.properties.filter(ts.isPropertyAssignment).flatMap((property) => {
150
- const value = unwrap(property.initializer);
151
- const names = ts.isArrayLiteralExpression(value) ? value.elements.map(guardNameOf) : [guardNameOf(value)];
152
- return names.map((name) => `${ast.nodeName(property.name)} ${name ?? "?"}`);
153
- });
154
- const single = guardNameOf(guards);
155
- return single ? [single] : [];
156
- };
157
413
  const optionObjectOf = (call) => call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) && arg.properties.some((property) => ast.propertyName(property) === "guards"));
158
414
  const chainOf = (initializer) => {
159
415
  let root = unwrap(initializer);
@@ -170,7 +426,7 @@ var createFactReader = (ts, ast) => {
170
426
  const node = keyName(property);
171
427
  return name && node ? [{ name, span: spanOf(source, node), extent: extentOf(source, property) }] : [];
172
428
  });
173
- const sliceKeysOf = (source, builder) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
429
+ const sliceKeysOf = (source, builder, made, model) => (returnedObject(builder)?.properties ?? []).flatMap((property) => {
174
430
  if (!ts.isPropertyAssignment(property))
175
431
  return [];
176
432
  const name = ast.nodeName(property.name);
@@ -182,7 +438,7 @@ var createFactReader = (ts, ast) => {
182
438
  name,
183
439
  span: spanOf(source, property.name),
184
440
  extent: extentOf(source, property),
185
- guards: root ? guardsIn(optionObjectOf(root)) : [],
441
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
186
442
  live: methods.includes("live"),
187
443
  argCount: methods.filter((method) => argMethods.has(method)).length
188
444
  }
@@ -232,7 +488,7 @@ var createFactReader = (ts, ast) => {
232
488
  const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
233
489
  return `${ms / size}${unit}`;
234
490
  };
235
- const modesOf = (option) => {
491
+ const modesOf = (option, type) => {
236
492
  const object = option && unwrap(option);
237
493
  if (!object || !ts.isObjectLiteralExpression(object))
238
494
  return [];
@@ -242,9 +498,9 @@ var createFactReader = (ts, ast) => {
242
498
  if (name === "enabled")
243
499
  return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
244
500
  if (name === "once")
245
- return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
501
+ return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
246
502
  if (name === "lock")
247
- return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
503
+ return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
248
504
  if (name !== "serverMode" && name !== "operationMode")
249
505
  return [];
250
506
  const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
@@ -262,7 +518,8 @@ var createFactReader = (ts, ast) => {
262
518
  const { root } = chainOf(property.initializer);
263
519
  const type = root ? verbOf(root.expression) : null;
264
520
  const [first, second] = root?.arguments ?? [];
265
- const schedule = type === "cron" && first ? ts.isStringLiteralLike(first) ? first.text : first.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
521
+ const cron = type === "cron" && first ? unwrap(first) : null;
522
+ const schedule = cron ? ts.isStringLiteralLike(cron) ? cron.text : cron.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
266
523
  const option = type === "initialize" || type === "destroy" ? first : second;
267
524
  return [
268
525
  {
@@ -271,7 +528,7 @@ var createFactReader = (ts, ast) => {
271
528
  extent: extentOf(source, property),
272
529
  type,
273
530
  schedule,
274
- modes: modesOf(option)
531
+ modes: modesOf(option, type)
275
532
  }
276
533
  ];
277
534
  });
@@ -288,7 +545,20 @@ var createFactReader = (ts, ast) => {
288
545
  return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
289
546
  });
290
547
  };
291
- const endpointKeysOf = (source, builder) => {
548
+ const adaptorBindingsIn = (source) => {
549
+ const bindings = [];
550
+ const visit = (node) => {
551
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "applyAdaptor") {
552
+ const [role, adaptor] = node.arguments.map(guardNameOf);
553
+ if (role && adaptor)
554
+ bindings.push({ role, adaptor, span: spanOf(source, node.expression.name) });
555
+ }
556
+ ts.forEachChild(node, visit);
557
+ };
558
+ visit(source);
559
+ return bindings;
560
+ };
561
+ const endpointKeysOf = (source, builder, made, model) => {
292
562
  const verbOf = verbReaderOf(builder?.parameters[0]);
293
563
  return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
294
564
  if (!ts.isPropertyAssignment(property))
@@ -303,7 +573,7 @@ var createFactReader = (ts, ast) => {
303
573
  span: spanOf(source, property.name),
304
574
  extent: extentOf(source, property),
305
575
  verb: root ? verbOf(root.expression) : null,
306
- guards: root ? guardsIn(optionObjectOf(root)) : [],
576
+ guards: root ? guardsIn(source, optionObjectOf(root), made, `${model}.${name}`) : [],
307
577
  argCount: methods.filter((method) => argMethods.has(method)).length
308
578
  }
309
579
  ];
@@ -370,22 +640,6 @@ var createFactReader = (ts, ast) => {
370
640
  })) : []
371
641
  ];
372
642
  };
373
- const implementsGuard = (node, guardNames) => !!node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword && clause.types.some(({ expression }) => guardNames.has(ast.expressionName(expression) ?? "")));
374
- const guardClassIn = (node, guardNames) => {
375
- if ((ts.isClassExpression(node) || ts.isClassDeclaration(node)) && implementsGuard(node, guardNames))
376
- return node;
377
- return ts.forEachChild(node, (child) => guardClassIn(child, guardNames)) ?? null;
378
- };
379
- const callsIn = (node, names) => {
380
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && names.has(node.expression.text))
381
- return node.expression.text;
382
- return ts.forEachChild(node, (child) => callsIn(child, names)) ?? null;
383
- };
384
- const scopeOf = (node) => {
385
- const scope = node.members.find((member) => ts.isPropertyDeclaration(member) && ast.nodeName(member.name) === "scope" && !!ts.getModifiers(member)?.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword))?.initializer;
386
- const value = scope && unwrap(scope);
387
- return value && ts.isStringLiteralLike(value) ? value.text : null;
388
- };
389
643
  const docOf = (source, node) => {
390
644
  const { text } = source;
391
645
  const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? [];
@@ -401,108 +655,20 @@ var createFactReader = (ts, ast) => {
401
655
  `)).map((line) => line.trim().replace(/^\/\*\*?|\*\/$/g, "").replace(/^(?:\/\/[?*!]?|\*)/, "").replace(/\{@link\s+([^}\s|]+)[^}]*\}/g, "$1").trim()).filter((line) => line && !/^(?:biome-ignore|eslint-|@ts-)/.test(line));
402
656
  return lines.length ? lines.join(" ") : null;
403
657
  };
404
- const functionsOf = (source) => {
405
- const functions = new Map;
406
- for (const statement of source.statements) {
407
- if (ts.isFunctionDeclaration(statement) && statement.name && statement.body)
408
- functions.set(statement.name.text, { statement, name: statement.name, body: statement.body });
409
- if (!ts.isVariableStatement(statement))
410
- continue;
411
- for (const declaration of statement.declarationList.declarations) {
412
- const initializer = declaration.initializer && unwrap(declaration.initializer);
413
- if (initializer && ts.isIdentifier(declaration.name) && (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)))
414
- functions.set(declaration.name.text, { statement, name: declaration.name, body: initializer.body });
415
- }
416
- }
417
- return functions;
418
- };
419
- const guardFactoriesOf = (source, guardNames) => {
420
- const functions = functionsOf(source);
421
- const factories = new Map;
422
- const factoryOf = (name, scope, via) => {
423
- const found = functions.get(name);
424
- if (!found)
425
- return;
426
- factories.set(name, {
427
- name,
428
- span: spanOf(source, found.name),
429
- extent: extentOf(source, found.statement),
430
- scope,
431
- doc: docOf(source, found.statement),
432
- via
433
- });
434
- };
435
- for (const [name, { body }] of functions) {
436
- const guard = guardClassIn(body, guardNames);
437
- if (guard)
438
- factoryOf(name, scopeOf(guard), null);
439
- }
440
- for (let isGrowing = true;isGrowing; ) {
441
- isGrowing = false;
442
- for (const [name, { body }] of functions) {
443
- const called = factories.has(name) ? null : callsIn(body, new Set(factories.keys()));
444
- if (!called)
445
- continue;
446
- factoryOf(name, factories.get(called)?.scope ?? null, called);
447
- isGrowing = true;
448
- }
449
- }
450
- return factories;
451
- };
452
- const guardsOf = (source, imports) => {
453
- const guardNames = new Set([
454
- "Guard",
455
- ...imports.filter(({ imported }) => imported === "Guard").map(({ name }) => name)
456
- ]);
457
- const imported = new Set(imports.map(({ name }) => name));
458
- const factories = guardFactoriesOf(source, guardNames);
459
- const guards = [];
460
- const calls = [];
461
- for (const statement of source.statements) {
462
- if (ts.isClassDeclaration(statement) && statement.name && implementsGuard(statement, guardNames))
463
- guards.push({
464
- name: statement.name.text,
465
- span: spanOf(source, statement.name),
466
- extent: extentOf(source, statement),
467
- scope: scopeOf(statement),
468
- doc: docOf(source, statement),
469
- factory: null
470
- });
471
- if (!ts.isVariableStatement(statement))
472
- continue;
473
- for (const declaration of statement.declarationList.declarations) {
474
- const initializer = declaration.initializer && unwrap(declaration.initializer);
475
- if (!initializer || !ts.isIdentifier(declaration.name))
476
- continue;
477
- const made = {
478
- name: declaration.name.text,
479
- span: spanOf(source, declaration.name),
480
- extent: extentOf(source, statement),
481
- doc: docOf(source, statement)
482
- };
483
- if (ts.isClassExpression(initializer) && implementsGuard(initializer, guardNames))
484
- guards.push({ ...made, scope: scopeOf(initializer), factory: null });
485
- if (!ts.isCallExpression(initializer) || !ts.isIdentifier(initializer.expression))
486
- continue;
487
- const callee = initializer.expression.text;
488
- const factory = factories.get(callee);
489
- if (factory)
490
- guards.push({ ...made, scope: factory.scope, factory: callee });
491
- else if (imported.has(callee))
492
- calls.push({ ...made, callee });
493
- }
494
- }
495
- return { guards, guardFactories: [...factories.values()], guardCalls: calls };
496
- };
658
+ const { guardsIn, guardsOf } = createGuardFactReader(ts, ast, { spanOf, extentOf, unwrap, docOf });
497
659
  const lowerFirst = (text) => text.charAt(0).toLowerCase() + text.slice(1);
498
660
  const importsIn = (source) => source.statements.filter(ts.isImportDeclaration).flatMap(importsOf);
499
661
  const read = (source, owner) => {
500
662
  const imports = importsIn(source);
663
+ const madeInPlace = [];
501
664
  const facts = {
502
665
  fileName: source.fileName,
503
666
  owner,
504
667
  imports,
505
- ...guardsOf(source, imports),
668
+ guards: [],
669
+ guardFactories: [],
670
+ guardCalls: [],
671
+ guardLabels: {},
506
672
  constants: [],
507
673
  enums: [],
508
674
  filters: [],
@@ -512,6 +678,7 @@ var createFactReader = (ts, ast) => {
512
678
  endpoints: [],
513
679
  internals: [],
514
680
  adaptors: [],
681
+ adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
515
682
  stores: []
516
683
  };
517
684
  for (const statement of source.statements) {
@@ -536,16 +703,19 @@ var createFactReader = (ts, ast) => {
536
703
  ...name,
537
704
  model: moduleRefOf(call.arguments[0], "db"),
538
705
  methods: methodsOf(source, statement),
539
- plugs: plugsOf(source, builder)
706
+ plugs: plugsOf(source, builder),
707
+ fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
540
708
  });
541
- else if (callee === "slice")
709
+ else if (callee === "slice") {
710
+ const model = moduleRefOf(call.arguments[0], "srv");
711
+ const member = model ?? name.name;
542
712
  facts.slices.push({
543
713
  ...name,
544
- model: moduleRefOf(call.arguments[0], "srv"),
545
- guards: guardsIn(call.arguments.find(ts.isObjectLiteralExpression)),
546
- keys: sliceKeysOf(source, builder)
714
+ model,
715
+ guards: guardsIn(source, call.arguments.find(ts.isObjectLiteralExpression), madeInPlace, member),
716
+ keys: sliceKeysOf(source, builder, madeInPlace, member)
547
717
  });
548
- else if (callee === "internal")
718
+ } else if (callee === "internal")
549
719
  facts.internals.push({
550
720
  ...name,
551
721
  model: moduleRefOf(call.arguments[0], "srv"),
@@ -560,13 +730,14 @@ var createFactReader = (ts, ast) => {
560
730
  plugs: plugsOf(source, builder),
561
731
  doc: docOf(source, statement)
562
732
  });
563
- } else if (callee === "endpoint")
733
+ } else if (callee === "endpoint") {
734
+ const model = moduleRefOf(call.arguments[0], "srv");
564
735
  facts.endpoints.push({
565
736
  ...name,
566
- model: moduleRefOf(call.arguments[0], "srv"),
567
- keys: endpointKeysOf(source, builder)
737
+ model,
738
+ keys: endpointKeysOf(source, builder, madeInPlace, model ?? name.name)
568
739
  });
569
- else if (callee === "store")
740
+ } else if (callee === "store")
570
741
  facts.stores.push({
571
742
  ...name,
572
743
  model: moduleRefOf(call.arguments[0], "sig"),
@@ -574,10 +745,439 @@ var createFactReader = (ts, ast) => {
574
745
  actions: methodsOf(source, statement)
575
746
  });
576
747
  }
577
- return facts;
578
- };
579
- return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
580
- };
748
+ return { ...facts, ...guardsOf(source, imports, madeInPlace) };
749
+ };
750
+ return { read, spanOf, extentOf, docOf, importsIn, moduleRefOf, chainOf, unwrap };
751
+ };
752
+
753
+ // pkgs/@akanjs/language/guardResolver.ts
754
+ var frameworkOwner = "akanjs";
755
+ var frameworkPattern = /^akanjs(?:\/|$)/;
756
+ var aliasPattern = /^@(apps|libs)\/([^/]+)/;
757
+ var maxMapDepth = 4;
758
+ var starExportPattern = /export\s*\*\s*from\s*["']([^"']+)["']/g;
759
+ var declaringPatternOf = (name, isAnyBinding) => {
760
+ const escaped = escapeRegExp(name);
761
+ const braced = "\\{(?:[^}]*?[\\s,])?";
762
+ const leads = [
763
+ "function\\s*\\*?\\s*",
764
+ "(?:const|let|var)\\s+",
765
+ `export\\s*${braced}`,
766
+ ...isAnyBinding ? ["class\\s+", `(?:const|let|var)\\s*${braced}`] : []
767
+ ];
768
+ return new RegExp(`(?:${leads.join("|")})${escaped}(?![\\w$])|,\\s*${escaped}\\s*(?::[^=;,]+)?=(?![=>])`);
769
+ };
770
+
771
+ class GuardResolver {
772
+ #source;
773
+ #routes;
774
+ #guards = new Map;
775
+ #madeInPlace = new Map;
776
+ #mapEntries = new Map;
777
+ #reexports = new Map;
778
+ #declarations = new Map;
779
+ #starExports = new Map;
780
+ #ownerFiles = null;
781
+ #modules = null;
782
+ constructor(source, routes) {
783
+ this.#source = source;
784
+ this.#routes = routes;
785
+ }
786
+ find(local, site) {
787
+ return this.#findGuard(local, site, 0);
788
+ }
789
+ madeInPlace(fileName, name) {
790
+ return this.#madeInPlace.get(`${fileName}
791
+ ${name}`);
792
+ }
793
+ declare(facts) {
794
+ const factories = new Map;
795
+ const declaredFactories = [];
796
+ for (const file of facts)
797
+ for (const fact of file.guardFactories) {
798
+ const factory = {
799
+ fact,
800
+ owner: file.owner,
801
+ site: file,
802
+ made: [],
803
+ isReal: fact.direct,
804
+ scope: fact.scope,
805
+ targets: []
806
+ };
807
+ factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
808
+ declaredFactories.push(factory);
809
+ }
810
+ for (const factory of declaredFactories)
811
+ factory.targets = factory.fact.via.map((callee) => this.#factoryAt(factories, callee, factory.site)).filter((target) => target !== undefined);
812
+ for (let isGrowing = true;isGrowing; ) {
813
+ isGrowing = false;
814
+ for (const factory of declaredFactories) {
815
+ const target = factory.targets.find((other) => other.isReal);
816
+ if (factory.isReal || !target)
817
+ continue;
818
+ factory.isReal = true;
819
+ factory.scope ??= target.scope;
820
+ isGrowing = true;
821
+ }
822
+ }
823
+ const partsOf = (factory, seen = new Set) => {
824
+ if (seen.has(factory))
825
+ return [];
826
+ seen.add(factory);
827
+ return [
828
+ { fileName: factory.site.fileName, extent: factory.fact.extent },
829
+ ...factory.targets.filter((target) => target.isReal).flatMap((target) => partsOf(target, seen))
830
+ ];
831
+ };
832
+ const declare = (file, made, scope, parts) => {
833
+ const { name, span, extent, doc } = made;
834
+ const label = file.guardLabels[name];
835
+ const own = { fileName: file.fileName, extent };
836
+ const guard = { name, owner: file.owner, span, scope, doc, parts: [own, ...parts], file: null, label };
837
+ if (name.includes("@"))
838
+ this.#madeInPlace.set(`${file.fileName}
839
+ ${name}`, guard);
840
+ else
841
+ this.#guards.set(name, [...this.#guards.get(name) ?? [], guard]);
842
+ return guard;
843
+ };
844
+ const extending = [];
845
+ for (const file of facts) {
846
+ for (const made of file.guards) {
847
+ const factory = made.factory ? factories.get(made.factory)?.find((other) => other.site.fileName === file.fileName) : undefined;
848
+ if (!made.factory)
849
+ declare(file, made, made.scope, []);
850
+ else if (factory?.isReal)
851
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
852
+ }
853
+ for (const made of file.guardCalls) {
854
+ if (made.kind === "extends") {
855
+ extending.push([file, made]);
856
+ continue;
857
+ }
858
+ const factory = this.#factoryAt(factories, made.callee, file);
859
+ if (factory?.isReal)
860
+ factory.made.push(declare(file, made, factory.scope, partsOf(factory)));
861
+ }
862
+ }
863
+ for (let isGrowing = true;isGrowing; ) {
864
+ isGrowing = false;
865
+ for (const [idx, entry] of extending.entries()) {
866
+ const [file, made] = entry;
867
+ const found = this.#findGuard(made.callee, file, 0, true);
868
+ if (!("guard" in found))
869
+ continue;
870
+ declare(file, made, made.scope ?? found.guard.scope, found.guard.parts);
871
+ extending.splice(idx, 1);
872
+ isGrowing = true;
873
+ break;
874
+ }
875
+ }
876
+ for (const { fact, made, isReal } of declaredFactories)
877
+ if (isReal && made.every((guard) => !guard.doc))
878
+ for (const guard of made)
879
+ guard.doc = fact.doc;
880
+ for (const list of this.#guards.values())
881
+ for (const guard of list)
882
+ if (list.some((other) => other !== guard && other.owner === guard.owner))
883
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
884
+ for (const guard of this.#madeInPlace.values())
885
+ guard.file = this.inOwner(guard.span.fileName, guard.owner);
886
+ return [...[...this.#guards.values()].flat(), ...this.#madeInPlace.values()];
887
+ }
888
+ #factoryAt(factories, callee, site, depth = 0) {
889
+ const [head = callee, member] = callee.split(".");
890
+ const imported = site.imports.find((item) => item.name === head);
891
+ if (!imported)
892
+ return member ? undefined : factories.get(callee)?.find((other) => other.site.fileName === site.fileName);
893
+ if (member && imported.imported !== "*")
894
+ return;
895
+ const from = this.ownerOfSpecifier(site.fileName, imported.from);
896
+ if (from === undefined || from === frameworkOwner)
897
+ return;
898
+ const name = member ?? (imported.imported === "default" || imported.imported === "*" ? head : imported.imported);
899
+ const candidates = factories.get(name) ?? [];
900
+ const near = this.pathOfSpecifier(site.fileName, imported.from);
901
+ const own = this.#nearest(candidates.filter((other) => other.owner === from), ({ site }) => site.fileName, near);
902
+ if (own)
903
+ return own;
904
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
905
+ if (reexport)
906
+ return this.#factoryAt(factories, reexport.local, reexport.site, depth + 1);
907
+ return from && near ? this.#throughStar(candidates, from, name, near) : undefined;
908
+ }
909
+ #findGuard(local, site, depth, isBase = false) {
910
+ const segments = local.split(".");
911
+ const head = segments[0] ?? local;
912
+ const imported = site.imports.find((item) => item.name === head);
913
+ const from = imported ? this.ownerOfSpecifier(site.fileName, imported.from) : undefined;
914
+ if (imported && from === undefined)
915
+ return { external: true };
916
+ const near = imported ? this.pathOfSpecifier(site.fileName, imported.from) : this.#source.relative(site.fileName).replace(/\.tsx?$/, "");
917
+ const named = imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : null;
918
+ const path = imported?.imported === "*" ? segments.slice(1) : [named ?? head, ...segments.slice(1)];
919
+ const holder = imported ? from ?? null : site.owner;
920
+ const [objectName, member] = path;
921
+ if (path.length === 2 && objectName && member && holder !== frameworkOwner && depth < maxMapDepth) {
922
+ const made = this.#nearest((this.#guards.get(`${objectName}.${member}`) ?? []).filter((guard) => guard.owner === holder), ({ span }) => span.fileName, near);
923
+ if (made)
924
+ return { guard: made };
925
+ for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
926
+ const found = this.#findGuard(entry.name, entry.site, depth + 1, isBase);
927
+ if ("guard" in found)
928
+ return found;
929
+ }
930
+ }
931
+ const name = path[path.length - 1] ?? head;
932
+ if (from === frameworkOwner)
933
+ return { name, from };
934
+ const declared = this.#guards.get(name) ?? [];
935
+ const own = this.#nearest(declared.filter((other) => other.owner === (imported ? from : site.owner)), ({ span }) => span.fileName, near);
936
+ if (own)
937
+ return { guard: own };
938
+ if (!imported) {
939
+ const visible = isBase ? undefined : declared.find((other) => this.visible(other.owner, site.owner));
940
+ return visible ? { guard: visible } : { name, from };
941
+ }
942
+ const reexport = from && depth < maxMapDepth ? this.reexportOf(from, name, near) : null;
943
+ if (reexport)
944
+ return this.#findGuard(reexport.local, reexport.site, depth + 1, isBase);
945
+ const reached = from && near ? this.#throughStar(declared, from, name, near) : undefined;
946
+ return reached ? { guard: reached } : { name, from };
947
+ }
948
+ reexportOf(owner, name, near) {
949
+ const key = `${owner}
950
+ ${name}
951
+ ${near ?? ""}`;
952
+ const cached = this.#reexports.get(key);
953
+ if (cached !== undefined)
954
+ return cached;
955
+ let found = null;
956
+ const exporting = new RegExp(`export\\s*\\{(?:[^}]*?[\\s,])?${escapeRegExp(name)}(?![\\w$])`);
957
+ for (const fileName of this.#filesOf(owner, name, near)) {
958
+ const text = this.#source.readText(fileName);
959
+ if (text === undefined || !exporting.test(text))
960
+ continue;
961
+ const reexport = this.#reexportIn(this.#source.parse(fileName, text), name);
962
+ if (!reexport)
963
+ continue;
964
+ const site = { fileName, owner, imports: [] };
965
+ found = reexport.from ? {
966
+ local: reexport.name,
967
+ site: { ...site, imports: [{ ...reexport, imported: reexport.name, from: reexport.from }] }
968
+ } : { local: reexport.name, site };
969
+ break;
970
+ }
971
+ this.#reexports.set(key, found);
972
+ return found;
973
+ }
974
+ #reexportIn(source, name) {
975
+ const { ts, reader } = this.#source;
976
+ for (const statement of source.statements) {
977
+ if (!ts.isExportDeclaration(statement) || statement.isTypeOnly)
978
+ continue;
979
+ const { exportClause, moduleSpecifier } = statement;
980
+ if (!exportClause || !ts.isNamedExports(exportClause))
981
+ continue;
982
+ const element = exportClause.elements.find((item) => !item.isTypeOnly && item.name.text === name);
983
+ if (!element)
984
+ continue;
985
+ const original = (element.propertyName ?? element.name).text;
986
+ if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier))
987
+ return { name: original, from: moduleSpecifier.text };
988
+ const imported = reader.importsIn(source).find((item) => item.name === original);
989
+ if (!imported)
990
+ return { name: original, from: null };
991
+ return imported.imported === "default" || imported.imported === "*" ? null : { name: imported.imported, from: imported.from };
992
+ }
993
+ return null;
994
+ }
995
+ #filesOf(owner, name, near) {
996
+ const isNear = (fileName) => {
997
+ const relative = this.#source.relative(fileName);
998
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
999
+ };
1000
+ const candidates = this.filesIn(owner).filter(([, words]) => words.includes(`
1001
+ ${name}
1002
+ `)).map(([fileName]) => fileName);
1003
+ return [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))];
1004
+ }
1005
+ #mapEntriesOf(objectName, member, fileName, owner) {
1006
+ const key = `${objectName}
1007
+ ${member}
1008
+ ${fileName ?? ""}
1009
+ ${owner ?? ""}`;
1010
+ const cached = this.#mapEntries.get(key);
1011
+ if (cached)
1012
+ return cached;
1013
+ const { ts, reader } = this.#source;
1014
+ const entries = [];
1015
+ const fileNames = [
1016
+ ...fileName ? [fileName] : [],
1017
+ ...(owner === null ? [] : this.filesIn(owner)).filter(([other, words]) => other !== fileName && words.includes(`
1018
+ ${objectName}
1019
+ `) && words.includes(`
1020
+ ${member}
1021
+ `)).map(([other]) => other)
1022
+ ];
1023
+ for (const holder of fileNames) {
1024
+ const text = this.#source.readText(holder);
1025
+ if (text === undefined)
1026
+ continue;
1027
+ const source = this.#source.parse(holder, text);
1028
+ const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
1029
+ for (const statement of source.statements) {
1030
+ if (!ts.isVariableStatement(statement))
1031
+ continue;
1032
+ const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
1033
+ const object = map && reader.unwrap(map);
1034
+ if (!object || !ts.isObjectLiteralExpression(object))
1035
+ continue;
1036
+ for (const property of object.properties) {
1037
+ if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
1038
+ entries.push({ name: member, site });
1039
+ if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
1040
+ continue;
1041
+ const value = reader.unwrap(property.initializer);
1042
+ if (ts.isIdentifier(value))
1043
+ entries.push({ name: value.text, site });
1044
+ else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
1045
+ entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
1046
+ }
1047
+ }
1048
+ }
1049
+ this.#mapEntries.set(key, entries);
1050
+ return entries;
1051
+ }
1052
+ declarationOf(owner, name, near, depth = 0) {
1053
+ const key = `${owner}
1054
+ ${name}
1055
+ ${near ?? ""}`;
1056
+ const cached = this.#declarations.get(key);
1057
+ if (cached !== undefined)
1058
+ return cached;
1059
+ this.#declarations.set(key, null);
1060
+ const declaring = declaringPatternOf(name, false);
1061
+ let found = null;
1062
+ for (const fileName of this.#filesOf(owner, name, near)) {
1063
+ const text = this.#source.readText(fileName);
1064
+ if (text === undefined || !declaring.test(text))
1065
+ continue;
1066
+ const source = this.#source.parse(fileName, text);
1067
+ const declared = this.#routes.declarationIn(source, name);
1068
+ if (declared) {
1069
+ found = { source, ...declared, name };
1070
+ break;
1071
+ }
1072
+ const reexport = this.#reexportIn(source, name);
1073
+ if (!reexport)
1074
+ continue;
1075
+ if (!reexport.from) {
1076
+ const local = this.#routes.declarationIn(source, reexport.name);
1077
+ found = local ? { source, ...local, name: reexport.name } : null;
1078
+ break;
1079
+ }
1080
+ const next = this.ownerOfSpecifier(fileName, reexport.from);
1081
+ if (next && next !== frameworkOwner && depth < maxMapDepth)
1082
+ found = this.declarationOf(next, reexport.name, this.pathOfSpecifier(fileName, reexport.from), depth + 1);
1083
+ break;
1084
+ }
1085
+ this.#declarations.set(key, found);
1086
+ return found;
1087
+ }
1088
+ #throughStar(declared, owner, name, near) {
1089
+ if (!declared.length)
1090
+ return;
1091
+ const { owners, isOpaque } = this.#starExportsOf(owner, near);
1092
+ const reached = declared.filter((other) => other.owner !== null && owners.includes(other.owner));
1093
+ return reached.length === 1 && !isOpaque && !this.declares(owner, name) ? reached[0] : undefined;
1094
+ }
1095
+ #starExportsOf(owner, near) {
1096
+ const cached = this.#starExports.get(near);
1097
+ if (cached)
1098
+ return cached;
1099
+ const found = { owners: [], isOpaque: false };
1100
+ this.#starExports.set(near, found);
1101
+ const paths = [near];
1102
+ for (let idx = 0;idx < paths.length && !found.isOpaque; idx++)
1103
+ for (const fileName of this.#modulesAt(paths[idx] ?? "")) {
1104
+ for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? []) {
1105
+ const target = this.ownerOfSpecifier(fileName, specifier);
1106
+ const path = this.pathOfSpecifier(fileName, specifier);
1107
+ if (target === undefined || target === frameworkOwner || path === null)
1108
+ found.isOpaque = true;
1109
+ else if (!paths.includes(path))
1110
+ paths.push(path);
1111
+ if (target && target !== frameworkOwner && target !== owner && !found.owners.includes(target))
1112
+ found.owners.push(target);
1113
+ }
1114
+ }
1115
+ return found;
1116
+ }
1117
+ #modulesAt(path) {
1118
+ if (!this.#modules) {
1119
+ this.#modules = new Map;
1120
+ for (const [fileName] of this.#source.files) {
1121
+ const module = this.#source.relative(fileName).replace(/\.tsx?$/, "");
1122
+ for (const key of module.endsWith("/index") ? [module, module.slice(0, -6)] : [module])
1123
+ this.#modules.set(key, [...this.#modules.get(key) ?? [], fileName]);
1124
+ }
1125
+ }
1126
+ return this.#modules.get(path) ?? [];
1127
+ }
1128
+ declares(owner, name) {
1129
+ const declaring = declaringPatternOf(name, true);
1130
+ return this.#filesOf(owner, name, null).some((fileName) => declaring.test(this.#source.readText(fileName) ?? ""));
1131
+ }
1132
+ filesIn(owner) {
1133
+ if (!this.#ownerFiles) {
1134
+ this.#ownerFiles = new Map;
1135
+ for (const entry of this.#source.files) {
1136
+ const fileOwner = testFilePattern.test(entry[0]) ? null : this.#source.ownerOf(entry[0]);
1137
+ const files = fileOwner === null ? null : this.#ownerFiles.get(fileOwner);
1138
+ if (files)
1139
+ files.push(entry);
1140
+ else if (fileOwner !== null)
1141
+ this.#ownerFiles.set(fileOwner, [entry]);
1142
+ }
1143
+ }
1144
+ return this.#ownerFiles.get(owner) ?? [];
1145
+ }
1146
+ #nearest(items, fileOf, near) {
1147
+ const isNear = (fileName) => {
1148
+ const relative = this.#source.relative(fileName);
1149
+ return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1150
+ };
1151
+ return items.find((item) => isNear(fileOf(item))) ?? items[0];
1152
+ }
1153
+ inOwner(fileName, owner) {
1154
+ const relative = this.#source.relative(fileName);
1155
+ return owner && relative.startsWith(`${owner}/`) ? relative.slice(owner.length + 1) : relative;
1156
+ }
1157
+ ownerOfSpecifier(fileName, specifier) {
1158
+ if (frameworkPattern.test(specifier))
1159
+ return frameworkOwner;
1160
+ const path = this.pathOfSpecifier(fileName, specifier);
1161
+ return path === null ? undefined : ownerOf(path) ?? undefined;
1162
+ }
1163
+ pathOfSpecifier(fileName, specifier) {
1164
+ const alias = specifier.match(aliasPattern);
1165
+ if (alias)
1166
+ return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1167
+ if (!specifier.startsWith("."))
1168
+ return null;
1169
+ const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1170
+ for (const segment of specifier.split("/"))
1171
+ if (segment === "..")
1172
+ segments.pop();
1173
+ else if (segment !== ".")
1174
+ segments.push(segment);
1175
+ return segments.join("/");
1176
+ }
1177
+ visible(target, from) {
1178
+ return !from || !target || target === from || target.startsWith("libs/");
1179
+ }
1180
+ }
581
1181
 
582
1182
  // pkgs/akanjs/common/trimSlashes.ts
583
1183
  var trimTrailingSlashes = (value) => {
@@ -726,24 +1326,23 @@ rootLayout
726
1326
  }
727
1327
  declarationIn(source, name) {
728
1328
  const ts = this.#ts;
1329
+ const overloads = source.statements.filter((statement) => ts.isFunctionDeclaration(statement) && statement.name?.text === name);
1330
+ const [first] = overloads;
1331
+ const last = overloads.at(-1);
1332
+ if (first?.name && last)
1333
+ return { statement: first, declared: first.name, extent: { start: first.getStart(source), end: last.getEnd() } };
729
1334
  for (const statement of source.statements) {
730
- if (ts.isFunctionDeclaration(statement) && statement.name?.text === name)
731
- return { statement, declared: statement.name, initializer: null };
732
1335
  if (!ts.isVariableStatement(statement))
733
1336
  continue;
734
1337
  const declaration = statement.declarationList.declarations.find((item) => ts.isIdentifier(item.name) && item.name.text === name);
735
1338
  if (declaration)
736
- return { statement, declared: declaration.name, initializer: declaration.initializer ?? null };
1339
+ return { statement, declared: declaration.name, extent: this.#reader.extentOf(source, statement) };
737
1340
  }
738
1341
  return null;
739
1342
  }
740
1343
  #guardOf(source, exported) {
741
1344
  const ts = this.#ts;
742
1345
  let current = ts.isExportAssignment(exported) ? this.#reader.unwrap(exported.expression) : null;
743
- if (current && ts.isIdentifier(current)) {
744
- const initializer = this.declarationIn(source, current.text)?.initializer;
745
- current = initializer ? this.#reader.unwrap(initializer) : null;
746
- }
747
1346
  while (current && ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
748
1347
  const { name, expression } = current.expression;
749
1348
  const [stage] = current.arguments;
@@ -755,9 +1354,20 @@ rootLayout
755
1354
  }
756
1355
  #callsIn(source, stage) {
757
1356
  const ts = this.#ts;
758
- const imported = new Set(this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from)).map(({ name }) => name));
1357
+ const imports = this.#reader.importsIn(source).filter(({ from }) => RouteFactReader.#workspacePattern.test(from));
1358
+ const imported = new Set(imports.filter(({ imported }) => imported !== "*").map(({ name }) => name));
1359
+ const namespaces = new Set(imports.filter(({ imported }) => imported === "*").map(({ name }) => name));
759
1360
  const calls = new Map;
760
- const callOf = (name) => {
1361
+ const callOf = (callee, shadowed) => {
1362
+ if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression) && namespaces.has(callee.expression.text) && !shadowed.has(callee.expression.text)) {
1363
+ const name = `${callee.expression.text}.${callee.name.text}`;
1364
+ if (!calls.has(name))
1365
+ calls.set(name, { name, local: null });
1366
+ return true;
1367
+ }
1368
+ if (!ts.isIdentifier(callee) || shadowed.has(callee.text))
1369
+ return false;
1370
+ const name = callee.text;
761
1371
  if (calls.has(name))
762
1372
  return true;
763
1373
  const declared = this.declarationIn(source, name);
@@ -767,23 +1377,71 @@ rootLayout
767
1377
  name,
768
1378
  local: declared && {
769
1379
  span: this.#reader.spanOf(source, declared.declared),
770
- extent: this.#reader.extentOf(source, declared.statement),
1380
+ extent: declared.extent,
771
1381
  doc: this.#reader.docOf(source, declared.statement)
772
1382
  }
773
1383
  });
774
1384
  return true;
775
1385
  };
776
- const visit = (node) => {
777
- if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && callOf(node.expression.text))
1386
+ const visit = (node, outer) => {
1387
+ const shadowed = this.#scopeOf(node, outer);
1388
+ if (ts.isCallExpression(node) && callOf(node.expression, shadowed))
778
1389
  return;
779
- ts.forEachChild(node, visit);
1390
+ ts.forEachChild(node, (child) => visit(child, shadowed));
780
1391
  };
781
1392
  if (ts.isIdentifier(stage))
782
- callOf(stage.text);
1393
+ callOf(stage, new Set);
783
1394
  else
784
- visit(stage);
1395
+ visit(stage, new Set);
785
1396
  return [...calls.values()];
786
1397
  }
1398
+ #scopeOf(node, outer) {
1399
+ const ts = this.#ts;
1400
+ const names = [];
1401
+ const bind = (name) => {
1402
+ if (ts.isIdentifier(name))
1403
+ names.push(name.text);
1404
+ else
1405
+ for (const element of name.elements)
1406
+ if (!ts.isOmittedExpression(element))
1407
+ bind(element.name);
1408
+ };
1409
+ const declare = (statement) => {
1410
+ if (ts.isVariableStatement(statement))
1411
+ for (const declaration of statement.declarationList.declarations)
1412
+ bind(declaration.name);
1413
+ else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name)
1414
+ names.push(statement.name.text);
1415
+ };
1416
+ const hoist = (child) => {
1417
+ if (ts.isFunctionLike(child) || ts.isClassLike(child))
1418
+ return;
1419
+ if (ts.isVariableDeclarationList(child) && !(child.flags & ts.NodeFlags.BlockScoped))
1420
+ for (const declaration of child.declarations)
1421
+ bind(declaration.name);
1422
+ ts.forEachChild(child, hoist);
1423
+ };
1424
+ if (ts.isFunctionLike(node)) {
1425
+ for (const parameter of node.parameters)
1426
+ bind(parameter.name);
1427
+ if (ts.isFunctionExpression(node) && node.name)
1428
+ names.push(node.name.text);
1429
+ if ("body" in node && node.body)
1430
+ ts.forEachChild(node.body, hoist);
1431
+ } else if (ts.isClassExpression(node) && node.name)
1432
+ names.push(node.name.text);
1433
+ else if (ts.isBlock(node))
1434
+ node.statements.forEach(declare);
1435
+ else if (ts.isCaseBlock(node))
1436
+ for (const clause of node.clauses)
1437
+ clause.statements.forEach(declare);
1438
+ else if (ts.isCatchClause(node) && node.variableDeclaration)
1439
+ bind(node.variableDeclaration.name);
1440
+ else if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node)) && node.initializer && ts.isVariableDeclarationList(node.initializer))
1441
+ for (const declaration of node.initializer.declarations)
1442
+ bind(declaration.name);
1443
+ return names.length ? new Set([...outer, ...names]) : outer;
1444
+ }
787
1445
  }
788
1446
 
789
1447
  // pkgs/@akanjs/language/akanGraph.ts
@@ -799,10 +1457,6 @@ var builtinGuards = {
799
1457
  Public: { scope: "account", doc: "Passes every caller, signed in or not." },
800
1458
  None: { scope: "account", doc: "Refuses every caller." }
801
1459
  };
802
- var frameworkOwner = "akanjs";
803
- var frameworkPattern = /^akanjs(?:\/|$)/;
804
- var aliasPattern = /^@(apps|libs)\/([^/]+)/;
805
- var maxMapDepth = 4;
806
1460
 
807
1461
  class GraphBuilder {
808
1462
  #source;
@@ -810,16 +1464,16 @@ class GraphBuilder {
810
1464
  #edges = new Map;
811
1465
  #contexts = new Map;
812
1466
  #members = new Map;
813
- #guards = new Map;
814
- #mapEntries = new Map;
815
1467
  #routeFiles = new Map;
816
- #declarations = new Map;
817
1468
  #routes;
1469
+ #resolver;
818
1470
  #adaptors = new Map;
819
1471
  #plugs = new Map;
1472
+ #serviceFields = new Set;
820
1473
  constructor(source) {
821
1474
  this.#source = source;
822
1475
  this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
1476
+ this.#resolver = new GuardResolver(source, this.#routes);
823
1477
  }
824
1478
  build() {
825
1479
  const { facts } = this.#source;
@@ -831,17 +1485,28 @@ class GraphBuilder {
831
1485
  ...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
832
1486
  ];
833
1487
  const typed = [];
1488
+ const adaptorCounts = new Map;
834
1489
  for (const file of facts)
835
1490
  for (const fact of file.adaptors) {
836
- const declared = { fact, owner: file.owner, fileName: file.fileName };
837
- this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
838
- this.#adaptorNodeOf(declared);
1491
+ const key = this.#key("adaptor", file.owner, fact.name);
1492
+ adaptorCounts.set(key, (adaptorCounts.get(key) ?? 0) + 1);
839
1493
  }
1494
+ const declaredAdaptors = facts.flatMap((file) => file.adaptors.map((fact) => {
1495
+ const key = this.#key("adaptor", file.owner, fact.name);
1496
+ const id = (adaptorCounts.get(key) ?? 0) > 1 ? this.#key("adaptor", file.owner, `${this.#resolver.inOwner(file.fileName, file.owner)}#${fact.name}`) : key;
1497
+ const declared = { fact, owner: file.owner, fileName: file.fileName, id };
1498
+ this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
1499
+ this.#adaptorNodeOf(declared);
1500
+ return { declared, file };
1501
+ }));
1502
+ for (const { declared, file } of declaredAdaptors)
1503
+ for (const plug of declared.fact.plugs)
1504
+ this.#addEdge(declared.id, this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
840
1505
  for (const file of facts)
841
- for (const fact of file.adaptors)
842
- for (const plug of fact.plugs)
843
- this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
844
- this.#declareGuards(facts);
1506
+ for (const binding of file.adaptorBindings)
1507
+ this.#addEdge(this.#adaptorIdOf(binding.role, file), this.#adaptorIdOf(binding.adaptor, file), "binds", file.owner, this.#location(binding.span));
1508
+ for (const guard of this.#resolver.declare(facts))
1509
+ this.#guardNodeOf(guard);
845
1510
  for (const file of facts) {
846
1511
  const { owner } = file;
847
1512
  const scalar = this.#source.relative(file.fileName).match(scalarPattern)?.[1];
@@ -872,10 +1537,18 @@ class GraphBuilder {
872
1537
  }
873
1538
  for (const method of service.methods)
874
1539
  this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
875
- for (const plug of service.plugs)
876
- this.#plugs.set(`${owner ?? ""}
1540
+ for (const plug of service.plugs) {
1541
+ const key = `${refName}
1542
+ ${plug.name}`;
1543
+ this.#plugs.set(key, [
1544
+ ...this.#plugs.get(key) ?? [],
1545
+ { owner, target: this.#adaptorIdOf(plug.target, file) }
1546
+ ]);
1547
+ }
1548
+ for (const field of service.fields)
1549
+ this.#serviceFields.add(`${owner ?? ""}
877
1550
  ${refName}
878
- ${plug.name}`, this.#adaptorIdOf(plug.target, file));
1551
+ ${field}`);
879
1552
  }
880
1553
  for (const filter of file.filters) {
881
1554
  if (!filter.name.endsWith("Filter"))
@@ -885,31 +1558,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
885
1558
  for (const query of filter.queries)
886
1559
  this.#digest(this.#addNode(this.#key("query", owner, refName, query.name), "query", owner, refName, query.name, query.span), this.#source.readText(file.fileName), query.extent);
887
1560
  }
888
- for (const slice of file.slices) {
889
- const refName = slice.model;
890
- if (!refName)
891
- continue;
892
- const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards);
893
- for (const guard of slice.guards)
894
- this.#guard(fallback, guard, file);
895
- for (const key of slice.keys) {
896
- const detail = [...key.guards, ...key.live ? ["live"] : []];
897
- const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
898
- for (const guard of key.guards)
899
- this.#guard(node, guard, file);
900
- }
901
- }
902
- for (const endpoint of file.endpoints) {
903
- const refName = endpoint.model;
904
- if (!refName)
905
- continue;
906
- for (const key of endpoint.keys) {
907
- const detail = [...key.verb ? [key.verb] : [], ...key.guards];
908
- const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
909
- for (const guard of key.guards)
910
- this.#guard(node, guard, file);
911
- }
912
- }
1561
+ this.#addGuarded(file);
913
1562
  for (const internal of file.internals)
914
1563
  for (const key of internal.keys)
915
1564
  if (internal.model && key.type !== "resolveField")
@@ -925,7 +1574,7 @@ ${plug.name}`, this.#adaptorIdOf(plug.target, file));
925
1574
  types.set(node.label, [...types.get(node.label) ?? [], node]);
926
1575
  for (const node of typed)
927
1576
  for (const field of node.fields ?? []) {
928
- const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#visible(type.owner, node.owner));
1577
+ const candidates = (field.type ? types.get(field.type) ?? [] : []).filter((type) => this.#resolver.visible(type.owner, node.owner));
929
1578
  field.ref = (candidates.find((type) => type.owner === node.owner) ?? candidates[0])?.id ?? null;
930
1579
  if (field.ref)
931
1580
  this.#addEdge(node.id, field.ref, "field", `${field.name}${field.array ? "[]" : ""}`, field.at);
@@ -1007,61 +1656,52 @@ ${label ?? ""}`;
1007
1656
  else
1008
1657
  this.#edges.set(key, { from, to, kind, label, count: 1, at });
1009
1658
  }
1010
- #guard(node, guard, file) {
1011
- const [role, local] = guard.includes(" ") ? guard.split(" ") : [null, guard];
1012
- if (!local || local === "?")
1013
- return;
1014
- this.#addEdge(node.id, this.#guardIdOf(local, file), "guard", role, node.at);
1015
- }
1016
- #declareGuards(facts) {
1017
- const factories = new Map;
1018
- for (const file of facts)
1019
- for (const fact of file.guardFactories) {
1020
- const factory = { fact, owner: file.owner, fileName: file.fileName, made: [] };
1021
- factories.set(fact.name, [...factories.get(fact.name) ?? [], factory]);
1022
- }
1023
- const sameFile = (factory, name) => name ? factories.get(name)?.find((other) => other.fileName === factory.fileName) : undefined;
1024
- const declare = (guard, factory) => {
1025
- this.#guards.set(guard.name, [...this.#guards.get(guard.name) ?? [], guard]);
1026
- factory?.made.push(guard);
1027
- };
1028
- const madeBy = (factory, fileName, extent) => {
1029
- const parts = [{ fileName, extent }];
1030
- const seen = new Set;
1031
- for (let current = factory;current && !seen.has(current); current = sameFile(current, current.fact.via)) {
1032
- seen.add(current);
1033
- parts.push({ fileName: current.fileName, extent: current.fact.extent });
1034
- }
1035
- return parts;
1036
- };
1037
- for (const file of facts) {
1038
- const { owner, fileName } = file;
1039
- for (const { name, span, extent, scope, doc, factory: callee } of file.guards) {
1040
- const factory = callee ? factories.get(callee)?.find((other) => other.fileName === fileName) : undefined;
1041
- declare({ name, owner, span, scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
1659
+ #addGuarded(file) {
1660
+ const { owner } = file;
1661
+ for (const slice of file.slices) {
1662
+ const refName = slice.model;
1663
+ if (!refName)
1664
+ continue;
1665
+ const fallback = this.#addNode(this.#key("slice", owner, refName), "slice", owner, refName, "", slice.span, slice.guards.map((guard) => guardLabelOf(guard, file.guardLabels)));
1666
+ for (const guard of slice.guards)
1667
+ this.#guard(fallback, guard, file);
1668
+ for (const key of slice.keys) {
1669
+ const detail = [
1670
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels)),
1671
+ ...key.live ? ["live"] : []
1672
+ ];
1673
+ const node = this.#addMember("slice", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1674
+ for (const guard of key.guards)
1675
+ this.#guard(node, guard, file);
1042
1676
  }
1043
- for (const { name, span, extent, doc, callee } of file.guardCalls) {
1044
- const imported = file.imports.find((item) => item.name === callee);
1045
- if (!imported)
1046
- continue;
1047
- const from = this.#ownerOfSpecifier(fileName, imported.from);
1048
- const candidates = factories.get(imported.imported === "default" || imported.imported === "*" ? callee : imported.imported) ?? [];
1049
- const factory = candidates.find((other) => other.owner === from) ?? this.#soleVisible(candidates, from ?? owner);
1050
- if (factory)
1051
- declare({ name, owner, span, scope: factory.fact.scope, doc, parts: madeBy(factory, fileName, extent) }, factory);
1677
+ }
1678
+ for (const endpoint of file.endpoints) {
1679
+ const refName = endpoint.model;
1680
+ if (!refName)
1681
+ continue;
1682
+ for (const key of endpoint.keys) {
1683
+ const detail = [
1684
+ ...key.verb ? [key.verb] : [],
1685
+ ...key.guards.map((guard) => guardLabelOf(guard, file.guardLabels))
1686
+ ];
1687
+ const node = this.#addMember("endpoint", owner, refName, key, `${refName}.${key.name}`, file.fileName, detail);
1688
+ for (const guard of key.guards)
1689
+ this.#guard(node, guard, file);
1052
1690
  }
1053
1691
  }
1054
- for (const list of factories.values())
1055
- for (const { fact, made } of list)
1056
- if (made.every((guard) => !guard.doc))
1057
- for (const guard of made)
1058
- guard.doc = fact.doc;
1059
- for (const list of this.#guards.values())
1060
- for (const guard of list)
1061
- this.#guardNodeOf(guard);
1692
+ }
1693
+ #guard(node, guard, file) {
1694
+ const [, role = null, local = guard] = guard.match(/^([\w$]+) (.+)$/) ?? [];
1695
+ if (!local || local === "?")
1696
+ return;
1697
+ const id = local.includes("@") ? this.#madeInPlaceIdOf(local, file) : this.#guardIdOf(local, file);
1698
+ if (id)
1699
+ this.#addEdge(node.id, id, "guard", role, node.at);
1062
1700
  }
1063
1701
  #guardIdOf(local, site) {
1064
- const found = this.#findGuard(local, site, 0);
1702
+ const found = this.#resolver.find(local, site);
1703
+ if ("external" in found)
1704
+ return null;
1065
1705
  if ("guard" in found)
1066
1706
  return this.#guardNodeOf(found.guard);
1067
1707
  const { name, from } = found;
@@ -1074,99 +1714,18 @@ ${label ?? ""}`;
1074
1714
  node.doc = builtin.doc;
1075
1715
  return node.id;
1076
1716
  }
1077
- #findGuard(local, site, depth) {
1078
- const [head = local, member] = local.split(".");
1079
- const imported = site.imports.find((item) => item.name === head);
1080
- const from = imported ? this.#ownerOfSpecifier(site.fileName, imported.from) : undefined;
1081
- const holder = imported ? from : site.owner;
1082
- if (member && imported?.imported !== "*" && holder !== undefined && holder !== frameworkOwner && depth < maxMapDepth) {
1083
- const objectName = imported && imported.imported !== "default" ? imported.imported : head;
1084
- for (const entry of this.#mapEntriesOf(objectName, member, imported ? null : site.fileName, holder)) {
1085
- const found = this.#findGuard(entry.name, entry.site, depth + 1);
1086
- if ("guard" in found)
1087
- return found;
1088
- }
1089
- }
1090
- const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1091
- const declared = this.#guards.get(name) ?? [];
1092
- const guard = from === undefined ? declared.find((other) => other.owner === site.owner) ?? declared.find((other) => this.#visible(other.owner, site.owner)) : declared.find((other) => other.owner === from) ?? (from === frameworkOwner ? undefined : this.#soleVisible(declared, from));
1093
- return guard ? { guard } : { name, from };
1094
- }
1095
- #mapEntriesOf(objectName, member, fileName, owner) {
1096
- const key = `${objectName}
1097
- ${member}
1098
- ${fileName ?? ""}
1099
- ${owner ?? ""}`;
1100
- const cached = this.#mapEntries.get(key);
1101
- if (cached)
1102
- return cached;
1103
- const { ts, reader } = this.#source;
1104
- const entries = [];
1105
- const fileNames = [
1106
- ...fileName ? [fileName] : [],
1107
- ...this.#source.files.filter(([other, words]) => other !== fileName && owner !== null && !testFilePattern.test(other) && words.includes(`
1108
- ${objectName}
1109
- `) && words.includes(`
1110
- ${member}
1111
- `) && this.#source.ownerOf(other) === owner).map(([other]) => other)
1112
- ];
1113
- for (const holder of fileNames) {
1114
- const text = this.#source.readText(holder);
1115
- if (text === undefined)
1116
- continue;
1117
- const source = this.#source.parse(holder, text);
1118
- const site = { fileName: holder, owner: this.#source.ownerOf(holder), imports: reader.importsIn(source) };
1119
- for (const statement of source.statements) {
1120
- if (!ts.isVariableStatement(statement))
1121
- continue;
1122
- const map = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === objectName)?.initializer;
1123
- const object = map && reader.unwrap(map);
1124
- if (!object || !ts.isObjectLiteralExpression(object))
1125
- continue;
1126
- for (const property of object.properties) {
1127
- if (ts.isShorthandPropertyAssignment(property) && property.name.text === member)
1128
- entries.push({ name: member, site });
1129
- if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) || property.name.text !== member)
1130
- continue;
1131
- const value = reader.unwrap(property.initializer);
1132
- if (ts.isIdentifier(value))
1133
- entries.push({ name: value.text, site });
1134
- else if (ts.isPropertyAccessExpression(value) && ts.isIdentifier(value.expression))
1135
- entries.push({ name: `${value.expression.text}.${value.name.text}`, site });
1136
- }
1137
- }
1138
- }
1139
- this.#mapEntries.set(key, entries);
1140
- return entries;
1141
- }
1142
- #declarationOf(owner, name, near) {
1143
- const key = `${owner}
1144
- ${name}
1145
- ${near ?? ""}`;
1146
- const cached = this.#declarations.get(key);
1147
- if (cached !== undefined)
1148
- return cached;
1149
- const isNear = (fileName) => {
1150
- const relative = this.#source.relative(fileName);
1151
- return !!near && (relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near);
1152
- };
1153
- const candidates = this.#source.files.filter(([fileName, words]) => !testFilePattern.test(fileName) && words.includes(`
1154
- ${name}
1155
- `) && this.#source.ownerOf(fileName) === owner).map(([fileName]) => fileName);
1156
- let found = null;
1157
- for (const fileName of [...candidates.filter(isNear), ...candidates.filter((other) => !isNear(other))]) {
1158
- const text = this.#source.readText(fileName);
1159
- if (text === undefined)
1160
- continue;
1161
- const source = this.#source.parse(fileName, text);
1162
- const declared = this.#routes.declarationIn(source, name);
1163
- if (!declared)
1164
- continue;
1165
- found = { source, statement: declared.statement, declared: declared.declared };
1166
- break;
1167
- }
1168
- this.#declarations.set(key, found);
1169
- return found;
1717
+ #madeInPlaceIdOf(name, file) {
1718
+ const made = this.#resolver.madeInPlace(file.fileName, name);
1719
+ if (made)
1720
+ return this.#guardNodeOf(made);
1721
+ const head = name.slice(name.indexOf(":") + 1, name.indexOf("@")).split(".")[0] ?? name;
1722
+ const imported = file.imports.find((item) => item.name === head);
1723
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : file.owner;
1724
+ if (from === undefined || from === frameworkOwner)
1725
+ return null;
1726
+ const scope = `${this.#resolver.inOwner(file.fileName, file.owner)}#${name}`;
1727
+ const label = guardLabelOf(name, file.guardLabels);
1728
+ return this.#addNode(this.#key("guard", file.owner, scope), "guard", file.owner, null, name, null, [], label).id;
1170
1729
  }
1171
1730
  #addRoutes() {
1172
1731
  const routes = [];
@@ -1219,22 +1778,23 @@ ${dir}`, node);
1219
1778
  }
1220
1779
  #routeGuardIdOf({ name: local, local: declared }, fact, text) {
1221
1780
  if (declared) {
1222
- const file = this.#source.relative(fact.fileName).slice(fact.owner.length + 1);
1223
- const id = this.#key("guard", fact.owner, `${file}#${local}`);
1781
+ const id = this.#key("guard", fact.owner, `${this.#resolver.inOwner(fact.fileName, fact.owner)}#${local}`);
1224
1782
  return this.#routeGuardNodeOf(id, fact.owner, local, declared.span, declared.doc, text, declared.extent);
1225
1783
  }
1226
- const imported = fact.imports.find((item) => item.name === local);
1227
- if (!imported)
1784
+ const [head = local, member] = local.split(".");
1785
+ const imported = fact.imports.find((item) => item.name === head);
1786
+ if (!imported || member && imported.imported !== "*")
1228
1787
  return null;
1229
- const name = imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local;
1230
- const from = this.#ownerOfSpecifier(fact.fileName, imported.from);
1788
+ const name = member ?? (imported.imported !== "default" && imported.imported !== "*" ? imported.imported : local);
1789
+ const from = this.#resolver.ownerOfSpecifier(fact.fileName, imported.from);
1231
1790
  if (!from || from === frameworkOwner)
1232
1791
  return null;
1233
- const found = this.#declarationOf(from, name, this.#pathOfSpecifier(fact.fileName, imported.from));
1792
+ const found = this.#resolver.declarationOf(from, name, this.#resolver.pathOfSpecifier(fact.fileName, imported.from));
1234
1793
  if (!found)
1235
1794
  return null;
1236
1795
  const { reader } = this.#source;
1237
- return this.#routeGuardNodeOf(this.#key("guard", from, name), from, name, reader.spanOf(found.source, found.declared), reader.docOf(found.source, found.statement), found.source.text, reader.extentOf(found.source, found.statement));
1796
+ const owner = this.#source.ownerOf(found.source.fileName) ?? from;
1797
+ return this.#routeGuardNodeOf(this.#key("guard", owner, `${this.#resolver.inOwner(found.source.fileName, owner)}#${found.name}`), owner, found.name, reader.spanOf(found.source, found.declared), reader.docOf(found.source, found.statement), found.source.text, found.extent);
1238
1798
  }
1239
1799
  #routeGuardNodeOf(id, owner, name, span, doc, text, extent) {
1240
1800
  const isNew = !this.#nodes.has(id);
@@ -1247,18 +1807,70 @@ ${dir}`, node);
1247
1807
  #adaptorIdOf(local, file) {
1248
1808
  const [head, member] = local.split(".");
1249
1809
  const imported = file.imports.find((item) => item.name === head);
1250
- const from = imported ? this.#ownerOfSpecifier(file.fileName, imported.from) : undefined;
1251
1810
  const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1811
+ const path = imported ? this.#resolver.pathOfSpecifier(file.fileName, imported.from) : null;
1812
+ if (imported && path === null && !frameworkPattern.test(imported.from))
1813
+ return this.#packageAdaptorOf(name, imported.from);
1814
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
1252
1815
  const declared = this.#adaptors.get(name) ?? [];
1253
- const found = from === undefined ? declared.find((adaptor) => adaptor.owner === file.owner) ?? declared.find((adaptor) => this.#visible(adaptor.owner, file.owner)) : declared.find((adaptor) => adaptor.owner === from) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#visible(adaptor.owner, from)));
1816
+ 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)));
1254
1817
  if (found)
1255
- return this.#adaptorNodeOf(found);
1818
+ return found.id;
1819
+ const reexported = from && from !== frameworkOwner ? this.#packageShimOf(from, name, path) : null;
1820
+ if (reexported)
1821
+ return this.#packageAdaptorOf(reexported.name, reexported.from);
1256
1822
  const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
1257
1823
  const detail = from === frameworkOwner ? [frameworkOwner] : [];
1258
1824
  return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
1259
1825
  }
1260
- #adaptorNodeOf({ fact, owner, fileName }) {
1261
- const id = this.#key("adaptor", owner, fact.name);
1826
+ #adaptorIn(declared, owner, path, fileName) {
1827
+ const own = declared.filter((adaptor) => adaptor.owner === owner);
1828
+ if (own.length < 2)
1829
+ return own.at(0);
1830
+ const pathOf = (adaptor) => this.#source.relative(adaptor.fileName).replace(/\.tsx?$/, "");
1831
+ 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}/`));
1832
+ return named ?? own.at(0);
1833
+ }
1834
+ #pluggedOf({ owner, module }, field) {
1835
+ const plugs = this.#plugs.get(`${module}
1836
+ ${field}`) ?? [];
1837
+ const own = plugs.filter((plug) => plug.owner === owner);
1838
+ if (!own.length && this.#serviceFields.has(`${owner ?? ""}
1839
+ ${module}
1840
+ ${field}`))
1841
+ return [];
1842
+ const inherited = own.length ? own : plugs.filter((plug) => this.#resolver.visible(plug.owner, owner));
1843
+ return [...new Set(inherited.map(({ target }) => target))];
1844
+ }
1845
+ #packageAdaptorOf(name, specifier) {
1846
+ if (frameworkPattern.test(specifier))
1847
+ return this.#addNode(this.#key("adaptor", null, name), "adaptor", null, null, name, null, [frameworkOwner]).id;
1848
+ const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
1849
+ return this.#addNode(this.#key("adaptor", pkg, name), "adaptor", null, null, name, null, [pkg]).id;
1850
+ }
1851
+ #packageShimOf(owner, name, near) {
1852
+ const reexport = this.#resolver.reexportOf(owner, name, near);
1853
+ const [shim] = reexport?.site.imports ?? [];
1854
+ if (reexport && shim && this.#resolver.pathOfSpecifier(reexport.site.fileName, shim.from) === null)
1855
+ return { name: reexport.local === "default" ? name : reexport.local, from: shim.from };
1856
+ if (reexport || !near)
1857
+ return null;
1858
+ const packages = new Set;
1859
+ for (const [fileName, words] of this.#resolver.filesIn(owner)) {
1860
+ const relative = this.#source.relative(fileName);
1861
+ const isNear = relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near;
1862
+ if (!isNear || !words.includes(`
1863
+ export
1864
+ `))
1865
+ continue;
1866
+ for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? [])
1867
+ if (this.#resolver.pathOfSpecifier(fileName, specifier) === null)
1868
+ packages.add(specifier);
1869
+ }
1870
+ const [only] = packages;
1871
+ return packages.size === 1 && only && !this.#resolver.declares(owner, name) ? { name, from: only } : null;
1872
+ }
1873
+ #adaptorNodeOf({ fact, owner, fileName, id }) {
1262
1874
  const detail = fact.refName ? [fact.refName] : [];
1263
1875
  const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
1264
1876
  if (fact.doc && !node.doc)
@@ -1266,38 +1878,22 @@ ${dir}`, node);
1266
1878
  this.#digest(node, this.#source.readText(fileName), fact.extent);
1267
1879
  return node.id;
1268
1880
  }
1269
- #guardNodeOf({ name, owner, span, scope, doc, parts }) {
1270
- const node = this.#addNode(this.#key("guard", owner, name), "guard", owner, null, name, span, scope ? [scope] : []);
1881
+ #guardNodeOf({ name, owner, span, scope, doc, parts, file, label }) {
1882
+ const id = this.#key("guard", owner, file ? `${file}#${name}` : name);
1883
+ const node = this.#addNode(id, "guard", owner, null, name, span, scope ? [scope] : [], label ?? name);
1271
1884
  if (doc && !node.doc)
1272
1885
  node.doc = doc;
1273
- const digest = node.digest ? null : this.#hashOf(...parts.map(({ fileName, extent }) => [
1274
- this.#source.readText(fileName),
1275
- extent
1276
- ]));
1277
- if (digest)
1278
- node.digest = digest;
1886
+ if (node.digest)
1887
+ return node.id;
1888
+ const digest = this.#hashOf(...parts.map(({ fileName, extent }) => [this.#source.readText(fileName), extent]));
1889
+ if (!digest)
1890
+ return node.id;
1891
+ node.digest = digest;
1892
+ const sources = [...new Set(parts.map(({ fileName }) => this.#source.relative(fileName)))].filter((source) => source !== node.at?.file);
1893
+ if (sources.length)
1894
+ node.sources = sources;
1279
1895
  return node.id;
1280
1896
  }
1281
- #ownerOfSpecifier(fileName, specifier) {
1282
- if (frameworkPattern.test(specifier))
1283
- return frameworkOwner;
1284
- const path = this.#pathOfSpecifier(fileName, specifier);
1285
- return path === null ? undefined : ownerOf(path) ?? undefined;
1286
- }
1287
- #pathOfSpecifier(fileName, specifier) {
1288
- const alias = specifier.match(aliasPattern);
1289
- if (alias)
1290
- return `${alias[1]}/${alias[2]}${specifier.slice(alias[0].length)}`;
1291
- if (!specifier.startsWith("."))
1292
- return null;
1293
- const segments = this.#source.relative(fileName).split("/").slice(0, -1);
1294
- for (const segment of specifier.split("/"))
1295
- if (segment === "..")
1296
- segments.pop();
1297
- else if (segment !== ".")
1298
- segments.push(segment);
1299
- return segments.join("/");
1300
- }
1301
1897
  #moduleDirOf(fileName) {
1302
1898
  const dir = this.#source.relative(fileName).match(moduleDirPattern)?.[1] ?? null;
1303
1899
  return dir?.startsWith("_") && !dir.startsWith("__") ? dir.slice(1) : dir;
@@ -1333,20 +1929,13 @@ ${dir}`, node);
1333
1929
  visit(constant);
1334
1930
  return [...fields.values()];
1335
1931
  }
1336
- #visible(target, from) {
1337
- return !from || !target || target === from || target.startsWith("libs/");
1338
- }
1339
- #soleVisible(declared, from) {
1340
- const visible = declared.filter((other) => this.#visible(other.owner, from));
1341
- return visible.length === 1 ? visible[0] : undefined;
1342
- }
1343
1932
  #targetNodeOf({ family, owner, refName, sourceKey }) {
1344
1933
  const isDefault = /^[A-Z]/.test(sourceKey);
1345
1934
  const id = family === "slice" || family === "query" ? this.#key(family, owner, refName, isDefault ? "" : sourceKey) : family === "model" || family === "formSetter" ? this.#key("model", owner, refName) : family === "endpoint" ? this.#key("endpoint", owner, refName, sourceKey) : null;
1346
1935
  return id && this.#nodes.has(id) ? id : null;
1347
1936
  }
1348
1937
  #membersNamed(kind, name, owner, refName) {
1349
- const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#visible(node.owner, owner));
1938
+ const nodes = (this.#members.get(`${kind}:${name}`) ?? []).filter((node) => (!refName || node.module === refName) && this.#resolver.visible(node.owner, owner));
1350
1939
  const own = nodes.filter((node) => node.owner === owner);
1351
1940
  return (own.length ? own : nodes).map((node) => node.id);
1352
1941
  }
@@ -1391,10 +1980,8 @@ st
1391
1980
  visit(source);
1392
1981
  }
1393
1982
  #link(fileName, source, identifier, chain, context, owner) {
1394
- const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#plugs.get(`${context.owner ?? ""}
1395
- ${context.module}
1396
- ${chain[1]}`) : undefined;
1397
- const resolved = plugged ? [plugged] : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
1983
+ const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#pluggedOf(context, chain[1]) : [];
1984
+ const resolved = plugged.length ? plugged : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
1398
1985
  const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
1399
1986
  if (!targets.length)
1400
1987
  return;
@@ -1700,6 +2287,10 @@ var endpointNames = (key) => {
1700
2287
  };
1701
2288
 
1702
2289
  // pkgs/@akanjs/language/nameTable.ts
2290
+ var guardDetailOf = (facts, guards) => {
2291
+ const labels = Object.assign({}, ...facts.map((file) => file.guardLabels));
2292
+ return guards.length ? [`guards: ${guards.map((guard) => guardLabelOf(guard, labels)).join(", ")}`] : [];
2293
+ };
1703
2294
  var capitalize3 = (text) => text.charAt(0).toUpperCase() + text.slice(1);
1704
2295
 
1705
2296
  class NameTable {
@@ -1799,7 +2390,7 @@ class NameTable {
1799
2390
  role: "sig",
1800
2391
  gate: "sig",
1801
2392
  label: sliceClass.name,
1802
- detail: sliceClass.guards.length ? [`guards: ${sliceClass.guards.join(", ")}`] : []
2393
+ detail: guardDetailOf(facts, sliceClass.guards)
1803
2394
  });
1804
2395
  if (modelDoc)
1805
2396
  this.#add({
@@ -1864,7 +2455,7 @@ class NameTable {
1864
2455
  sourceKey: slice.name,
1865
2456
  span: slice.span,
1866
2457
  label: `slice ${refName} (default)`,
1867
- detail: slice.guards.length ? [`guards: ${slice.guards.join(", ")}`] : []
2458
+ detail: guardDetailOf(facts, slice.guards)
1868
2459
  };
1869
2460
  this.#addSliceNames(defaultTarget, refName, "", modelNameSet);
1870
2461
  for (const key of slice.keys)
@@ -1874,7 +2465,7 @@ class NameTable {
1874
2465
  sourceKey: key.name,
1875
2466
  span: key.span,
1876
2467
  label: `slice ${key.name}`,
1877
- detail: [...key.guards.length ? [`guards: ${key.guards.join(", ")}`] : [], ...key.live ? ["live"] : []]
2468
+ detail: [...guardDetailOf(facts, key.guards), ...key.live ? ["live"] : []]
1878
2469
  }, refName, key.name, modelNameSet, slice.name);
1879
2470
  }
1880
2471
  if (filterClass) {
@@ -1931,7 +2522,7 @@ class NameTable {
1931
2522
  role: key.verb === "pubsub" ? "subscribe" : "listen",
1932
2523
  gate: null,
1933
2524
  label: `${key.verb} ${key.name}`,
1934
- detail: key.guards.length ? [`guards: ${key.guards.join(", ")}`] : []
2525
+ detail: guardDetailOf(facts, key.guards)
1935
2526
  });
1936
2527
  }
1937
2528
  }
@@ -1989,14 +2580,12 @@ var dictionaryPattern = /\.dictionary\.ts$/;
1989
2580
  var srvkitPattern = /\/srvkit\//;
1990
2581
  var wordPattern = /[A-Za-z_$][\w$]*/g;
1991
2582
  var identifierPattern = /^[A-Za-z_$][\w$]*$/;
1992
- var regExpSpecials = /[\\^$.*+?()[\]{}|]/g;
1993
2583
  var stateReceivers = new Set(["use", "sel", "get", "do"]);
1994
2584
  var lensFamilies = new Set(["slice", "query", "formSetter", "storeState", "endpoint"]);
1995
2585
  var modellessFamilies = new Set(["query", "storeState", "endpoint"]);
1996
2586
  var gateWords = { srv: "srv", sig: "sig", db: "db", slice: "slice" };
1997
2587
  var parseCacheSize = 64;
1998
2588
  var pathOf = (fileName) => fileName.replace(/\\/g, "/");
1999
- var escapeRegExp = (text) => text.replace(regExpSpecials, (special) => `\\${special}`);
2000
2589
  var receiverGap = String.raw`(?:\s|\)|\/\*[\s\S]*?\*\/)*`;
2001
2590
 
2002
2591
  class AkanIndex {
@@ -2127,6 +2716,8 @@ canPass
2127
2716
  adapt
2128
2717
  `) || words.includes(`
2129
2718
  dangerouslyAdapt
2719
+ `) || words.includes(`
2720
+ applyAdaptor
2130
2721
  `);
2131
2722
  }
2132
2723
  static #sourceKeyOf({ family, sourceKey, label, detail, span }) {