@uploadista/flow-security-nodes 0.0.20-beta.9 → 0.1.0-beta.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.
@@ -1,9 +1,9 @@
1
1
 
2
2
  
3
- > @uploadista/flow-security-nodes@0.0.20-beta.8 build /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/security/nodes
3
+ > @uploadista/flow-security-nodes@0.1.0-beta.4 build /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/security/nodes
4
4
  > tsc --noEmit && tsdown
5
5
 
6
- ℹ tsdown v0.18.0 powered by rolldown v1.0.0-beta.53
6
+ ℹ tsdown v0.19.0 powered by rolldown v1.0.0-beta.59
7
7
  ℹ config file: /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/security/nodes/tsdown.config.ts
8
8
  ℹ entry: src/index.ts
9
9
  ℹ tsconfig: tsconfig.json
@@ -12,12 +12,12 @@
12
12
  ℹ [CJS] dist/index.cjs 1.23 kB │ gzip: 0.69 kB
13
13
  ℹ [CJS] 1 files, total: 1.23 kB
14
14
  ℹ [ESM] dist/index.mjs 1.26 kB │ gzip: 0.73 kB
15
- ℹ [ESM] dist/index.mjs.map 5.67 kB │ gzip: 2.19 kB
15
+ ℹ [ESM] dist/index.mjs.map 5.64 kB │ gzip: 2.19 kB
16
16
  ℹ [ESM] dist/index.d.mts.map 0.54 kB │ gzip: 0.29 kB
17
17
  ℹ [ESM] dist/index.d.mts 3.29 kB │ gzip: 1.21 kB
18
- ℹ [ESM] 4 files, total: 10.76 kB
19
- ✔ Build complete in 6097ms
18
+ ℹ [ESM] 4 files, total: 10.73 kB
19
+ ✔ Build complete in 6293ms
20
20
  ℹ [CJS] dist/index.d.cts.map 0.54 kB │ gzip: 0.29 kB
21
21
  ℹ [CJS] dist/index.d.cts 3.29 kB │ gzip: 1.20 kB
22
22
  ℹ [CJS] 2 files, total: 3.83 kB
23
- ✔ Build complete in 6110ms
23
+ ✔ Build complete in 6315ms
@@ -0,0 +1,6 @@
1
+
2
+ 
3
+ > @uploadista/flow-security-nodes@0.1.0-beta.2 check /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/security/nodes
4
+ > biome check --write ./src
5
+
6
+ Checked 2 files in 79ms. No fixes applied.
@@ -1,5 +1,126 @@
1
-
2
- > @uploadista/flow-security-nodes@0.0.17 test /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/security/nodes
3
- > vitest
4
-
5
-  ELIFECYCLE  Test failed. See above for more details.
1
+
2
+ 
3
+ > @uploadista/flow-security-nodes@0.1.0-beta.2 test /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/security/nodes
4
+ > vitest run
5
+
6
+ [?25l
7
+  RUN  v4.0.17 /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/security/nodes
8
+
9
+ [?2026h
10
+  Test Files 0 passed (1)
11
+  Tests 0 passed (0)
12
+  Start at 16:11:08
13
+  Duration 959ms
14
+ [?2026l[?2026h
15
+  ❯ tests/scan-virus-node.test.ts [queued]
16
+
17
+  Test Files 0 passed (1)
18
+  Tests 0 passed (0)
19
+  Start at 16:11:08
20
+  Duration 1.16s
21
+ [?2026l[?2026h
22
+  ❯ tests/scan-virus-node.test.ts [queued]
23
+
24
+  Test Files 0 passed (1)
25
+  Tests 0 passed (0)
26
+  Start at 16:11:08
27
+  Duration 1.90s
28
+ [?2026l[?2026h
29
+  ❯ tests/scan-virus-node.test.ts [queued]
30
+
31
+  Test Files 0 passed (1)
32
+  Tests 0 passed (0)
33
+  Start at 16:11:08
34
+  Duration 2.91s
35
+ [?2026l[?2026h
36
+  ❯ tests/scan-virus-node.test.ts [queued]
37
+
38
+  Test Files 0 passed (1)
39
+  Tests 0 passed (0)
40
+  Start at 16:11:08
41
+  Duration 3.97s
42
+ [?2026l[?2026h
43
+  ❯ tests/scan-virus-node.test.ts 0/16
44
+
45
+  Test Files 0 passed (1)
46
+  Tests 0 passed (16)
47
+  Start at 16:11:08
48
+  Duration 4.59s
49
+ [?2026l[?2026h
50
+  ❯ tests/scan-virus-node.test.ts 1/16
51
+
52
+  Test Files 0 passed (1)
53
+  Tests 1 passed (16)
54
+  Start at 16:11:08
55
+  Duration 4.89s
56
+ [?2026l[?2026h
57
+  ❯ tests/scan-virus-node.test.ts 2/16
58
+
59
+  Test Files 0 passed (1)
60
+  Tests 2 passed (16)
61
+  Start at 16:11:08
62
+  Duration 4.99s
63
+ [?2026l[?2026h
64
+  ❯ tests/scan-virus-node.test.ts 4/16
65
+
66
+  Test Files 0 passed (1)
67
+  Tests 4 passed (16)
68
+  Start at 16:11:08
69
+  Duration 5.20s
70
+ [?2026l[?2026h
71
+  ❯ tests/scan-virus-node.test.ts 6/16
72
+
73
+  Test Files 0 passed (1)
74
+  Tests 6 passed (16)
75
+  Start at 16:11:08
76
+  Duration 5.30s
77
+ [?2026l[?2026h
78
+  ❯ tests/scan-virus-node.test.ts 7/16
79
+
80
+  Test Files 0 passed (1)
81
+  Tests 7 passed (16)
82
+  Start at 16:11:08
83
+  Duration 5.50s
84
+ [?2026l[?2026h
85
+  ❯ tests/scan-virus-node.test.ts 9/16
86
+
87
+  Test Files 0 passed (1)
88
+  Tests 9 passed (16)
89
+  Start at 16:11:08
90
+  Duration 5.60s
91
+ [?2026l[?2026h ✓ tests/scan-virus-node.test.ts (16 tests) 1141ms
92
+ ✓ Scan Virus Node (16)
93
+ ✓ Node Creation (2)
94
+ ✓ should create scan virus node with correct properties 226ms
95
+ ✓ should create node with default parameters 46ms
96
+ ✓ Clean File Scanning (3)
97
+ ✓ should pass clean file through unchanged 57ms
98
+ ✓ should add virus scan metadata to clean file 45ms
99
+ ✓ should preserve existing file metadata 116ms
100
+ ✓ Infected File Scanning - Fail Action (3)
101
+ ✓ should fail flow when virus detected with fail action 147ms
102
+ ✓ should include virus names in error message 48ms
103
+ ✓ should include scan metadata in error details 111ms
104
+ ✓ Infected File Scanning - Pass Action (3)
105
+ ✓ should continue flow when virus detected with pass action 11ms
106
+ ✓ should add virus detection metadata when passing infected file 82ms
107
+ ✓ should preserve file bytes when passing infected file 33ms
108
+ ✓ Parameter Validation (4)
109
+ ✓ should accept valid fail action 5ms
110
+ ✓ should accept valid pass action 40ms
111
+ ✓ should apply default timeout 10ms
112
+ ✓ should accept custom timeout within limits 26ms
113
+ ✓ Engine Version (1)
114
+ ✓ should include engine version in scan metadata 14ms
115
+
116
+  Test Files 1 passed (1)
117
+  Tests 16 passed (16)
118
+  Start at 16:11:08
119
+  Duration 5.70s
120
+ [?2026l
121
+  Test Files  1 passed (1)
122
+  Tests  16 passed (16)
123
+  Start at  16:11:08
124
+  Duration  5.78s (transform 755ms, setup 0ms, import 3.43s, tests 1.14s, environment 0ms)
125
+
126
+ [?25h
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cAaa,YAAU,CAAA,CAAA;;;AAAvB,CAAA,CAAA;AACY,KAAA,UAAA,GAAa,CAAA,CAAE,KAAa,CAAA,OAAA,UAAR,CAAA;AAKhC;;;cAAa,iBAAe,CAAA,CAAA;;;;EAAA,CAAA,CAAA,CAAA;EAAA,OAAA,cAAA,cAAA,YAAA,CAAA,CAAA;AAgB5B,CAAA,eAAY,CAAA;AA8BI,KA9BJ,eAAA,GAAkB,CAAA,CAAE,KA8BG,CAAA,OA9BU,eA8BV,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAnB,mBAAA,sBAEN;;IAC0B,MAAA,CAAA,OAD0B,sBAAA,CAC1B,YAAA;yBAAA,uBAAA,CAAA,UAAA;;;;;;;aAqE2l1M;;iEAAA,uBAAA,CAAA,UAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cAaa,YAAU,CAAA,CAAA;;;AAAvB,CAAA,CAAA;AACY,KAAA,UAAA,GAAa,CAAA,CAAE,KAAa,CAAA,OAAA,UAAR,CAAA;AAKhC;;;cAAa,iBAAe,CAAA,CAAA;;;;EAAA,CAAA,CAAA,CAAA;EAAA,OAAA,cAAA,cAAA,YAAA,CAAA,CAAA;AAgB5B,CAAA,eAAY,CAAA;AA8BI,KA9BJ,eAAA,GAAkB,CAAA,CAAE,KA8BG,CAAA,OA9BU,eA8BV,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAnB,mBAAA,sBAEN;;IAC0B,MAAA,CAAA,OAD0B,sBAAA,CAC1B,YAAA;yBAAA,uBAAA,CAAA,UAAA;;;;;;;aAqEik5M;;iEAAA,uBAAA,CAAA,UAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cAaa,YAAU,CAAA,CAAA;;;AAAvB,CAAA,CAAA;AACY,KAAA,UAAA,GAAa,CAAA,CAAE,KAAa,CAAA,OAAA,UAAR,CAAA;AAKhC;;;cAAa,iBAAe,CAAA,CAAA;;;;EAAA,CAAA,CAAA,CAAA;EAAA,OAAA,cAAA,cAAA,YAAA,CAAA,CAAA;AAgB5B,CAAA,eAAY,CAAA;AA8BI,KA9BJ,eAAA,GAAkB,CAAA,CAAE,KA8BG,CAAA,OA9BU,eA8BV,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAnB,mBAAA,sBAEN;;IAC0B,MAAA,CAAA,OAD0B,sBAAA,CAC1B,YAAA;yBAAA,uBAAA,CAAA,UAAA;;;;;;;aAqE2l1M;;iEAAA,uBAAA,CAAA,UAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;cAaa,YAAU,CAAA,CAAA;;;AAAvB,CAAA,CAAA;AACY,KAAA,UAAA,GAAa,CAAA,CAAE,KAAa,CAAA,OAAA,UAAR,CAAA;AAKhC;;;cAAa,iBAAe,CAAA,CAAA;;;;EAAA,CAAA,CAAA,CAAA;EAAA,OAAA,cAAA,cAAA,YAAA,CAAA,CAAA;AAgB5B,CAAA,eAAY,CAAA;AA8BI,KA9BJ,eAAA,GAAkB,CAAA,CAAE,KA8BG,CAAA,OA9BU,eA8BV,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAnB,mBAAA,sBAEN;;IAC0B,MAAA,CAAA,OAD0B,sBAAA,CAC1B,YAAA;yBAAA,uBAAA,CAAA,UAAA;;;;;;;aAqEik5M;;iEAAA,uBAAA,CAAA,UAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["scanMetadata: ScanMetadata"],"sources":["../src/scan-virus-node.ts"],"sourcesContent":["import { httpFailure } from \"@uploadista/core/errors\";\nimport {\n createTransformNode,\n type ScanMetadata,\n STORAGE_OUTPUT_TYPE_ID,\n VirusScanPlugin,\n} from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\nimport { z } from \"zod\";\n\n/**\n * Scan action to take when a virus is detected\n */\nexport const ScanAction = z.enum([\"fail\", \"pass\"]);\nexport type ScanAction = z.infer<typeof ScanAction>;\n\n/**\n * Parameters for the Scan Virus node\n */\nexport const ScanVirusParams = z.object({\n /**\n * Action to take when a virus is detected:\n * - \"fail\": Mark flow task as FAILED and stop processing\n * - \"pass\": Continue processing but add virus metadata to file\n */\n action: ScanAction.default(\"fail\"),\n\n /**\n * Maximum time to wait for scan completion (in milliseconds)\n * Default: 60000ms (60 seconds)\n * Max: 300000ms (5 minutes)\n */\n timeout: z.number().min(1000).max(300000).optional().default(60000),\n});\n\nexport type ScanVirusParams = z.infer<typeof ScanVirusParams>;\n\n/**\n * Creates a Scan Virus node for malware detection\n *\n * Scans files for viruses and malware using the configured VirusScanPlugin\n * (typically ClamAV). Supports configurable actions on detection:\n * - \"fail\": Stop flow execution when virus detected\n * - \"pass\": Continue flow with detection metadata\n *\n * All scan results are stored in file.metadata.virusScan for downstream nodes.\n *\n * @param id - Unique node identifier\n * @param params - Configuration parameters for scan behavior\n * @returns Effect that resolves to the configured node\n *\n * @example\n * ```typescript\n * // Fail on virus detection (default)\n * const failNode = yield* createScanVirusNode(\"scan-1\", {\n * action: \"fail\"\n * });\n *\n * // Pass through with metadata\n * const passNode = yield* createScanVirusNode(\"scan-2\", {\n * action: \"pass\",\n * timeout: 120000 // 2 minutes\n * });\n * ```\n */\nexport function createScanVirusNode(\n id: string,\n params: ScanVirusParams = { action: \"fail\", timeout: 60000 },\n options?: { keepOutput?: boolean },\n) {\n return Effect.gen(function* () {\n const virusScanService = yield* VirusScanPlugin;\n\n // Validate params\n const validatedParams = ScanVirusParams.parse(params);\n\n return yield* createTransformNode({\n id,\n name: \"Scan Virus\",\n description: \"Scans files for viruses and malware using ClamAV\",\n nodeTypeId: \"scan-virus\",\n outputTypeId: STORAGE_OUTPUT_TYPE_ID,\n keepOutput: options?.keepOutput,\n // External service - enable circuit breaker with fail fallback (don't skip security scans)\n circuitBreaker: {\n enabled: true,\n failureThreshold: 5,\n resetTimeout: 60000,\n fallback: { type: \"fail\" },\n },\n transform: (inputBytes, file) =>\n Effect.gen(function* () {\n // Perform virus scan\n const scanResult = yield* virusScanService.scan(inputBytes);\n\n // Get engine version for metadata\n const engineVersion = yield* virusScanService.getVersion();\n\n // Build comprehensive scan metadata\n const scanMetadata: ScanMetadata = {\n scanned: true,\n isClean: scanResult.isClean,\n detectedViruses: scanResult.detectedViruses,\n scanDate: new Date().toISOString(),\n engineVersion,\n definitionsDate: new Date().toISOString(), // TODO: Get actual definitions date from plugin\n };\n\n // Check if virus was detected\n if (!scanResult.isClean) {\n // Build error message with detected viruses\n const virusList = scanResult.detectedViruses.join(\", \");\n const message = `Virus detected: ${virusList}`;\n\n // Handle based on configured action\n if (validatedParams.action === \"fail\") {\n // Fail the flow task\n return yield* httpFailure(\"VIRUS_DETECTED\", {\n body: message,\n details: { scanMetadata },\n });\n }\n // action === \"pass\": Continue with metadata (handled below)\n }\n\n // Return file with scan metadata (clean or pass action)\n return {\n bytes: inputBytes, // Pass through original bytes unchanged\n metadata: {\n ...file.metadata,\n virusScan: scanMetadata,\n },\n };\n }),\n });\n });\n}\n"],"mappings":"2NAaA,MAAa,EAAa,EAAE,KAAK,CAAC,OAAQ,OAAO,CAAC,CAMrC,EAAkB,EAAE,OAAO,CAMtC,OAAQ,EAAW,QAAQ,OAAO,CAOlC,QAAS,EAAE,QAAQ,CAAC,IAAI,IAAK,CAAC,IAAI,IAAO,CAAC,UAAU,CAAC,QAAQ,IAAM,CACpE,CAAC,CAgCF,SAAgB,EACd,EACA,EAA0B,CAAE,OAAQ,OAAQ,QAAS,IAAO,CAC5D,EACA,CACA,OAAO,EAAO,IAAI,WAAa,CAC7B,IAAM,EAAmB,MAAO,EAG1B,EAAkB,EAAgB,MAAM,EAAO,CAErD,OAAO,MAAO,EAAoB,CAChC,KACA,KAAM,aACN,YAAa,mDACb,WAAY,aACZ,aAAc,EACd,WAAY,GAAS,WAErB,eAAgB,CACd,QAAS,GACT,iBAAkB,EAClB,aAAc,IACd,SAAU,CAAE,KAAM,OAAQ,CAC3B,CACD,WAAY,EAAY,IACtB,EAAO,IAAI,WAAa,CAEtB,IAAM,EAAa,MAAO,EAAiB,KAAK,EAAW,CAGrD,EAAgB,MAAO,EAAiB,YAAY,CAGpDA,EAA6B,CACjC,QAAS,GACT,QAAS,EAAW,QACpB,gBAAiB,EAAW,gBAC5B,SAAU,IAAI,MAAM,CAAC,aAAa,CAClC,gBACA,gBAAiB,IAAI,MAAM,CAAC,aAAa,CAC1C,CAGD,GAAI,CAAC,EAAW,QAAS,CAGvB,IAAM,EAAU,mBADE,EAAW,gBAAgB,KAAK,KAAK,GAIvD,GAAI,EAAgB,SAAW,OAE7B,OAAO,MAAO,EAAY,iBAAkB,CAC1C,KAAM,EACN,QAAS,CAAE,eAAc,CAC1B,CAAC,CAMN,MAAO,CACL,MAAO,EACP,SAAU,CACR,GAAG,EAAK,SACR,UAAW,EACZ,CACF,EACD,CACL,CAAC,EACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/scan-virus-node.ts"],"sourcesContent":["import { httpFailure } from \"@uploadista/core/errors\";\nimport {\n createTransformNode,\n type ScanMetadata,\n STORAGE_OUTPUT_TYPE_ID,\n VirusScanPlugin,\n} from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\nimport { z } from \"zod\";\n\n/**\n * Scan action to take when a virus is detected\n */\nexport const ScanAction = z.enum([\"fail\", \"pass\"]);\nexport type ScanAction = z.infer<typeof ScanAction>;\n\n/**\n * Parameters for the Scan Virus node\n */\nexport const ScanVirusParams = z.object({\n /**\n * Action to take when a virus is detected:\n * - \"fail\": Mark flow task as FAILED and stop processing\n * - \"pass\": Continue processing but add virus metadata to file\n */\n action: ScanAction.default(\"fail\"),\n\n /**\n * Maximum time to wait for scan completion (in milliseconds)\n * Default: 60000ms (60 seconds)\n * Max: 300000ms (5 minutes)\n */\n timeout: z.number().min(1000).max(300000).optional().default(60000),\n});\n\nexport type ScanVirusParams = z.infer<typeof ScanVirusParams>;\n\n/**\n * Creates a Scan Virus node for malware detection\n *\n * Scans files for viruses and malware using the configured VirusScanPlugin\n * (typically ClamAV). Supports configurable actions on detection:\n * - \"fail\": Stop flow execution when virus detected\n * - \"pass\": Continue flow with detection metadata\n *\n * All scan results are stored in file.metadata.virusScan for downstream nodes.\n *\n * @param id - Unique node identifier\n * @param params - Configuration parameters for scan behavior\n * @returns Effect that resolves to the configured node\n *\n * @example\n * ```typescript\n * // Fail on virus detection (default)\n * const failNode = yield* createScanVirusNode(\"scan-1\", {\n * action: \"fail\"\n * });\n *\n * // Pass through with metadata\n * const passNode = yield* createScanVirusNode(\"scan-2\", {\n * action: \"pass\",\n * timeout: 120000 // 2 minutes\n * });\n * ```\n */\nexport function createScanVirusNode(\n id: string,\n params: ScanVirusParams = { action: \"fail\", timeout: 60000 },\n options?: { keepOutput?: boolean },\n) {\n return Effect.gen(function* () {\n const virusScanService = yield* VirusScanPlugin;\n\n // Validate params\n const validatedParams = ScanVirusParams.parse(params);\n\n return yield* createTransformNode({\n id,\n name: \"Scan Virus\",\n description: \"Scans files for viruses and malware using ClamAV\",\n nodeTypeId: \"scan-virus\",\n outputTypeId: STORAGE_OUTPUT_TYPE_ID,\n keepOutput: options?.keepOutput,\n // External service - enable circuit breaker with fail fallback (don't skip security scans)\n circuitBreaker: {\n enabled: true,\n failureThreshold: 5,\n resetTimeout: 60000,\n fallback: { type: \"fail\" },\n },\n transform: (inputBytes, file) =>\n Effect.gen(function* () {\n // Perform virus scan\n const scanResult = yield* virusScanService.scan(inputBytes);\n\n // Get engine version for metadata\n const engineVersion = yield* virusScanService.getVersion();\n\n // Build comprehensive scan metadata\n const scanMetadata: ScanMetadata = {\n scanned: true,\n isClean: scanResult.isClean,\n detectedViruses: scanResult.detectedViruses,\n scanDate: new Date().toISOString(),\n engineVersion,\n definitionsDate: new Date().toISOString(), // TODO: Get actual definitions date from plugin\n };\n\n // Check if virus was detected\n if (!scanResult.isClean) {\n // Build error message with detected viruses\n const virusList = scanResult.detectedViruses.join(\", \");\n const message = `Virus detected: ${virusList}`;\n\n // Handle based on configured action\n if (validatedParams.action === \"fail\") {\n // Fail the flow task\n return yield* httpFailure(\"VIRUS_DETECTED\", {\n body: message,\n details: { scanMetadata },\n });\n }\n // action === \"pass\": Continue with metadata (handled below)\n }\n\n // Return file with scan metadata (clean or pass action)\n return {\n bytes: inputBytes, // Pass through original bytes unchanged\n metadata: {\n ...file.metadata,\n virusScan: scanMetadata,\n },\n };\n }),\n });\n });\n}\n"],"mappings":"2NAaA,MAAa,EAAa,EAAE,KAAK,CAAC,OAAQ,OAAO,CAAC,CAMrC,EAAkB,EAAE,OAAO,CAMtC,OAAQ,EAAW,QAAQ,OAAO,CAOlC,QAAS,EAAE,QAAQ,CAAC,IAAI,IAAK,CAAC,IAAI,IAAO,CAAC,UAAU,CAAC,QAAQ,IAAM,CACpE,CAAC,CAgCF,SAAgB,EACd,EACA,EAA0B,CAAE,OAAQ,OAAQ,QAAS,IAAO,CAC5D,EACA,CACA,OAAO,EAAO,IAAI,WAAa,CAC7B,IAAM,EAAmB,MAAO,EAG1B,EAAkB,EAAgB,MAAM,EAAO,CAErD,OAAO,MAAO,EAAoB,CAChC,KACA,KAAM,aACN,YAAa,mDACb,WAAY,aACZ,aAAc,EACd,WAAY,GAAS,WAErB,eAAgB,CACd,QAAS,GACT,iBAAkB,EAClB,aAAc,IACd,SAAU,CAAE,KAAM,OAAQ,CAC3B,CACD,WAAY,EAAY,IACtB,EAAO,IAAI,WAAa,CAEtB,IAAM,EAAa,MAAO,EAAiB,KAAK,EAAW,CAGrD,EAAgB,MAAO,EAAiB,YAAY,CAGpD,EAA6B,CACjC,QAAS,GACT,QAAS,EAAW,QACpB,gBAAiB,EAAW,gBAC5B,SAAU,IAAI,MAAM,CAAC,aAAa,CAClC,gBACA,gBAAiB,IAAI,MAAM,CAAC,aAAa,CAC1C,CAGD,GAAI,CAAC,EAAW,QAAS,CAGvB,IAAM,EAAU,mBADE,EAAW,gBAAgB,KAAK,KAAK,GAIvD,GAAI,EAAgB,SAAW,OAE7B,OAAO,MAAO,EAAY,iBAAkB,CAC1C,KAAM,EACN,QAAS,CAAE,eAAc,CAC1B,CAAC,CAMN,MAAO,CACL,MAAO,EACP,SAAU,CACR,GAAG,EAAK,SACR,UAAW,EACZ,CACF,EACD,CACL,CAAC,EACF"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@uploadista/flow-security-nodes",
3
3
  "type": "module",
4
- "version": "0.0.20-beta.9",
4
+ "version": "0.1.0-beta.5",
5
5
  "description": "Security processing nodes for Uploadista Flow",
6
6
  "license": "MIT",
7
7
  "author": "Uploadista",
@@ -14,7 +14,7 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "@uploadista/core": "0.0.20-beta.9"
17
+ "@uploadista/core": "0.1.0-beta.5"
18
18
  },
19
19
  "peerDependencies": {
20
20
  "effect": "^3.0.0",
@@ -22,19 +22,19 @@
22
22
  },
23
23
  "devDependencies": {
24
24
  "@effect/vitest": "0.27.0",
25
- "@types/node": "24.10.4",
26
- "effect": "3.19.12",
27
- "tsdown": "0.18.0",
28
- "vitest": "4.0.15",
29
- "zod": "4.2.0",
30
- "@uploadista/typescript-config": "0.0.20-beta.9"
25
+ "@types/node": "24.10.8",
26
+ "effect": "3.19.14",
27
+ "tsdown": "0.19.0",
28
+ "vitest": "4.0.17",
29
+ "zod": "4.3.5",
30
+ "@uploadista/typescript-config": "0.1.0-beta.5"
31
31
  },
32
32
  "scripts": {
33
33
  "build": "tsc --noEmit && tsdown",
34
34
  "format": "biome format --write ./src",
35
35
  "lint": "biome lint --write ./src",
36
36
  "check": "biome check --write ./src",
37
- "test": "vitest",
37
+ "test": "vitest run",
38
38
  "test:run": "vitest run",
39
39
  "test:watch": "vitest watch"
40
40
  }
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it } from "@effect/vitest";
2
2
  import { UploadistaError } from "@uploadista/core/errors";
3
3
  import {
4
- TestUploadServer,
4
+ TestUploadEngine,
5
5
  TestVirusScanPlugin,
6
6
  } from "@uploadista/core/testing";
7
7
  import type { UploadFile } from "@uploadista/core/types";
@@ -56,7 +56,7 @@ const createInfectedFileBytes = (): Uint8Array => {
56
56
  /**
57
57
  * Test layer combining all mocks
58
58
  */
59
- const TestLayer = Layer.mergeAll(TestVirusScanPlugin, TestUploadServer);
59
+ const TestLayer = Layer.mergeAll(TestVirusScanPlugin, TestUploadEngine);
60
60
 
61
61
  describe("Scan Virus Node", () => {
62
62
  describe("Node Creation", () => {
@@ -64,6 +64,7 @@ describe("Scan Virus Node", () => {
64
64
  Effect.gen(function* () {
65
65
  const node = yield* createScanVirusNode("scan-1", {
66
66
  action: "fail",
67
+ timeout: 60000,
67
68
  });
68
69
 
69
70
  expect(node.id).toBe("scan-1");
@@ -89,6 +90,7 @@ describe("Scan Virus Node", () => {
89
90
  Effect.gen(function* () {
90
91
  const node = yield* createScanVirusNode("scan-clean", {
91
92
  action: "fail",
93
+ timeout: 60000,
92
94
  });
93
95
 
94
96
  const testFile = createTestUploadFile();
@@ -117,6 +119,7 @@ describe("Scan Virus Node", () => {
117
119
  Effect.gen(function* () {
118
120
  const node = yield* createScanVirusNode("scan-metadata", {
119
121
  action: "fail",
122
+ timeout: 60000,
120
123
  });
121
124
 
122
125
  const testFile = createTestUploadFile();
@@ -147,6 +150,7 @@ describe("Scan Virus Node", () => {
147
150
  Effect.gen(function* () {
148
151
  const node = yield* createScanVirusNode("scan-preserve", {
149
152
  action: "fail",
153
+ timeout: 60000,
150
154
  });
151
155
 
152
156
  const testFile = createTestUploadFile({
@@ -182,41 +186,43 @@ describe("Scan Virus Node", () => {
182
186
  });
183
187
 
184
188
  describe("Infected File Scanning - Fail Action", () => {
185
- it.effect(
186
- "should fail flow when virus detected with fail action",
187
- () =>
188
- Effect.gen(function* () {
189
- const node = yield* createScanVirusNode("scan-fail", {
190
- action: "fail",
191
- });
189
+ it.effect("should fail flow when virus detected with fail action", () =>
190
+ Effect.gen(function* () {
191
+ const node = yield* createScanVirusNode("scan-fail", {
192
+ action: "fail",
193
+ timeout: 60000,
194
+ });
192
195
 
193
- const testFile = createTestUploadFile();
194
-
195
- const result = yield* Effect.either(
196
- node.run({
197
- data: testFile,
198
- jobId: "test-job",
199
- flowId: "test-flow",
200
- storageId: "test-storage",
201
- clientId: "test-client",
202
- }),
203
- );
204
-
205
- expect(result._tag).toBe("Left");
206
- if (result._tag === "Left") {
207
- expect(result.left).toBeInstanceOf(UploadistaError);
208
- expect(result.left.code).toBe("VIRUS_DETECTED");
209
- }
210
- }).pipe(Effect.provide(TestLayer)),
196
+ // Use file ID containing "infected" to trigger EICAR content from mock
197
+ const testFile = createTestUploadFile({ id: "infected-file-1" });
198
+
199
+ const result = yield* Effect.either(
200
+ node.run({
201
+ data: testFile,
202
+ jobId: "test-job",
203
+ flowId: "test-flow",
204
+ storageId: "test-storage",
205
+ clientId: "test-client",
206
+ }),
207
+ );
208
+
209
+ expect(result._tag).toBe("Left");
210
+ if (result._tag === "Left") {
211
+ expect(result.left).toBeInstanceOf(UploadistaError);
212
+ expect(result.left.code).toBe("VIRUS_DETECTED");
213
+ }
214
+ }).pipe(Effect.provide(TestLayer)),
211
215
  );
212
216
 
213
217
  it.effect("should include virus names in error message", () =>
214
218
  Effect.gen(function* () {
215
219
  const node = yield* createScanVirusNode("scan-names", {
216
220
  action: "fail",
221
+ timeout: 60000,
217
222
  });
218
223
 
219
- const testFile = createTestUploadFile();
224
+ // Use file ID containing "infected" to trigger EICAR content from mock
225
+ const testFile = createTestUploadFile({ id: "infected-file-2" });
220
226
 
221
227
  const result = yield* Effect.either(
222
228
  node.run({
@@ -241,9 +247,11 @@ describe("Scan Virus Node", () => {
241
247
  Effect.gen(function* () {
242
248
  const node = yield* createScanVirusNode("scan-details", {
243
249
  action: "fail",
250
+ timeout: 60000,
244
251
  });
245
252
 
246
- const testFile = createTestUploadFile();
253
+ // Use file ID containing "infected" to trigger EICAR content from mock
254
+ const testFile = createTestUploadFile({ id: "infected-file-3" });
247
255
 
248
256
  const result = yield* Effect.either(
249
257
  node.run({
@@ -262,35 +270,35 @@ describe("Scan Virus Node", () => {
262
270
  expect(error.details?.scanMetadata).toBeDefined();
263
271
  expect(error.details?.scanMetadata.isClean).toBe(false);
264
272
  expect(error.details?.scanMetadata.detectedViruses).toBeDefined();
265
- expect(error.details?.scanMetadata.detectedViruses.length).toBeGreaterThan(
266
- 0,
267
- );
273
+ expect(
274
+ error.details?.scanMetadata.detectedViruses.length,
275
+ ).toBeGreaterThan(0);
268
276
  }
269
277
  }).pipe(Effect.provide(TestLayer)),
270
278
  );
271
279
  });
272
280
 
273
281
  describe("Infected File Scanning - Pass Action", () => {
274
- it.effect(
275
- "should continue flow when virus detected with pass action",
276
- () =>
277
- Effect.gen(function* () {
278
- const node = yield* createScanVirusNode("scan-pass", {
279
- action: "pass",
280
- });
282
+ it.effect("should continue flow when virus detected with pass action", () =>
283
+ Effect.gen(function* () {
284
+ const node = yield* createScanVirusNode("scan-pass", {
285
+ action: "pass",
286
+ timeout: 60000,
287
+ });
281
288
 
282
- const testFile = createTestUploadFile();
289
+ // Use file ID containing "infected" to trigger EICAR content from mock
290
+ const testFile = createTestUploadFile({ id: "infected-file-pass-1" });
283
291
 
284
- const result = yield* node.run({
285
- data: testFile,
286
- jobId: "test-job",
287
- flowId: "test-flow",
288
- storageId: "test-storage",
289
- clientId: "test-client",
290
- });
292
+ const result = yield* node.run({
293
+ data: testFile,
294
+ jobId: "test-job",
295
+ flowId: "test-flow",
296
+ storageId: "test-storage",
297
+ clientId: "test-client",
298
+ });
291
299
 
292
- expect(result.type).toBe("complete");
293
- }).pipe(Effect.provide(TestLayer)),
300
+ expect(result.type).toBe("complete");
301
+ }).pipe(Effect.provide(TestLayer)),
294
302
  );
295
303
 
296
304
  it.effect(
@@ -299,9 +307,11 @@ describe("Scan Virus Node", () => {
299
307
  Effect.gen(function* () {
300
308
  const node = yield* createScanVirusNode("scan-pass-metadata", {
301
309
  action: "pass",
310
+ timeout: 60000,
302
311
  });
303
312
 
304
- const testFile = createTestUploadFile();
313
+ // Use file ID containing "infected" to trigger EICAR content from mock
314
+ const testFile = createTestUploadFile({ id: "infected-file-pass-2" });
305
315
 
306
316
  const result = yield* node.run({
307
317
  data: testFile,
@@ -327,9 +337,11 @@ describe("Scan Virus Node", () => {
327
337
  Effect.gen(function* () {
328
338
  const node = yield* createScanVirusNode("scan-pass-bytes", {
329
339
  action: "pass",
340
+ timeout: 60000,
330
341
  });
331
342
 
332
- const testFile = createTestUploadFile();
343
+ // Use file ID containing "infected" to trigger EICAR content from mock
344
+ const testFile = createTestUploadFile({ id: "infected-file-pass-3" });
333
345
 
334
346
  const result = yield* node.run({
335
347
  data: testFile,
@@ -342,7 +354,9 @@ describe("Scan Virus Node", () => {
342
354
  expect(result.type).toBe("complete");
343
355
  if (result.type === "complete") {
344
356
  expect(result.data).toBeDefined();
345
- expect(result.data.id).toBe(testFile.id);
357
+ // The transform creates a new upload with a new ID, but the file is processed
358
+ expect(result.data.id).toBeDefined();
359
+ expect(typeof result.data.id).toBe("string");
346
360
  }
347
361
  }).pipe(Effect.provide(TestLayer)),
348
362
  );
@@ -375,6 +389,7 @@ describe("Scan Virus Node", () => {
375
389
  Effect.gen(function* () {
376
390
  const node = yield* createScanVirusNode("scan-default-timeout", {
377
391
  action: "fail",
392
+ timeout: 60000,
378
393
  });
379
394
 
380
395
  expect(node.id).toBe("scan-default-timeout");
@@ -398,6 +413,7 @@ describe("Scan Virus Node", () => {
398
413
  Effect.gen(function* () {
399
414
  const node = yield* createScanVirusNode("scan-version", {
400
415
  action: "fail",
416
+ timeout: 60000,
401
417
  });
402
418
 
403
419
  const testFile = createTestUploadFile();