@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.
- package/README.md +186 -0
- package/dist/src/client/index.d.ts +33 -0
- package/dist/src/client/index.js +586 -0
- package/dist/src/client/index.js.map +13 -0
- package/dist/src/client/wrapper.d.ts +54 -0
- package/dist/src/core/clock.d.ts +63 -0
- package/dist/src/elysia/index.d.ts +43 -0
- package/dist/src/elysia/index.js +241 -0
- package/dist/src/elysia/index.js.map +12 -0
- package/dist/src/elysia/plugin.d.ts +5 -0
- package/dist/src/elysia/tla-generator.d.ts +16 -0
- package/dist/src/elysia/types.d.ts +137 -0
- package/dist/src/utils/function-serialization.d.ts +14 -0
- package/dist/tools/analysis/src/extract/adr.d.ts +37 -0
- package/dist/tools/analysis/src/extract/architecture.d.ts +42 -0
- package/dist/tools/analysis/src/extract/contexts.d.ts +74 -0
- package/dist/tools/analysis/src/extract/flows.d.ts +68 -0
- package/dist/tools/analysis/src/extract/handlers.d.ts +330 -0
- package/dist/tools/analysis/src/extract/index.d.ts +9 -0
- package/dist/tools/analysis/src/extract/integrations.d.ts +77 -0
- package/dist/tools/analysis/src/extract/manifest.d.ts +64 -0
- package/dist/tools/analysis/src/extract/project-detector.d.ts +103 -0
- package/dist/tools/analysis/src/extract/relationships.d.ts +119 -0
- package/dist/tools/analysis/src/extract/types.d.ts +139 -0
- package/dist/tools/analysis/src/index.d.ts +2 -0
- package/dist/tools/analysis/src/types/adr.d.ts +39 -0
- package/dist/tools/analysis/src/types/architecture.d.ts +198 -0
- package/dist/tools/analysis/src/types/core.d.ts +178 -0
- package/dist/tools/analysis/src/types/index.d.ts +4 -0
- package/dist/tools/teach/src/cli.js +376 -81
- package/dist/tools/teach/src/cli.js.map +13 -13
- package/dist/tools/teach/src/index.d.ts +28 -0
- package/dist/tools/teach/src/index.js +233 -84
- package/dist/tools/teach/src/index.js.map +13 -13
- package/dist/tools/verify/src/cli.js +127 -26
- package/dist/tools/verify/src/cli.js.map +7 -7
- package/dist/tools/visualize/src/cli.js +213 -78
- package/dist/tools/visualize/src/cli.js.map +11 -11
- package/dist/tools/visualize/src/codegen/structurizr.d.ts +343 -0
- package/dist/tools/visualize/src/types/structurizr.d.ts +235 -0
- package/package.json +22 -2
|
@@ -94,25 +94,30 @@ class ProjectDetector {
|
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
detectBackgroundEntry(manifest, entryPoints) {
|
|
97
|
-
const background = manifest
|
|
97
|
+
const background = manifest["background"];
|
|
98
98
|
if (!background)
|
|
99
99
|
return;
|
|
100
|
-
const file = background
|
|
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
|
|
106
|
+
const contentScripts = manifest["content_scripts"];
|
|
107
107
|
if (!contentScripts || contentScripts.length === 0)
|
|
108
108
|
return;
|
|
109
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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
|
|
214
|
-
version: packageJson
|
|
215
|
-
description: packageJson
|
|
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
|
|
275
|
-
version: packageJson
|
|
276
|
-
description: packageJson
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5451
|
-
this.
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
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
|
-
|
|
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 ${
|
|
5470
|
-
|
|
5471
|
-
|
|
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
|
|
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
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
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
|
|
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
|
-
|
|
6726
|
+
if (!Node5.isJSDocable(node))
|
|
6727
|
+
return;
|
|
6728
|
+
const jsDocs = node.getJsDocs();
|
|
6604
6729
|
if (jsDocs.length === 0)
|
|
6605
6730
|
return;
|
|
6606
|
-
const
|
|
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
|
|
6646
|
-
version: manifest
|
|
6647
|
-
description: manifest
|
|
6648
|
-
manifestVersion: manifest
|
|
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
|
|
6655
|
-
hostPermissions: manifest
|
|
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
|
|
6849
|
+
const bg = this.manifestData["background"];
|
|
6722
6850
|
if (!bg)
|
|
6723
6851
|
return;
|
|
6724
|
-
if (bg
|
|
6852
|
+
if (bg["service_worker"]) {
|
|
6725
6853
|
return {
|
|
6726
6854
|
type: "service_worker",
|
|
6727
|
-
files: [bg
|
|
6855
|
+
files: [bg["service_worker"]]
|
|
6728
6856
|
};
|
|
6729
6857
|
}
|
|
6730
|
-
if (bg
|
|
6858
|
+
if (bg["scripts"]) {
|
|
6731
6859
|
return {
|
|
6732
6860
|
type: "script",
|
|
6733
|
-
files: bg
|
|
6861
|
+
files: bg["scripts"]
|
|
6734
6862
|
};
|
|
6735
6863
|
}
|
|
6736
|
-
if (bg
|
|
6864
|
+
if (bg["page"]) {
|
|
6737
6865
|
return {
|
|
6738
6866
|
type: "script",
|
|
6739
|
-
files: [bg
|
|
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
|
|
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
|
|
6752
|
-
js: script
|
|
6753
|
-
css: script
|
|
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
|
|
6887
|
+
const action = this.manifestData["action"] || this.manifestData["browser_action"];
|
|
6760
6888
|
if (!action)
|
|
6761
6889
|
return;
|
|
6762
|
-
if (action
|
|
6890
|
+
if (action["default_popup"]) {
|
|
6763
6891
|
return {
|
|
6764
|
-
html: action
|
|
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
|
|
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:
|
|
6784
|
-
openInTab:
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
7621
|
-
|
|
7622
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
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=
|
|
9582
|
+
//# debugId=770FFA3847E6590364756E2164756E21
|