@akanjs/language 3.2.0-rc.2 → 3.2.0-rc.5

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.
@@ -33,6 +33,8 @@ interface Declaration {
33
33
  name: string;
34
34
  }
35
35
  export declare const frameworkOwner = "akanjs";
36
+ export declare const frameworkPattern: RegExp;
37
+ export declare const starExportPattern: RegExp;
36
38
  export declare class GuardResolver {
37
39
  #private;
38
40
  constructor(source: AkanGraphSource, routes: RouteFactReader);
package/index.cjs CHANGED
@@ -80,6 +80,9 @@ class AkanGraphDiff {
80
80
  ...head.edges.flatMap((edge) => !baseKeys.has(edgeKeyOf(edge)) ? [{ ...edge, change: "added" }] : marked.has(edge.from) || marked.has(edge.to) ? [edge] : []),
81
81
  ...base.edges.filter((edge) => !headKeys.has(edgeKeyOf(edge))).map((edge) => ({ ...edge, change: "removed" }))
82
82
  ];
83
+ const shown = new Set([...marked.keys(), ...edges.flatMap((edge) => [edge.from, edge.to])]);
84
+ const listed = new Set(edges.map(edgeKeyOf));
85
+ edges.push(...head.edges.filter((edge) => edge.kind === "binds" && shown.has(edge.from) && !listed.has(edgeKeyOf(edge))));
83
86
  const context = edges.flatMap((edge) => [edge.from, edge.to]).filter((id) => !marked.has(id)).flatMap((id) => {
84
87
  const node = headNodes.get(id) ?? baseNodes.get(id);
85
88
  return node ? [node] : [];
@@ -537,7 +540,7 @@ var createFactReader = (ts, ast) => {
537
540
  const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
538
541
  return `${ms / size}${unit}`;
539
542
  };
540
- const modesOf = (option) => {
543
+ const modesOf = (option, type) => {
541
544
  const object = option && unwrap(option);
542
545
  if (!object || !ts.isObjectLiteralExpression(object))
543
546
  return [];
@@ -547,9 +550,9 @@ var createFactReader = (ts, ast) => {
547
550
  if (name === "enabled")
548
551
  return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
549
552
  if (name === "once")
550
- return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
553
+ return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
551
554
  if (name === "lock")
552
- return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
555
+ return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
553
556
  if (name !== "serverMode" && name !== "operationMode")
554
557
  return [];
555
558
  const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
@@ -567,7 +570,8 @@ var createFactReader = (ts, ast) => {
567
570
  const { root } = chainOf(property.initializer);
568
571
  const type = root ? verbOf(root.expression) : null;
569
572
  const [first, second] = root?.arguments ?? [];
570
- const schedule = type === "cron" && first ? ts.isStringLiteralLike(first) ? first.text : first.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
573
+ const cron = type === "cron" && first ? unwrap(first) : null;
574
+ const schedule = cron ? ts.isStringLiteralLike(cron) ? cron.text : cron.getText(source) : type === "interval" || type === "timeout" ? durationOf(first, source) : null;
571
575
  const option = type === "initialize" || type === "destroy" ? first : second;
572
576
  return [
573
577
  {
@@ -576,7 +580,7 @@ var createFactReader = (ts, ast) => {
576
580
  extent: extentOf(source, property),
577
581
  type,
578
582
  schedule,
579
- modes: modesOf(option)
583
+ modes: modesOf(option, type)
580
584
  }
581
585
  ];
582
586
  });
@@ -593,6 +597,19 @@ var createFactReader = (ts, ast) => {
593
597
  return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
594
598
  });
595
599
  };
600
+ const adaptorBindingsIn = (source) => {
601
+ const bindings = [];
602
+ const visit = (node) => {
603
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "applyAdaptor") {
604
+ const [role, adaptor] = node.arguments.map(guardNameOf);
605
+ if (role && adaptor)
606
+ bindings.push({ role, adaptor, span: spanOf(source, node.expression.name) });
607
+ }
608
+ ts.forEachChild(node, visit);
609
+ };
610
+ visit(source);
611
+ return bindings;
612
+ };
596
613
  const endpointKeysOf = (source, builder, made, model) => {
597
614
  const verbOf = verbReaderOf(builder?.parameters[0]);
598
615
  return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
@@ -713,6 +730,7 @@ var createFactReader = (ts, ast) => {
713
730
  endpoints: [],
714
731
  internals: [],
715
732
  adaptors: [],
733
+ adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
716
734
  stores: []
717
735
  };
718
736
  for (const statement of source.statements) {
@@ -737,7 +755,8 @@ var createFactReader = (ts, ast) => {
737
755
  ...name,
738
756
  model: moduleRefOf(call.arguments[0], "db"),
739
757
  methods: methodsOf(source, statement),
740
- plugs: plugsOf(source, builder)
758
+ plugs: plugsOf(source, builder),
759
+ fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
741
760
  });
742
761
  else if (callee === "slice") {
743
762
  const model = moduleRefOf(call.arguments[0], "srv");
@@ -1502,6 +1521,7 @@ class GraphBuilder {
1502
1521
  #resolver;
1503
1522
  #adaptors = new Map;
1504
1523
  #plugs = new Map;
1524
+ #serviceFields = new Set;
1505
1525
  constructor(source) {
1506
1526
  this.#source = source;
1507
1527
  this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
@@ -1517,16 +1537,26 @@ class GraphBuilder {
1517
1537
  ...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
1518
1538
  ];
1519
1539
  const typed = [];
1540
+ const adaptorCounts = new Map;
1520
1541
  for (const file of facts)
1521
1542
  for (const fact of file.adaptors) {
1522
- const declared = { fact, owner: file.owner, fileName: file.fileName };
1523
- this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
1524
- this.#adaptorNodeOf(declared);
1543
+ const key = this.#key("adaptor", file.owner, fact.name);
1544
+ adaptorCounts.set(key, (adaptorCounts.get(key) ?? 0) + 1);
1525
1545
  }
1546
+ const declaredAdaptors = facts.flatMap((file) => file.adaptors.map((fact) => {
1547
+ const key = this.#key("adaptor", file.owner, fact.name);
1548
+ const id = (adaptorCounts.get(key) ?? 0) > 1 ? this.#key("adaptor", file.owner, `${this.#resolver.inOwner(file.fileName, file.owner)}#${fact.name}`) : key;
1549
+ const declared = { fact, owner: file.owner, fileName: file.fileName, id };
1550
+ this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
1551
+ this.#adaptorNodeOf(declared);
1552
+ return { declared, file };
1553
+ }));
1554
+ for (const { declared, file } of declaredAdaptors)
1555
+ for (const plug of declared.fact.plugs)
1556
+ this.#addEdge(declared.id, this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
1526
1557
  for (const file of facts)
1527
- for (const fact of file.adaptors)
1528
- for (const plug of fact.plugs)
1529
- this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
1558
+ for (const binding of file.adaptorBindings)
1559
+ this.#addEdge(this.#adaptorIdOf(binding.role, file), this.#adaptorIdOf(binding.adaptor, file), "binds", file.owner, this.#location(binding.span));
1530
1560
  for (const guard of this.#resolver.declare(facts))
1531
1561
  this.#guardNodeOf(guard);
1532
1562
  for (const file of facts) {
@@ -1559,10 +1589,18 @@ class GraphBuilder {
1559
1589
  }
1560
1590
  for (const method of service.methods)
1561
1591
  this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
1562
- for (const plug of service.plugs)
1563
- this.#plugs.set(`${owner ?? ""}
1592
+ for (const plug of service.plugs) {
1593
+ const key = `${refName}
1594
+ ${plug.name}`;
1595
+ this.#plugs.set(key, [
1596
+ ...this.#plugs.get(key) ?? [],
1597
+ { owner, target: this.#adaptorIdOf(plug.target, file) }
1598
+ ]);
1599
+ }
1600
+ for (const field of service.fields)
1601
+ this.#serviceFields.add(`${owner ?? ""}
1564
1602
  ${refName}
1565
- ${plug.name}`, this.#adaptorIdOf(plug.target, file));
1603
+ ${field}`);
1566
1604
  }
1567
1605
  for (const filter of file.filters) {
1568
1606
  if (!filter.name.endsWith("Filter"))
@@ -1821,18 +1859,70 @@ ${dir}`, node);
1821
1859
  #adaptorIdOf(local, file) {
1822
1860
  const [head, member] = local.split(".");
1823
1861
  const imported = file.imports.find((item) => item.name === head);
1824
- const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
1825
1862
  const name = member ?? (imported && imported.imported !== "default" && imported.imported !== "*" ? imported.imported : head);
1863
+ const path = imported ? this.#resolver.pathOfSpecifier(file.fileName, imported.from) : null;
1864
+ if (imported && path === null && !frameworkPattern.test(imported.from))
1865
+ return this.#packageAdaptorOf(name, imported.from);
1866
+ const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
1826
1867
  const declared = this.#adaptors.get(name) ?? [];
1827
- const found = from === undefined ? declared.find((adaptor) => adaptor.owner === file.owner) ?? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, file.owner)) : declared.find((adaptor) => adaptor.owner === from) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#resolver.visible(adaptor.owner, from)));
1868
+ const found = from === undefined ? this.#adaptorIn(declared, file.owner, path, file.fileName) ?? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, file.owner)) : this.#adaptorIn(declared, from, path, file.fileName) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#resolver.visible(adaptor.owner, from)));
1828
1869
  if (found)
1829
- return this.#adaptorNodeOf(found);
1870
+ return found.id;
1871
+ const reexported = from && from !== frameworkOwner ? this.#packageShimOf(from, name, path) : null;
1872
+ if (reexported)
1873
+ return this.#packageAdaptorOf(reexported.name, reexported.from);
1830
1874
  const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
1831
1875
  const detail = from === frameworkOwner ? [frameworkOwner] : [];
1832
1876
  return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
1833
1877
  }
1834
- #adaptorNodeOf({ fact, owner, fileName }) {
1835
- const id = this.#key("adaptor", owner, fact.name);
1878
+ #adaptorIn(declared, owner, path, fileName) {
1879
+ const own = declared.filter((adaptor) => adaptor.owner === owner);
1880
+ if (own.length < 2)
1881
+ return own.at(0);
1882
+ const pathOf = (adaptor) => this.#source.relative(adaptor.fileName).replace(/\.tsx?$/, "");
1883
+ const named = path === null ? own.find((adaptor) => adaptor.fileName === fileName) : own.find((adaptor) => pathOf(adaptor) === path || pathOf(adaptor) === `${path}/index`) ?? own.find((adaptor) => pathOf(adaptor).startsWith(`${path}/`));
1884
+ return named ?? own.at(0);
1885
+ }
1886
+ #pluggedOf({ owner, module: module2 }, field) {
1887
+ const plugs = this.#plugs.get(`${module2}
1888
+ ${field}`) ?? [];
1889
+ const own = plugs.filter((plug) => plug.owner === owner);
1890
+ if (!own.length && this.#serviceFields.has(`${owner ?? ""}
1891
+ ${module2}
1892
+ ${field}`))
1893
+ return [];
1894
+ const inherited = own.length ? own : plugs.filter((plug) => this.#resolver.visible(plug.owner, owner));
1895
+ return [...new Set(inherited.map(({ target }) => target))];
1896
+ }
1897
+ #packageAdaptorOf(name, specifier) {
1898
+ if (frameworkPattern.test(specifier))
1899
+ return this.#addNode(this.#key("adaptor", null, name), "adaptor", null, null, name, null, [frameworkOwner]).id;
1900
+ const pkg = specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
1901
+ return this.#addNode(this.#key("adaptor", pkg, name), "adaptor", null, null, name, null, [pkg]).id;
1902
+ }
1903
+ #packageShimOf(owner, name, near) {
1904
+ const reexport = this.#resolver.reexportOf(owner, name, near);
1905
+ const [shim] = reexport?.site.imports ?? [];
1906
+ if (reexport && shim && this.#resolver.pathOfSpecifier(reexport.site.fileName, shim.from) === null)
1907
+ return { name: reexport.local === "default" ? name : reexport.local, from: shim.from };
1908
+ if (reexport || !near)
1909
+ return null;
1910
+ const packages = new Set;
1911
+ for (const [fileName, words] of this.#resolver.filesIn(owner)) {
1912
+ const relative = this.#source.relative(fileName);
1913
+ const isNear = relative.startsWith(`${near}/`) || relative.replace(/\.tsx?$/, "") === near;
1914
+ if (!isNear || !words.includes(`
1915
+ export
1916
+ `))
1917
+ continue;
1918
+ for (const [, specifier = ""] of this.#source.readText(fileName)?.matchAll(starExportPattern) ?? [])
1919
+ if (this.#resolver.pathOfSpecifier(fileName, specifier) === null)
1920
+ packages.add(specifier);
1921
+ }
1922
+ const [only] = packages;
1923
+ return packages.size === 1 && only && !this.#resolver.declares(owner, name) ? { name, from: only } : null;
1924
+ }
1925
+ #adaptorNodeOf({ fact, owner, fileName, id }) {
1836
1926
  const detail = fact.refName ? [fact.refName] : [];
1837
1927
  const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
1838
1928
  if (fact.doc && !node.doc)
@@ -1942,10 +2032,8 @@ st
1942
2032
  visit(source);
1943
2033
  }
1944
2034
  #link(fileName, source, identifier, chain, context, owner) {
1945
- const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#plugs.get(`${context.owner ?? ""}
1946
- ${context.module}
1947
- ${chain[1]}`) : undefined;
1948
- const resolved = plugged ? [plugged] : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
2035
+ const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#pluggedOf(context, chain[1]) : [];
2036
+ const resolved = plugged.length ? plugged : this.#source.resolve(fileName, identifier).map((target) => this.#targetNodeOf(target)).filter((id) => id !== null);
1949
2037
  const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
1950
2038
  if (!targets.length)
1951
2039
  return;
@@ -2680,6 +2768,8 @@ canPass
2680
2768
  adapt
2681
2769
  `) || words.includes(`
2682
2770
  dangerouslyAdapt
2771
+ `) || words.includes(`
2772
+ applyAdaptor
2683
2773
  `);
2684
2774
  }
2685
2775
  static #sourceKeyOf({ family, sourceKey, label, detail, span }) {
package/index.js CHANGED
@@ -28,6 +28,9 @@ class AkanGraphDiff {
28
28
  ...head.edges.flatMap((edge) => !baseKeys.has(edgeKeyOf(edge)) ? [{ ...edge, change: "added" }] : marked.has(edge.from) || marked.has(edge.to) ? [edge] : []),
29
29
  ...base.edges.filter((edge) => !headKeys.has(edgeKeyOf(edge))).map((edge) => ({ ...edge, change: "removed" }))
30
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))));
31
34
  const context = edges.flatMap((edge) => [edge.from, edge.to]).filter((id) => !marked.has(id)).flatMap((id) => {
32
35
  const node = headNodes.get(id) ?? baseNodes.get(id);
33
36
  return node ? [node] : [];
@@ -485,7 +488,7 @@ var createFactReader = (ts, ast) => {
485
488
  const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
486
489
  return `${ms / size}${unit}`;
487
490
  };
488
- const modesOf = (option) => {
491
+ const modesOf = (option, type) => {
489
492
  const object = option && unwrap(option);
490
493
  if (!object || !ts.isObjectLiteralExpression(object))
491
494
  return [];
@@ -495,9 +498,9 @@ var createFactReader = (ts, ast) => {
495
498
  if (name === "enabled")
496
499
  return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
497
500
  if (name === "once")
498
- return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
501
+ return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
499
502
  if (name === "lock")
500
- return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
503
+ return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
501
504
  if (name !== "serverMode" && name !== "operationMode")
502
505
  return [];
503
506
  const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
@@ -515,7 +518,8 @@ var createFactReader = (ts, ast) => {
515
518
  const { root } = chainOf(property.initializer);
516
519
  const type = root ? verbOf(root.expression) : null;
517
520
  const [first, second] = root?.arguments ?? [];
518
- 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;
519
523
  const option = type === "initialize" || type === "destroy" ? first : second;
520
524
  return [
521
525
  {
@@ -524,7 +528,7 @@ var createFactReader = (ts, ast) => {
524
528
  extent: extentOf(source, property),
525
529
  type,
526
530
  schedule,
527
- modes: modesOf(option)
531
+ modes: modesOf(option, type)
528
532
  }
529
533
  ];
530
534
  });
@@ -541,6 +545,19 @@ var createFactReader = (ts, ast) => {
541
545
  return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
542
546
  });
543
547
  };
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
+ };
544
561
  const endpointKeysOf = (source, builder, made, model) => {
545
562
  const verbOf = verbReaderOf(builder?.parameters[0]);
546
563
  return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
@@ -661,6 +678,7 @@ var createFactReader = (ts, ast) => {
661
678
  endpoints: [],
662
679
  internals: [],
663
680
  adaptors: [],
681
+ adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
664
682
  stores: []
665
683
  };
666
684
  for (const statement of source.statements) {
@@ -685,7 +703,8 @@ var createFactReader = (ts, ast) => {
685
703
  ...name,
686
704
  model: moduleRefOf(call.arguments[0], "db"),
687
705
  methods: methodsOf(source, statement),
688
- plugs: plugsOf(source, builder)
706
+ plugs: plugsOf(source, builder),
707
+ fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
689
708
  });
690
709
  else if (callee === "slice") {
691
710
  const model = moduleRefOf(call.arguments[0], "srv");
@@ -1450,6 +1469,7 @@ class GraphBuilder {
1450
1469
  #resolver;
1451
1470
  #adaptors = new Map;
1452
1471
  #plugs = new Map;
1472
+ #serviceFields = new Set;
1453
1473
  constructor(source) {
1454
1474
  this.#source = source;
1455
1475
  this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
@@ -1465,16 +1485,26 @@ class GraphBuilder {
1465
1485
  ...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
1466
1486
  ];
1467
1487
  const typed = [];
1488
+ const adaptorCounts = new Map;
1468
1489
  for (const file of facts)
1469
1490
  for (const fact of file.adaptors) {
1470
- const declared = { fact, owner: file.owner, fileName: file.fileName };
1471
- this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
1472
- this.#adaptorNodeOf(declared);
1491
+ const key = this.#key("adaptor", file.owner, fact.name);
1492
+ adaptorCounts.set(key, (adaptorCounts.get(key) ?? 0) + 1);
1473
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));
1474
1505
  for (const file of facts)
1475
- for (const fact of file.adaptors)
1476
- for (const plug of fact.plugs)
1477
- this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
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));
1478
1508
  for (const guard of this.#resolver.declare(facts))
1479
1509
  this.#guardNodeOf(guard);
1480
1510
  for (const file of facts) {
@@ -1507,10 +1537,18 @@ class GraphBuilder {
1507
1537
  }
1508
1538
  for (const method of service.methods)
1509
1539
  this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
1510
- for (const plug of service.plugs)
1511
- 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 ?? ""}
1512
1550
  ${refName}
1513
- ${plug.name}`, this.#adaptorIdOf(plug.target, file));
1551
+ ${field}`);
1514
1552
  }
1515
1553
  for (const filter of file.filters) {
1516
1554
  if (!filter.name.endsWith("Filter"))
@@ -1769,18 +1807,70 @@ ${dir}`, node);
1769
1807
  #adaptorIdOf(local, file) {
1770
1808
  const [head, member] = local.split(".");
1771
1809
  const imported = file.imports.find((item) => item.name === head);
1772
- const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
1773
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;
1774
1815
  const declared = this.#adaptors.get(name) ?? [];
1775
- const found = from === undefined ? declared.find((adaptor) => adaptor.owner === file.owner) ?? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, file.owner)) : declared.find((adaptor) => adaptor.owner === from) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#resolver.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)));
1776
1817
  if (found)
1777
- 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);
1778
1822
  const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
1779
1823
  const detail = from === frameworkOwner ? [frameworkOwner] : [];
1780
1824
  return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
1781
1825
  }
1782
- #adaptorNodeOf({ fact, owner, fileName }) {
1783
- 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 }) {
1784
1874
  const detail = fact.refName ? [fact.refName] : [];
1785
1875
  const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
1786
1876
  if (fact.doc && !node.doc)
@@ -1890,10 +1980,8 @@ st
1890
1980
  visit(source);
1891
1981
  }
1892
1982
  #link(fileName, source, identifier, chain, context, owner) {
1893
- const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#plugs.get(`${context.owner ?? ""}
1894
- ${context.module}
1895
- ${chain[1]}`) : undefined;
1896
- 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);
1897
1985
  const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
1898
1986
  if (!targets.length)
1899
1987
  return;
@@ -2628,6 +2716,8 @@ canPass
2628
2716
  adapt
2629
2717
  `) || words.includes(`
2630
2718
  dangerouslyAdapt
2719
+ `) || words.includes(`
2720
+ applyAdaptor
2631
2721
  `);
2632
2722
  }
2633
2723
  static #sourceKeyOf({ family, sourceKey, label, detail, span }) {
package/moduleFacts.d.ts CHANGED
@@ -63,6 +63,7 @@ export interface PlugFact extends NamedFact {
63
63
  export interface ServiceClassFact extends ModuleClassFact {
64
64
  methods: MemberFact[];
65
65
  plugs: PlugFact[];
66
+ fields: string[];
66
67
  }
67
68
  export interface InternalKeyFact extends MemberFact {
68
69
  type: string | null;
@@ -77,6 +78,11 @@ export interface AdaptorFact extends MemberFact {
77
78
  plugs: PlugFact[];
78
79
  doc: string | null;
79
80
  }
81
+ export interface AdaptorBindingFact {
82
+ role: string;
83
+ adaptor: string;
84
+ span: AkanSpan;
85
+ }
80
86
  export interface StoreClassFact extends ModuleClassFact {
81
87
  stateKeys: NamedFact[];
82
88
  actions: MemberFact[];
@@ -103,6 +109,7 @@ export interface FileFacts {
103
109
  endpoints: EndpointClassFact[];
104
110
  internals: InternalClassFact[];
105
111
  adaptors: AdaptorFact[];
112
+ adaptorBindings: AdaptorBindingFact[];
106
113
  stores: StoreClassFact[];
107
114
  }
108
115
  export declare const testFilePattern: RegExp;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/language",
3
- "version": "3.2.0-rc.2",
3
+ "version": "3.2.0-rc.5",
4
4
  "description": "Editor-independent Akan.js language core: resolves derived names (slices, queries, CRUD, form setters) back to the source key that produced them.",
5
5
  "sourceType": "module",
6
6
  "type": "module",
package/server.js CHANGED
@@ -384,7 +384,7 @@ var createFactReader = (ts, ast) => {
384
384
  const [size, unit] = intervalUnits.find(([size]) => ms >= size && ms % size === 0) ?? [1, "ms"];
385
385
  return `${ms / size}${unit}`;
386
386
  };
387
- const modesOf = (option) => {
387
+ const modesOf = (option, type) => {
388
388
  const object = option && unwrap(option);
389
389
  if (!object || !ts.isObjectLiteralExpression(object))
390
390
  return [];
@@ -394,9 +394,9 @@ var createFactReader = (ts, ast) => {
394
394
  if (name === "enabled")
395
395
  return value.kind === ts.SyntaxKind.FalseKeyword ? ["disabled"] : [];
396
396
  if (name === "once")
397
- return value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
397
+ return type === "initialize" && value.kind === ts.SyntaxKind.TrueKeyword ? ["once"] : [];
398
398
  if (name === "lock")
399
- return value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
399
+ return type === "interval" && value.kind === ts.SyntaxKind.FalseKeyword ? ["unlocked"] : [];
400
400
  if (name !== "serverMode" && name !== "operationMode")
401
401
  return [];
402
402
  const values = ts.isArrayLiteralExpression(value) ? [...value.elements] : [value];
@@ -414,7 +414,8 @@ var createFactReader = (ts, ast) => {
414
414
  const { root } = chainOf(property.initializer);
415
415
  const type = root ? verbOf(root.expression) : null;
416
416
  const [first, second] = root?.arguments ?? [];
417
- 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;
418
419
  const option = type === "initialize" || type === "destroy" ? first : second;
419
420
  return [
420
421
  {
@@ -423,7 +424,7 @@ var createFactReader = (ts, ast) => {
423
424
  extent: extentOf(source, property),
424
425
  type,
425
426
  schedule,
426
- modes: modesOf(option)
427
+ modes: modesOf(option, type)
427
428
  }
428
429
  ];
429
430
  });
@@ -440,6 +441,19 @@ var createFactReader = (ts, ast) => {
440
441
  return name && targetName ? [{ name, span: spanOf(source, property.name), target: targetName }] : [];
441
442
  });
442
443
  };
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
+ };
443
457
  const endpointKeysOf = (source, builder, made, model) => {
444
458
  const verbOf = verbReaderOf(builder?.parameters[0]);
445
459
  return (returnedObject(builder)?.properties ?? []).flatMap((property) => {
@@ -560,6 +574,7 @@ var createFactReader = (ts, ast) => {
560
574
  endpoints: [],
561
575
  internals: [],
562
576
  adaptors: [],
577
+ adaptorBindings: source.text.includes("applyAdaptor") ? adaptorBindingsIn(source) : [],
563
578
  stores: []
564
579
  };
565
580
  for (const statement of source.statements) {
@@ -584,7 +599,8 @@ var createFactReader = (ts, ast) => {
584
599
  ...name,
585
600
  model: moduleRefOf(call.arguments[0], "db"),
586
601
  methods: methodsOf(source, statement),
587
- plugs: plugsOf(source, builder)
602
+ plugs: plugsOf(source, builder),
603
+ fields: objectKeys(source, returnedObject(builder)).map(({ name }) => name)
588
604
  });
589
605
  else if (callee === "slice") {
590
606
  const model = moduleRefOf(call.arguments[0], "srv");
@@ -1349,6 +1365,7 @@ class GraphBuilder {
1349
1365
  #resolver;
1350
1366
  #adaptors = new Map;
1351
1367
  #plugs = new Map;
1368
+ #serviceFields = new Set;
1352
1369
  constructor(source) {
1353
1370
  this.#source = source;
1354
1371
  this.#routes = new RouteFactReader(source.ts, source.reader, source.parse);
@@ -1364,16 +1381,26 @@ class GraphBuilder {
1364
1381
  ...[...constantsByOwner].filter(([key]) => key !== owner && key?.startsWith("libs/")).flatMap(([, list]) => list)
1365
1382
  ];
1366
1383
  const typed = [];
1384
+ const adaptorCounts = new Map;
1367
1385
  for (const file of facts)
1368
1386
  for (const fact of file.adaptors) {
1369
- const declared = { fact, owner: file.owner, fileName: file.fileName };
1370
- this.#adaptors.set(fact.name, [...this.#adaptors.get(fact.name) ?? [], declared]);
1371
- this.#adaptorNodeOf(declared);
1387
+ const key = this.#key("adaptor", file.owner, fact.name);
1388
+ adaptorCounts.set(key, (adaptorCounts.get(key) ?? 0) + 1);
1372
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));
1373
1401
  for (const file of facts)
1374
- for (const fact of file.adaptors)
1375
- for (const plug of fact.plugs)
1376
- this.#addEdge(this.#key("adaptor", file.owner, fact.name), this.#adaptorIdOf(plug.target, file), "plugs", plug.name, this.#location(plug.span));
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));
1377
1404
  for (const guard of this.#resolver.declare(facts))
1378
1405
  this.#guardNodeOf(guard);
1379
1406
  for (const file of facts) {
@@ -1406,10 +1433,18 @@ class GraphBuilder {
1406
1433
  }
1407
1434
  for (const method of service.methods)
1408
1435
  this.#addMember("method", owner, refName, method, `${service.name}.${method.name}`, file.fileName);
1409
- for (const plug of service.plugs)
1410
- 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 ?? ""}
1411
1446
  ${refName}
1412
- ${plug.name}`, this.#adaptorIdOf(plug.target, file));
1447
+ ${field}`);
1413
1448
  }
1414
1449
  for (const filter of file.filters) {
1415
1450
  if (!filter.name.endsWith("Filter"))
@@ -1668,18 +1703,70 @@ ${dir}`, node);
1668
1703
  #adaptorIdOf(local, file) {
1669
1704
  const [head, member] = local.split(".");
1670
1705
  const imported = file.imports.find((item) => item.name === head);
1671
- const from = imported ? this.#resolver.ownerOfSpecifier(file.fileName, imported.from) : undefined;
1672
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;
1673
1711
  const declared = this.#adaptors.get(name) ?? [];
1674
- const found = from === undefined ? declared.find((adaptor) => adaptor.owner === file.owner) ?? declared.find((adaptor) => this.#resolver.visible(adaptor.owner, file.owner)) : declared.find((adaptor) => adaptor.owner === from) ?? (from === frameworkOwner ? undefined : declared.find((adaptor) => this.#resolver.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)));
1675
1713
  if (found)
1676
- 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);
1677
1718
  const owner = from === frameworkOwner ? null : imported ? from ?? null : file.owner;
1678
1719
  const detail = from === frameworkOwner ? [frameworkOwner] : [];
1679
1720
  return this.#addNode(this.#key("adaptor", owner, name), "adaptor", owner, null, name, null, detail).id;
1680
1721
  }
1681
- #adaptorNodeOf({ fact, owner, fileName }) {
1682
- 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 }) {
1683
1770
  const detail = fact.refName ? [fact.refName] : [];
1684
1771
  const node = this.#addNode(id, "adaptor", owner, null, fact.name, fact.span, detail);
1685
1772
  if (fact.doc && !node.doc)
@@ -1789,10 +1876,8 @@ st
1789
1876
  visit(source);
1790
1877
  }
1791
1878
  #link(fileName, source, identifier, chain, context, owner) {
1792
- const plugged = chain?.length === 2 && chain[0] === "this" && context.kind === "method" && this.#isServiceThis(identifier) ? this.#plugs.get(`${context.owner ?? ""}
1793
- ${context.module}
1794
- ${chain[1]}`) : undefined;
1795
- 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);
1796
1881
  const targets = resolved.length ? resolved : this.#plainTargetsOf(chain, identifier.text, context, owner);
1797
1882
  if (!targets.length)
1798
1883
  return;
@@ -2527,6 +2612,8 @@ canPass
2527
2612
  adapt
2528
2613
  `) || words.includes(`
2529
2614
  dangerouslyAdapt
2615
+ `) || words.includes(`
2616
+ applyAdaptor
2530
2617
  `);
2531
2618
  }
2532
2619
  static #sourceKeyOf({ family, sourceKey, label, detail, span }) {