@fairfox/polly 0.10.1 → 0.12.0

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.
Files changed (41) hide show
  1. package/README.md +186 -0
  2. package/dist/src/client/index.d.ts +33 -0
  3. package/dist/src/client/index.js +586 -0
  4. package/dist/src/client/index.js.map +13 -0
  5. package/dist/src/client/wrapper.d.ts +54 -0
  6. package/dist/src/core/clock.d.ts +63 -0
  7. package/dist/src/elysia/index.d.ts +43 -0
  8. package/dist/src/elysia/index.js +241 -0
  9. package/dist/src/elysia/index.js.map +12 -0
  10. package/dist/src/elysia/plugin.d.ts +5 -0
  11. package/dist/src/elysia/tla-generator.d.ts +16 -0
  12. package/dist/src/elysia/types.d.ts +137 -0
  13. package/dist/src/utils/function-serialization.d.ts +14 -0
  14. package/dist/tools/analysis/src/extract/adr.d.ts +37 -0
  15. package/dist/tools/analysis/src/extract/architecture.d.ts +42 -0
  16. package/dist/tools/analysis/src/extract/contexts.d.ts +74 -0
  17. package/dist/tools/analysis/src/extract/flows.d.ts +68 -0
  18. package/dist/tools/analysis/src/extract/handlers.d.ts +330 -0
  19. package/dist/tools/analysis/src/extract/index.d.ts +9 -0
  20. package/dist/tools/analysis/src/extract/integrations.d.ts +77 -0
  21. package/dist/tools/analysis/src/extract/manifest.d.ts +64 -0
  22. package/dist/tools/analysis/src/extract/project-detector.d.ts +103 -0
  23. package/dist/tools/analysis/src/extract/relationships.d.ts +119 -0
  24. package/dist/tools/analysis/src/extract/types.d.ts +139 -0
  25. package/dist/tools/analysis/src/index.d.ts +2 -0
  26. package/dist/tools/analysis/src/types/adr.d.ts +39 -0
  27. package/dist/tools/analysis/src/types/architecture.d.ts +198 -0
  28. package/dist/tools/analysis/src/types/core.d.ts +178 -0
  29. package/dist/tools/analysis/src/types/index.d.ts +4 -0
  30. package/dist/tools/teach/src/cli.js +376 -81
  31. package/dist/tools/teach/src/cli.js.map +13 -13
  32. package/dist/tools/teach/src/index.d.ts +28 -0
  33. package/dist/tools/teach/src/index.js +233 -84
  34. package/dist/tools/teach/src/index.js.map +13 -13
  35. package/dist/tools/verify/src/cli.js +127 -26
  36. package/dist/tools/verify/src/cli.js.map +7 -7
  37. package/dist/tools/visualize/src/cli.js +213 -78
  38. package/dist/tools/visualize/src/cli.js.map +11 -11
  39. package/dist/tools/visualize/src/codegen/structurizr.d.ts +343 -0
  40. package/dist/tools/visualize/src/types/structurizr.d.ts +235 -0
  41. package/package.json +22 -2
@@ -94,25 +94,30 @@ class ProjectDetector {
94
94
  };
95
95
  }
96
96
  detectBackgroundEntry(manifest, entryPoints) {
97
- const background = manifest.background;
97
+ const background = manifest["background"];
98
98
  if (!background)
99
99
  return;
100
- const file = background.service_worker || background.scripts?.[0] || background.page;
100
+ const file = background["service_worker"] || background["scripts"]?.[0] || background["page"];
101
101
  if (file) {
102
102
  entryPoints["background"] = this.findSourceFile(file);
103
103
  }
104
104
  }
105
105
  detectContentScriptEntry(manifest, entryPoints) {
106
- const contentScripts = manifest.content_scripts;
106
+ const contentScripts = manifest["content_scripts"];
107
107
  if (!contentScripts || contentScripts.length === 0)
108
108
  return;
109
- const firstScript = contentScripts[0].js?.[0];
109
+ const firstScriptObj = contentScripts[0];
110
+ if (!firstScriptObj)
111
+ return;
112
+ const firstScript = firstScriptObj["js"]?.[0];
110
113
  if (firstScript) {
111
114
  entryPoints["content"] = this.findSourceFile(firstScript);
112
115
  }
113
116
  }
114
117
  detectPopupEntry(manifest, entryPoints) {
115
- const popup = manifest.action?.default_popup || manifest.browser_action?.default_popup;
118
+ const action = manifest["action"];
119
+ const browserAction = manifest["browser_action"];
120
+ const popup = action?.["default_popup"] || browserAction?.["default_popup"];
116
121
  if (!popup)
117
122
  return;
118
123
  const jsFile = this.findAssociatedJS(path4.join(this.projectRoot, popup));
@@ -121,7 +126,8 @@ class ProjectDetector {
121
126
  }
122
127
  }
123
128
  detectOptionsEntry(manifest, entryPoints) {
124
- const options = manifest.options_ui?.page || manifest.options_page;
129
+ const optionsUi = manifest["options_ui"];
130
+ const options = optionsUi?.["page"] || manifest["options_page"];
125
131
  if (!options)
126
132
  return;
127
133
  const jsFile = this.findAssociatedJS(path4.join(this.projectRoot, options));
@@ -176,7 +182,7 @@ class ProjectDetector {
176
182
  detectElectron(packageJson) {
177
183
  const entryPoints = {};
178
184
  const mainCandidates = [
179
- packageJson.main,
185
+ packageJson["main"],
180
186
  "src/main/index.ts",
181
187
  "src/electron/main.ts",
182
188
  "electron/main.ts",
@@ -210,9 +216,9 @@ class ProjectDetector {
210
216
  renderer: "Renderer Process"
211
217
  },
212
218
  metadata: {
213
- name: packageJson.name,
214
- version: packageJson.version,
215
- description: packageJson.description
219
+ name: packageJson["name"],
220
+ version: packageJson["version"],
221
+ description: packageJson["description"]
216
222
  }
217
223
  };
218
224
  }
@@ -271,9 +277,9 @@ class ProjectDetector {
271
277
  client: "Client"
272
278
  },
273
279
  metadata: {
274
- name: packageJson.name,
275
- version: packageJson.version,
276
- description: packageJson.description
280
+ name: packageJson["name"],
281
+ version: packageJson["version"],
282
+ description: packageJson["description"]
277
283
  }
278
284
  };
279
285
  }
@@ -4645,9 +4651,12 @@ class ContextAnalyzer {
4645
4651
  const leadingComments = firstStatement.getLeadingCommentRanges();
4646
4652
  if (leadingComments.length === 0)
4647
4653
  return;
4648
- const comment = leadingComments[0].getText();
4654
+ const firstComment = leadingComments[0];
4655
+ if (!firstComment)
4656
+ return;
4657
+ const comment = firstComment.getText();
4649
4658
  const descMatch = comment.match(/@description\s+(.+?)(?:\n|$)/s);
4650
- if (descMatch) {
4659
+ if (descMatch?.[1]) {
4651
4660
  return descMatch[1].trim();
4652
4661
  }
4653
4662
  const lines = comment.split(`
@@ -4746,6 +4755,8 @@ class ContextAnalyzer {
4746
4755
  if (params.length === 0)
4747
4756
  return [];
4748
4757
  const propsParam = params[0];
4758
+ if (!propsParam)
4759
+ return [];
4749
4760
  const type = propsParam.getType();
4750
4761
  const props = [];
4751
4762
  for (const prop of type.getProperties()) {
@@ -4761,6 +4772,8 @@ class ContextAnalyzer {
4761
4772
  if (typeArgs.length === 0)
4762
4773
  return [];
4763
4774
  const propsType = typeArgs[0];
4775
+ if (!propsType)
4776
+ return [];
4764
4777
  const props = [];
4765
4778
  for (const prop of propsType.getProperties()) {
4766
4779
  props.push(prop.getName());
@@ -4768,10 +4781,15 @@ class ContextAnalyzer {
4768
4781
  return props;
4769
4782
  }
4770
4783
  extractJSDocDescription(node) {
4784
+ if (!Node.isJSDocable(node))
4785
+ return;
4771
4786
  const jsDocs = node.getJsDocs();
4772
4787
  if (jsDocs.length === 0)
4773
4788
  return;
4774
- const description = jsDocs[0].getDescription().trim();
4789
+ const firstDoc = jsDocs[0];
4790
+ if (!firstDoc)
4791
+ return;
4792
+ const description = firstDoc.getDescription().trim();
4775
4793
  return description || undefined;
4776
4794
  }
4777
4795
  isUIContext(contextType) {
@@ -4853,7 +4871,10 @@ class FlowAnalyzer {
4853
4871
  if (args.length === 0) {
4854
4872
  return;
4855
4873
  }
4856
- const msgType = this.extractMessageTypeFromArg(args[0]);
4874
+ const firstArg = args[0];
4875
+ if (!firstArg)
4876
+ return;
4877
+ const msgType = this.extractMessageTypeFromArg(firstArg);
4857
4878
  if (msgType === messageType) {
4858
4879
  senders.push({
4859
4880
  context,
@@ -4873,7 +4894,10 @@ class FlowAnalyzer {
4873
4894
  if (args.length === 0) {
4874
4895
  return;
4875
4896
  }
4876
- const msgType = this.extractMessageTypeFromArg(args[0]);
4897
+ const firstArg = args[0];
4898
+ if (!firstArg)
4899
+ return;
4900
+ const msgType = this.extractMessageTypeFromArg(firstArg);
4877
4901
  if (msgType === messageType) {
4878
4902
  senders.push({
4879
4903
  context,
@@ -4943,7 +4967,10 @@ class FlowAnalyzer {
4943
4967
  if (args.length === 0) {
4944
4968
  return;
4945
4969
  }
4946
- const messageType = this.extractMessageTypeFromArg(args[0]);
4970
+ const firstArg = args[0];
4971
+ if (!firstArg)
4972
+ return;
4973
+ const messageType = this.extractMessageTypeFromArg(firstArg);
4947
4974
  if (messageType) {
4948
4975
  sends.push({
4949
4976
  messageType,
@@ -4996,10 +5023,17 @@ class FlowAnalyzer {
4996
5023
  });
4997
5024
  if (!targetNode)
4998
5025
  return {};
4999
- const jsDocs = targetNode.getJsDocs?.() || [];
5026
+ const nodeAny = targetNode;
5027
+ if (!("getJsDocs" in nodeAny) || typeof nodeAny.getJsDocs !== "function") {
5028
+ return {};
5029
+ }
5030
+ const jsDocs = nodeAny.getJsDocs();
5000
5031
  if (jsDocs.length === 0)
5001
5032
  return {};
5002
- const comment = jsDocs[0].getText();
5033
+ const firstDoc = jsDocs[0];
5034
+ if (!firstDoc)
5035
+ return {};
5036
+ const comment = firstDoc.getText();
5003
5037
  const flowMatch = comment.match(/@flow\s+([^\s]+)/);
5004
5038
  const flowName = flowMatch ? flowMatch[1] : undefined;
5005
5039
  const triggerMatch = comment.match(/@trigger\s+(.+?)(?:\n|$)/);
@@ -5435,40 +5469,116 @@ class HandlerExtractor {
5435
5469
  project;
5436
5470
  typeGuardCache;
5437
5471
  relationshipExtractor;
5472
+ analyzedFiles;
5473
+ packageRoot;
5438
5474
  constructor(tsConfigPath) {
5439
5475
  this.project = new Project3({
5440
5476
  tsConfigFilePath: tsConfigPath
5441
5477
  });
5442
5478
  this.typeGuardCache = new WeakMap;
5443
5479
  this.relationshipExtractor = new RelationshipExtractor;
5480
+ this.analyzedFiles = new Set;
5481
+ this.packageRoot = this.findPackageRoot(tsConfigPath);
5482
+ }
5483
+ findPackageRoot(tsConfigPath) {
5484
+ let dir = tsConfigPath.substring(0, tsConfigPath.lastIndexOf("/"));
5485
+ while (dir.length > 1) {
5486
+ try {
5487
+ const packageJsonPath = `${dir}/package.json`;
5488
+ const file = Bun.file(packageJsonPath);
5489
+ if (file.size > 0) {
5490
+ return dir;
5491
+ }
5492
+ } catch {}
5493
+ const parentDir = dir.substring(0, dir.lastIndexOf("/"));
5494
+ if (parentDir === dir)
5495
+ break;
5496
+ dir = parentDir;
5497
+ }
5498
+ return tsConfigPath.substring(0, tsConfigPath.lastIndexOf("/"));
5444
5499
  }
5445
5500
  extractHandlers() {
5446
5501
  const handlers = [];
5447
5502
  const messageTypes = new Set;
5448
5503
  const invalidMessageTypes = new Set;
5449
5504
  const stateConstraints = [];
5450
- const sourceFiles = this.project.getSourceFiles();
5451
- this.debugLogSourceFiles(sourceFiles);
5452
- for (const sourceFile of sourceFiles) {
5453
- const fileHandlers = this.extractFromFile(sourceFile);
5454
- handlers.push(...fileHandlers);
5455
- this.categorizeHandlerMessageTypes(fileHandlers, messageTypes, invalidMessageTypes);
5456
- const fileConstraints = this.extractStateConstraintsFromFile(sourceFile);
5457
- stateConstraints.push(...fileConstraints);
5505
+ const allSourceFiles = this.project.getSourceFiles();
5506
+ const entryPoints = allSourceFiles.filter((f) => this.isWithinPackage(f.getFilePath()));
5507
+ this.debugLogSourceFiles(allSourceFiles, entryPoints);
5508
+ for (const entryPoint of entryPoints) {
5509
+ this.analyzeFileAndImports(entryPoint, handlers, messageTypes, invalidMessageTypes, stateConstraints);
5458
5510
  }
5459
5511
  this.debugLogExtractionResults(handlers.length, invalidMessageTypes.size);
5512
+ this.debugLogAnalysisStats(allSourceFiles.length, entryPoints.length);
5460
5513
  return {
5461
5514
  handlers,
5462
5515
  messageTypes,
5463
5516
  stateConstraints
5464
5517
  };
5465
5518
  }
5466
- debugLogSourceFiles(sourceFiles) {
5519
+ analyzeFileAndImports(sourceFile, handlers, messageTypes, invalidMessageTypes, stateConstraints) {
5520
+ const filePath = sourceFile.getFilePath();
5521
+ if (this.analyzedFiles.has(filePath)) {
5522
+ return;
5523
+ }
5524
+ this.analyzedFiles.add(filePath);
5525
+ if (process.env["POLLY_DEBUG"]) {
5526
+ console.log(`[DEBUG] Analyzing: ${filePath}`);
5527
+ }
5528
+ const fileHandlers = this.extractFromFile(sourceFile);
5529
+ handlers.push(...fileHandlers);
5530
+ this.categorizeHandlerMessageTypes(fileHandlers, messageTypes, invalidMessageTypes);
5531
+ const fileConstraints = this.extractStateConstraintsFromFile(sourceFile);
5532
+ stateConstraints.push(...fileConstraints);
5533
+ const importDeclarations = sourceFile.getImportDeclarations();
5534
+ for (const importDecl of importDeclarations) {
5535
+ const importedFile = importDecl.getModuleSpecifierSourceFile();
5536
+ if (importedFile) {
5537
+ const importedPath = importedFile.getFilePath();
5538
+ if (!this.isWithinPackage(importedPath)) {
5539
+ if (process.env["POLLY_DEBUG"]) {
5540
+ console.log(`[DEBUG] Skipping external import: ${importedPath}`);
5541
+ }
5542
+ continue;
5543
+ }
5544
+ this.analyzeFileAndImports(importedFile, handlers, messageTypes, invalidMessageTypes, stateConstraints);
5545
+ } else if (process.env["POLLY_DEBUG"]) {
5546
+ const specifier = importDecl.getModuleSpecifierValue();
5547
+ if (!specifier.startsWith("node:") && !this.isNodeModuleImport(specifier)) {
5548
+ console.log(`[DEBUG] Could not resolve import: ${specifier} in ${filePath}`);
5549
+ }
5550
+ }
5551
+ }
5552
+ }
5553
+ isWithinPackage(filePath) {
5554
+ if (!filePath.startsWith(this.packageRoot)) {
5555
+ return false;
5556
+ }
5557
+ if (filePath.includes("/node_modules/")) {
5558
+ return false;
5559
+ }
5560
+ return true;
5561
+ }
5562
+ isNodeModuleImport(specifier) {
5563
+ return !specifier.startsWith(".") && !specifier.startsWith("/");
5564
+ }
5565
+ debugLogAnalysisStats(totalSourceFiles, entryPointCount) {
5566
+ if (!process.env["POLLY_DEBUG"])
5567
+ return;
5568
+ console.log(`[DEBUG] Analysis Statistics:`);
5569
+ console.log(`[DEBUG] Package root: ${this.packageRoot}`);
5570
+ console.log(`[DEBUG] Source files from tsconfig: ${totalSourceFiles}`);
5571
+ console.log(`[DEBUG] Entry points (in package): ${entryPointCount}`);
5572
+ console.log(`[DEBUG] Files analyzed (including imports): ${this.analyzedFiles.size}`);
5573
+ console.log(`[DEBUG] Additional files discovered: ${this.analyzedFiles.size - entryPointCount}`);
5574
+ }
5575
+ debugLogSourceFiles(allSourceFiles, entryPoints) {
5467
5576
  if (!process.env["POLLY_DEBUG"])
5468
5577
  return;
5469
- console.log(`[DEBUG] Loaded ${sourceFiles.length} source files`);
5470
- if (sourceFiles.length <= 20) {
5471
- for (const sf of sourceFiles) {
5578
+ console.log(`[DEBUG] Loaded ${allSourceFiles.length} source files from tsconfig`);
5579
+ console.log(`[DEBUG] Filtered to ${entryPoints.length} entry points within package`);
5580
+ if (entryPoints.length <= 20) {
5581
+ for (const sf of entryPoints) {
5472
5582
  console.log(`[DEBUG] - ${sf.getFilePath()}`);
5473
5583
  }
5474
5584
  }
@@ -5726,7 +5836,10 @@ class HandlerExtractor {
5726
5836
  if (!body) {
5727
5837
  return;
5728
5838
  }
5729
- const firstAwaitPos = awaitExpressions[0].getStart();
5839
+ const firstAwait = awaitExpressions[0];
5840
+ if (!firstAwait)
5841
+ return;
5842
+ const firstAwaitPos = firstAwait.getStart();
5730
5843
  funcNode.forEachDescendant((node) => {
5731
5844
  if (Node4.isBinaryExpression(node)) {
5732
5845
  this.checkBinaryExpressionMutation(node, firstAwaitPos, mutations);
@@ -5777,6 +5890,8 @@ class HandlerExtractor {
5777
5890
  return null;
5778
5891
  }
5779
5892
  const conditionArg = args[0];
5893
+ if (!conditionArg)
5894
+ return null;
5780
5895
  const expression = conditionArg.getText();
5781
5896
  let message;
5782
5897
  if (args.length >= 2 && Node4.isStringLiteral(args[1])) {
@@ -6082,12 +6197,17 @@ class HandlerExtractor {
6082
6197
  return;
6083
6198
  }
6084
6199
  extractMessageTypeFromTypePredicateFunction(node, returnTypeNode) {
6085
- const typeNode = returnTypeNode.getTypeNode();
6086
- if (typeNode) {
6087
- const typeName = typeNode.getText();
6088
- const messageType = this.extractMessageTypeFromTypeName(typeName);
6089
- if (messageType) {
6090
- return messageType;
6200
+ if (!Node4.isTypePredicate(returnTypeNode)) {
6201
+ return null;
6202
+ }
6203
+ if ("getTypeNode" in returnTypeNode && typeof returnTypeNode.getTypeNode === "function") {
6204
+ const typeNode = returnTypeNode.getTypeNode();
6205
+ if (typeNode) {
6206
+ const typeName = typeNode.getText();
6207
+ const messageType = this.extractMessageTypeFromTypeName(typeName);
6208
+ if (messageType) {
6209
+ return messageType;
6210
+ }
6091
6211
  }
6092
6212
  }
6093
6213
  return this.extractMessageTypeFromFunctionBodyText(node);
@@ -6419,7 +6539,10 @@ class IntegrationAnalyzer {
6419
6539
  if (args.length === 0) {
6420
6540
  return;
6421
6541
  }
6422
- const url = this.extractURLFromArg(args[0]);
6542
+ const firstArg = args[0];
6543
+ if (!firstArg)
6544
+ return;
6545
+ const url = this.extractURLFromArg(firstArg);
6423
6546
  if (!url) {
6424
6547
  return;
6425
6548
  }
@@ -6600,10 +6723,15 @@ class IntegrationAnalyzer {
6600
6723
  }
6601
6724
  }
6602
6725
  extractJSDocDescription(node) {
6603
- const jsDocs = node.getJsDocs?.() || [];
6726
+ if (!Node5.isJSDocable(node))
6727
+ return;
6728
+ const jsDocs = node.getJsDocs();
6604
6729
  if (jsDocs.length === 0)
6605
6730
  return;
6606
- const comment = jsDocs[0].getDescription().trim();
6731
+ const firstDoc = jsDocs[0];
6732
+ if (!firstDoc)
6733
+ return;
6734
+ const comment = firstDoc.getDescription().trim();
6607
6735
  return comment || undefined;
6608
6736
  }
6609
6737
  }
@@ -6642,17 +6770,17 @@ class ManifestParser {
6642
6770
  }
6643
6771
  const manifest = this.manifestData;
6644
6772
  return {
6645
- name: manifest.name || "Unknown Extension",
6646
- version: manifest.version || "0.0.0",
6647
- description: manifest.description,
6648
- manifestVersion: manifest.manifest_version || 2,
6773
+ name: manifest["name"] || "Unknown Extension",
6774
+ version: manifest["version"] || "0.0.0",
6775
+ description: manifest["description"],
6776
+ manifestVersion: manifest["manifest_version"] || 2,
6649
6777
  background: this.parseBackground(),
6650
6778
  contentScripts: this.parseContentScripts(),
6651
6779
  popup: this.parsePopup(),
6652
6780
  options: this.parseOptions(),
6653
6781
  devtools: this.parseDevtools(),
6654
- permissions: manifest.permissions || [],
6655
- hostPermissions: manifest.host_permissions || []
6782
+ permissions: manifest["permissions"] || [],
6783
+ hostPermissions: manifest["host_permissions"] || []
6656
6784
  };
6657
6785
  }
6658
6786
  getContextEntryPoints() {
@@ -6718,25 +6846,25 @@ class ManifestParser {
6718
6846
  parseBackground() {
6719
6847
  if (!this.manifestData)
6720
6848
  return;
6721
- const bg = this.manifestData.background;
6849
+ const bg = this.manifestData["background"];
6722
6850
  if (!bg)
6723
6851
  return;
6724
- if (bg.service_worker) {
6852
+ if (bg["service_worker"]) {
6725
6853
  return {
6726
6854
  type: "service_worker",
6727
- files: [bg.service_worker]
6855
+ files: [bg["service_worker"]]
6728
6856
  };
6729
6857
  }
6730
- if (bg.scripts) {
6858
+ if (bg["scripts"]) {
6731
6859
  return {
6732
6860
  type: "script",
6733
- files: bg.scripts
6861
+ files: bg["scripts"]
6734
6862
  };
6735
6863
  }
6736
- if (bg.page) {
6864
+ if (bg["page"]) {
6737
6865
  return {
6738
6866
  type: "script",
6739
- files: [bg.page]
6867
+ files: [bg["page"]]
6740
6868
  };
6741
6869
  }
6742
6870
  return;
@@ -6744,24 +6872,24 @@ class ManifestParser {
6744
6872
  parseContentScripts() {
6745
6873
  if (!this.manifestData)
6746
6874
  return;
6747
- const cs = this.manifestData.content_scripts;
6875
+ const cs = this.manifestData["content_scripts"];
6748
6876
  if (!cs || !Array.isArray(cs))
6749
6877
  return;
6750
6878
  return cs.map((script) => ({
6751
- matches: script.matches || [],
6752
- js: script.js || [],
6753
- css: script.css
6879
+ matches: script["matches"] || [],
6880
+ js: script["js"] || [],
6881
+ css: script["css"]
6754
6882
  }));
6755
6883
  }
6756
6884
  parsePopup() {
6757
6885
  if (!this.manifestData)
6758
6886
  return;
6759
- const action = this.manifestData.action || this.manifestData.browser_action;
6887
+ const action = this.manifestData["action"] || this.manifestData["browser_action"];
6760
6888
  if (!action)
6761
6889
  return;
6762
- if (action.default_popup) {
6890
+ if (action["default_popup"]) {
6763
6891
  return {
6764
- html: action.default_popup,
6892
+ html: action["default_popup"],
6765
6893
  default: true
6766
6894
  };
6767
6895
  }
@@ -6770,7 +6898,7 @@ class ManifestParser {
6770
6898
  parseOptions() {
6771
6899
  if (!this.manifestData)
6772
6900
  return;
6773
- const options = this.manifestData.options_ui || this.manifestData.options_page;
6901
+ const options = this.manifestData["options_ui"] || this.manifestData["options_page"];
6774
6902
  if (!options)
6775
6903
  return;
6776
6904
  if (typeof options === "string") {
@@ -6779,15 +6907,16 @@ class ManifestParser {
6779
6907
  openInTab: false
6780
6908
  };
6781
6909
  }
6910
+ const optionsObj = options;
6782
6911
  return {
6783
- page: options.page,
6784
- openInTab: options.open_in_tab
6912
+ page: optionsObj["page"],
6913
+ openInTab: optionsObj["open_in_tab"]
6785
6914
  };
6786
6915
  }
6787
6916
  parseDevtools() {
6788
6917
  if (!this.manifestData)
6789
6918
  return;
6790
- const devtools = this.manifestData.devtools_page;
6919
+ const devtools = this.manifestData["devtools_page"];
6791
6920
  if (!devtools)
6792
6921
  return;
6793
6922
  return {
@@ -6984,11 +7113,20 @@ class TypeExtractor {
6984
7113
  const handlerAnalysis = this.extractHandlerAnalysis();
6985
7114
  const validMessageTypes = this.filterAndLogMessageTypes(messageTypes, handlerAnalysis.messageTypes);
6986
7115
  const validHandlers = this.filterAndLogHandlers(handlerAnalysis.handlers);
7116
+ const completeHandlers = validHandlers.map((h) => ({
7117
+ messageType: h.messageType,
7118
+ node: h.node || "unknown",
7119
+ assignments: h.assignments || [],
7120
+ preconditions: h.preconditions || [],
7121
+ postconditions: h.postconditions || [],
7122
+ location: h.location,
7123
+ relationships: h.relationships
7124
+ }));
6987
7125
  return {
6988
7126
  stateType,
6989
7127
  messageTypes: validMessageTypes,
6990
7128
  fields,
6991
- handlers: validHandlers,
7129
+ handlers: completeHandlers,
6992
7130
  stateConstraints: handlerAnalysis.stateConstraints
6993
7131
  };
6994
7132
  }
@@ -7090,7 +7228,7 @@ class TypeExtractor {
7090
7228
  if (type.getAliasSymbol()) {
7091
7229
  return this.extractFromTypeAlias(type, typeName, sourceFile, warnings);
7092
7230
  }
7093
- if (type.isConditionalType?.()) {
7231
+ if (typeof type.isConditionalType === "function" && type.isConditionalType()) {
7094
7232
  return this.extractFromConditionalType(type, warnings);
7095
7233
  }
7096
7234
  if (type.getText().includes("[K in ")) {
@@ -7155,7 +7293,10 @@ class TypeExtractor {
7155
7293
  if (parts.length < 2) {
7156
7294
  return messageTypes;
7157
7295
  }
7158
- const branches = parts[1].split(":");
7296
+ const secondPart = parts[1];
7297
+ if (!secondPart)
7298
+ return messageTypes;
7299
+ const branches = secondPart.split(":");
7159
7300
  for (const branch of branches) {
7160
7301
  const extracted = this.extractStringLiteralFromBranch(branch);
7161
7302
  if (extracted) {
@@ -7617,9 +7758,12 @@ class StructurizrDSLGenerator {
7617
7758
  }
7618
7759
  for (const [messageType, handlers2] of handlersByType) {
7619
7760
  const componentName = this.toComponentName(messageType);
7620
- const description = this.generateComponentDescription(messageType, handlers2[0]);
7621
- const tags = this.getComponentTags(messageType, handlers2[0]);
7622
- const properties = this.getComponentProperties(messageType, handlers2[0], contextType);
7761
+ const firstHandler = handlers2[0];
7762
+ if (!firstHandler)
7763
+ continue;
7764
+ const description = this.generateComponentDescription(messageType, firstHandler);
7765
+ const tags = this.getComponentTags(messageType, firstHandler);
7766
+ const properties = this.getComponentProperties(messageType, firstHandler, contextType);
7623
7767
  componentDefs.push({
7624
7768
  id: this.toId(componentName),
7625
7769
  name: componentName,
@@ -8150,7 +8294,8 @@ class StructurizrDSLGenerator {
8150
8294
  let stepCount = 0;
8151
8295
  for (const { handler, contextName: _contextName } of handlers2) {
8152
8296
  const handlerComponentId = this.toId(`${handler.messageType}_handler`);
8153
- for (const rel of handler.relationships) {
8297
+ const relationships = handler.relationships || [];
8298
+ for (const rel of relationships) {
8154
8299
  const toComponent = this.toId(rel.to);
8155
8300
  parts.push(` ${handlerComponentId} -> ${toComponent} "${rel.description}"`);
8156
8301
  stepCount++;
@@ -8231,7 +8376,7 @@ class StructurizrDSLGenerator {
8231
8376
  state: "Application state synchronization",
8232
8377
  general: "Message flow through the system"
8233
8378
  };
8234
- return descriptions[domain] || descriptions.general;
8379
+ return descriptions[domain] || descriptions["general"] || "Message flow through the system";
8235
8380
  }
8236
8381
  getUserAction(domain) {
8237
8382
  const actions = {
@@ -8240,7 +8385,7 @@ class StructurizrDSLGenerator {
8240
8385
  state: "Requests state",
8241
8386
  general: "Interacts"
8242
8387
  };
8243
- return actions[domain] || actions.general;
8388
+ return actions[domain] || actions["general"] || "Interacts";
8244
8389
  }
8245
8390
  getMessageDescription(messageType) {
8246
8391
  const type = messageType.toLowerCase();
@@ -8479,11 +8624,13 @@ class StructurizrDSLGenerator {
8479
8624
  }
8480
8625
  if (this.options.perspectives?.[comp.id]) {
8481
8626
  const perspectives = this.options.perspectives[comp.id];
8482
- parts.push(`${indent} perspectives {`);
8483
- for (const perspective of perspectives) {
8484
- parts.push(`${indent} "${this.escape(perspective.name)}" "${this.escape(perspective.description)}"`);
8627
+ if (perspectives) {
8628
+ parts.push(`${indent} perspectives {`);
8629
+ for (const perspective of perspectives) {
8630
+ parts.push(`${indent} "${this.escape(perspective.name)}" "${this.escape(perspective.description)}"`);
8631
+ }
8632
+ parts.push(`${indent} }`);
8485
8633
  }
8486
- parts.push(`${indent} }`);
8487
8634
  }
8488
8635
  parts.push(`${indent}}`);
8489
8636
  return parts.join(`
@@ -8810,6 +8957,7 @@ ${generateVerificationSection(context)}
8810
8957
  - **Performance**: How to optimize verification speed and state space exploration
8811
8958
  - **Debugging**: Interpreting counterexamples and fixing violations
8812
8959
  - **Configuration**: Understanding maxInFlight, bounds, and other verification parameters
8960
+ - **Elysia Integration**: Using Polly with Elysia/Bun servers for full-stack distributed systems verification
8813
8961
 
8814
8962
  # Important Notes
8815
8963
 
@@ -8822,6 +8970,149 @@ ${generateVerificationSection(context)}
8822
8970
  - Example: \`$constraints("loggedIn", { USER_LOGOUT: { requires: "state.loggedIn === true" } })\`
8823
8971
  - This eliminates duplication and creates a single source of truth for state invariants
8824
8972
  - Parser extracts constraints and adds them to all relevant message handlers automatically
8973
+ - **File Organization**: Constraints can be organized in separate files (e.g., specs/constraints.ts)
8974
+ - **Transitive Discovery**: The analyzer uses transitive import following to discover constraints
8975
+ - Files outside src/ are automatically found if imported from handler files
8976
+ - This enables clean separation of verification code from runtime code
8977
+
8978
+ # Elysia/Bun Integration
8979
+
8980
+ Polly now provides first-class support for Elysia (Bun-first web framework) with Eden type-safe client generation:
8981
+
8982
+ ## Server-Side Middleware (\`@fairfox/polly/elysia\`)
8983
+
8984
+ The \`polly()\` middleware adds distributed systems semantics to Elysia apps:
8985
+
8986
+ **Key Features:**
8987
+ - **State Management**: Define client and server state signals
8988
+ - **Client Effects**: Specify what should happen on the client after server operations
8989
+ - **Authorization**: Route-level authorization rules
8990
+ - **Offline Behavior**: Configure optimistic updates and queueing
8991
+ - **WebSocket Broadcast**: Real-time updates to connected clients
8992
+ - **TLA+ Generation**: Automatic formal specification generation from routes + config
8993
+
8994
+ **Example:**
8995
+ \`\`\`typescript
8996
+ import { Elysia, t } from 'elysia';
8997
+ import { polly } from '@fairfox/polly/elysia';
8998
+ import { $syncedState, $serverState } from '@fairfox/polly';
8999
+
9000
+ const app = new Elysia()
9001
+ .use(polly({
9002
+ state: {
9003
+ client: {
9004
+ todos: $syncedState('todos', []),
9005
+ user: $syncedState('user', null),
9006
+ },
9007
+ server: {
9008
+ db: $serverState('db', db),
9009
+ },
9010
+ },
9011
+ effects: {
9012
+ 'POST /todos': {
9013
+ client: ({ result, state }) => {
9014
+ state.client.todos.value = [...state.client.todos.value, result];
9015
+ },
9016
+ broadcast: true, // Send to all connected clients
9017
+ },
9018
+ },
9019
+ authorization: {
9020
+ 'POST /todos': ({ state }) => state.client.user.value !== null,
9021
+ },
9022
+ offline: {
9023
+ 'POST /todos': {
9024
+ queue: true,
9025
+ optimistic: (body) => ({ id: -Date.now(), ...body }),
9026
+ },
9027
+ },
9028
+ }))
9029
+ .post('/todos', handler, { body: t.Object({ text: t.String() }) });
9030
+ \`\`\`
9031
+
9032
+ **Route Pattern Matching:**
9033
+ - Exact: \`'POST /todos'\`
9034
+ - Params: \`'GET /todos/:id'\`
9035
+ - Wildcard: \`'/todos/*'\`
9036
+
9037
+ **Production Behavior:**
9038
+ - In development: Adds metadata to responses for hot-reload and debugging
9039
+ - In production: Pass-through (minimal overhead) - client effects are bundled at build time
9040
+ - Authorization and broadcasts work in both modes
9041
+
9042
+ ## Client-Side Wrapper (\`@fairfox/polly/client\`)
9043
+
9044
+ Enhances Eden treaty client with Polly features:
9045
+
9046
+ **Example:**
9047
+ \`\`\`typescript
9048
+ import { createPollyClient } from '@fairfox/polly/client';
9049
+ import { $syncedState } from '@fairfox/polly';
9050
+ import type { app } from './server';
9051
+
9052
+ const clientState = {
9053
+ todos: $syncedState('todos', []),
9054
+ user: $syncedState('user', null),
9055
+ };
9056
+
9057
+ export const api = createPollyClient<typeof app>('http://localhost:3000', {
9058
+ state: clientState,
9059
+ websocket: true, // Enable real-time updates
9060
+ });
9061
+
9062
+ // Use it (types are automatically inferred from server!)
9063
+ await api.todos.post({ text: 'Buy milk' });
9064
+
9065
+ // Access Polly features
9066
+ console.log(api.$polly.state.isOnline.value); // true/false
9067
+ console.log(api.$polly.state.queuedRequests.value); // Queued requests
9068
+ api.$polly.sync(); // Manually sync queued requests
9069
+ \`\`\`
9070
+
9071
+ **Key Features:**
9072
+ - Offline queueing with automatic retry
9073
+ - WebSocket connection for real-time updates
9074
+ - Lamport clock synchronization
9075
+ - Type inference from server via Eden
9076
+
9077
+ ## Why This Matters for Distributed Systems
9078
+
9079
+ SPAs/PWAs are distributed systems facing classic problems:
9080
+ - **CAP theorem**: Must choose consistency vs availability during partitions
9081
+ - **Network unreliability**: The first fallacy of distributed computing
9082
+ - **Cache invalidation**: "One of the two hard things in computer science"
9083
+ - **Eventual consistency**: State sync across client/server
9084
+ - **Conflict resolution**: When multiple devices edit offline
9085
+
9086
+ The Elysia integration addresses this by:
9087
+ 1. Making distributed concerns explicit (offline, authorization, effects)
9088
+ 2. Leveraging Eden for zero-duplication type safety
9089
+ 3. Supporting verification via TLA+ generation from middleware config
9090
+ 4. Providing WebSocket broadcast for real-time consistency
9091
+
9092
+ ## Architecture Pattern
9093
+
9094
+ \`\`\`
9095
+ Browser (Client)
9096
+ ├── Eden Treaty Client (types from Elysia)
9097
+ ├── Polly Wrapper (offline, sync, WebSocket)
9098
+ └── Client State ($syncedState)
9099
+
9100
+ │ HTTP / WebSocket
9101
+
9102
+ Server (Elysia + Bun)
9103
+ ├── Elysia Routes (normal routes)
9104
+ ├── Polly Middleware (effects, auth, broadcast)
9105
+ └── Server State ($serverState)
9106
+ \`\`\`
9107
+
9108
+ ## Best Practices
9109
+
9110
+ 1. **Separate Elysia is the contract** - Don't define types twice. Elysia routes define the API, Eden generates client types.
9111
+ 2. **Effects describe client behavior** - Keep effects pure and deterministic.
9112
+ 3. **Authorization at route level** - Centralize security rules in middleware config.
9113
+ 4. **Queue selectively** - Only queue idempotent operations when offline.
9114
+ 5. **Broadcast with filters** - Use broadcastFilter to target specific clients.
9115
+ 6. **Generate TLA+ for verification** - Enable tlaGeneration in dev to verify distributed properties.
8825
9116
 
8826
9117
  Begin by understanding their question and providing a clear, precise answer based on their project context.`;
8827
9118
  }
@@ -8968,6 +9259,10 @@ to reduce verification time while maintaining or improving verification precisio
8968
9259
  and available in the current version of Polly. You can recommend any of these optimizations with
8969
9260
  confidence that they will work when users apply them to their configuration.
8970
9261
 
9262
+ **Code Organization**: The analyzer uses transitive import following to discover all reachable code.
9263
+ Constraints and type guards can be organized in separate files (e.g., specs/constraints.ts) and will
9264
+ be automatically discovered via imports. Files outside src/ are fully supported.
9265
+
8971
9266
  # Communication Style
8972
9267
 
8973
9268
  - Direct and precise - no fluff
@@ -9284,4 +9579,4 @@ Goodbye!`);
9284
9579
  }
9285
9580
  main();
9286
9581
 
9287
- //# debugId=8BD8EAFF3D45D43B64756E2164756E21
9582
+ //# debugId=770FFA3847E6590364756E2164756E21