@uploadista/core 0.0.13-beta.5 → 0.0.13

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 (101) hide show
  1. package/README.md +1 -1
  2. package/dist/{checksum-P9C2JlRk.mjs → checksum-CtOagryS.mjs} +2 -2
  3. package/dist/{checksum-P9C2JlRk.mjs.map → checksum-CtOagryS.mjs.map} +1 -1
  4. package/dist/errors/index.d.cts +2 -2
  5. package/dist/errors/index.d.mts +2 -2
  6. package/dist/errors/index.mjs +1 -1
  7. package/dist/flow/index.cjs +1 -1
  8. package/dist/flow/index.d.cts +5 -5
  9. package/dist/flow/index.d.mts +5 -5
  10. package/dist/flow/index.mjs +1 -1
  11. package/dist/{flow-DkTE3siV.cjs → flow-ChADffZ5.cjs} +1 -1
  12. package/dist/{flow-IgE8hj7H.mjs → flow-_J9-Dm_m.mjs} +2 -2
  13. package/dist/flow-_J9-Dm_m.mjs.map +1 -0
  14. package/dist/{index-CrZopnP9.d.cts → index-4VDJDcWM.d.cts} +227 -241
  15. package/dist/index-4VDJDcWM.d.cts.map +1 -0
  16. package/dist/{index-BPBI84iT.d.mts → index-Bi9YYid8.d.mts} +2 -2
  17. package/dist/{index-BPBI84iT.d.mts.map → index-Bi9YYid8.d.mts.map} +1 -1
  18. package/dist/{index-BteFEg-c.d.mts → index-Cbf1OPLp.d.mts} +2 -2
  19. package/dist/{index-BteFEg-c.d.mts.map → index-Cbf1OPLp.d.mts.map} +1 -1
  20. package/dist/{index-DMfADSSJ.d.cts → index-De4wQJwR.d.cts} +2 -2
  21. package/dist/{index-DMfADSSJ.d.cts.map → index-De4wQJwR.d.cts.map} +1 -1
  22. package/dist/{index-DHt7Ht_J.d.mts → index-RgOX4psL.d.mts} +305 -139
  23. package/dist/index-RgOX4psL.d.mts.map +1 -0
  24. package/dist/{index-DubOIur4.d.cts → index-qZ90PVNl.d.cts} +2 -2
  25. package/dist/index-qZ90PVNl.d.cts.map +1 -0
  26. package/dist/index.cjs +1 -1
  27. package/dist/index.d.cts +5 -5
  28. package/dist/index.d.mts +5 -5
  29. package/dist/index.mjs +1 -1
  30. package/dist/{stream-limiter-DFtRZczp.mjs → stream-limiter-D9KSAaoY.mjs} +2 -2
  31. package/dist/{stream-limiter-DFtRZczp.mjs.map → stream-limiter-D9KSAaoY.mjs.map} +1 -1
  32. package/dist/streams/index.d.cts +2 -2
  33. package/dist/streams/index.d.mts +2 -2
  34. package/dist/streams/index.mjs +1 -1
  35. package/dist/testing/index.cjs +1 -0
  36. package/dist/testing/index.d.cts +110 -0
  37. package/dist/testing/index.d.cts.map +1 -0
  38. package/dist/testing/index.d.mts +110 -0
  39. package/dist/testing/index.d.mts.map +1 -0
  40. package/dist/testing/index.mjs +2 -0
  41. package/dist/testing/index.mjs.map +1 -0
  42. package/dist/types/index.d.cts +5 -5
  43. package/dist/types/index.d.mts +5 -5
  44. package/dist/types/index.mjs +1 -1
  45. package/dist/{types-DGZ892my.mjs → types-BI_KmpTc.mjs} +2 -2
  46. package/dist/types-BI_KmpTc.mjs.map +1 -0
  47. package/dist/upload/index.d.cts +5 -5
  48. package/dist/upload/index.d.mts +5 -5
  49. package/dist/upload/index.mjs +1 -1
  50. package/dist/{upload-DJTptYqV.mjs → upload-Yj5lrtZo.mjs} +2 -2
  51. package/dist/{upload-DJTptYqV.mjs.map → upload-Yj5lrtZo.mjs.map} +1 -1
  52. package/dist/{uploadista-error-9yLWP7TC.d.cts → uploadista-error-BQLhNZcY.d.cts} +1 -1
  53. package/dist/{uploadista-error-9yLWP7TC.d.cts.map → uploadista-error-BQLhNZcY.d.cts.map} +1 -1
  54. package/dist/{uploadista-error-nZ_q-EZy.mjs → uploadista-error-Buscq-FR.mjs} +1 -1
  55. package/dist/{uploadista-error-nZ_q-EZy.mjs.map → uploadista-error-Buscq-FR.mjs.map} +1 -1
  56. package/dist/{uploadista-error-CBkvsyZ3.d.mts → uploadista-error-DUWw6OqS.d.mts} +1 -1
  57. package/dist/{uploadista-error-CBkvsyZ3.d.mts.map → uploadista-error-DUWw6OqS.d.mts.map} +1 -1
  58. package/dist/utils/index.d.cts +2 -2
  59. package/dist/utils/index.d.mts +2 -2
  60. package/dist/utils/index.mjs +1 -1
  61. package/dist/{utils-BicUw_lt.mjs → utils-BWiu6lqv.mjs} +2 -2
  62. package/dist/{utils-BicUw_lt.mjs.map → utils-BWiu6lqv.mjs.map} +1 -1
  63. package/package.json +14 -6
  64. package/src/flow/node.ts +4 -4
  65. package/src/flow/nodes/transform-node.ts +23 -2
  66. package/src/flow/plugins/credential-provider.ts +1 -1
  67. package/src/flow/plugins/image-ai-plugin.ts +1 -1
  68. package/src/flow/plugins/image-plugin.ts +1 -1
  69. package/src/flow/plugins/video-plugin.ts +1 -1
  70. package/src/flow/plugins/zip-plugin.ts +1 -1
  71. package/src/flow/types/type-utils.ts +14 -3
  72. package/src/testing/index.ts +14 -0
  73. package/src/testing/mock-image-ai-plugin.ts +33 -0
  74. package/src/testing/mock-image-plugin.ts +56 -0
  75. package/src/testing/mock-upload-server.ts +176 -0
  76. package/src/testing/mock-video-plugin.ts +94 -0
  77. package/src/testing/mock-zip-plugin.ts +41 -0
  78. package/src/types/data-store.ts +1 -1
  79. package/{src/errors/__tests__ → tests/errors}/uploadista-error.test.ts +23 -19
  80. package/{src → tests}/flow/edge.test.ts +1 -1
  81. package/tests/flow/flow.test.ts +853 -0
  82. package/tests/flow/node.test.ts +757 -0
  83. package/{src → tests}/streams/stream-limiter.test.ts +2 -2
  84. package/tests/types/typed-event-emitter.test.ts +282 -0
  85. package/{src → tests}/utils/debounce.test.ts +1 -1
  86. package/{src → tests}/utils/once.test.ts +1 -1
  87. package/tests/utils/test-layers.ts +183 -0
  88. package/{src → tests}/utils/throttle.test.ts +1 -1
  89. package/tsdown.config.ts +1 -0
  90. package/type-tests/flow.test-d.ts +93 -0
  91. package/type-tests/type-utils.test-d.ts +104 -51
  92. package/vitest.config.ts +19 -1
  93. package/dist/flow-IgE8hj7H.mjs.map +0 -1
  94. package/dist/index-CrZopnP9.d.cts.map +0 -1
  95. package/dist/index-DHt7Ht_J.d.mts.map +0 -1
  96. package/dist/index-DubOIur4.d.cts.map +0 -1
  97. package/dist/types-DGZ892my.mjs.map +0 -1
  98. /package/dist/{errors-C0zLx77t.mjs → errors-DEFjN-xn.mjs} +0 -0
  99. /package/dist/{index-BtBZHVmz.d.cts → index-C-svZlpj.d.mts} +0 -0
  100. /package/dist/{index-DEHBdV_z.d.mts → index-_wQ5ClJU.d.cts} +0 -0
  101. /package/dist/{streams-CJKKIAwy.mjs → streams-DPU17bYp.mjs} +0 -0
@@ -0,0 +1,176 @@
1
+ import { Effect, Layer } from "effect";
2
+ import type { InputFile, UploadFile, WebSocketConnection } from "../types";
3
+ import { UploadServer } from "../upload";
4
+ import type { DataStoreCapabilities } from "../types/data-store";
5
+
6
+ /**
7
+ * Mock UploadServer implementation for testing.
8
+ *
9
+ * Provides a complete in-memory implementation of all UploadServer methods
10
+ * suitable for unit and integration tests.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { TestUploadServer } from "@uploadista/core/testing";
15
+ *
16
+ * const program = Effect.gen(function* () {
17
+ * const server = yield* UploadServer;
18
+ * const upload = yield* server.createUpload(inputFile, "client-123");
19
+ * return upload;
20
+ * }).pipe(Effect.provide(TestUploadServer));
21
+ * ```
22
+ */
23
+ export const TestUploadServer = Layer.succeed(
24
+ UploadServer,
25
+ UploadServer.of({
26
+ read: (fileId: string, _clientId: string | null) =>
27
+ Effect.sync(() => {
28
+ // Generate mock file data based on fileId
29
+ const text = `Content of file ${fileId}`;
30
+ return new TextEncoder().encode(text);
31
+ }),
32
+ upload: (file, _clientId, stream) =>
33
+ Effect.gen(function* () {
34
+ // Read stream to completion
35
+ const reader = stream.getReader();
36
+ let totalSize = 0;
37
+ const chunks: Uint8Array[] = [];
38
+
39
+ while (true) {
40
+ const { done, value } = yield* Effect.promise(() => reader.read());
41
+ if (done) break;
42
+ if (value) {
43
+ chunks.push(value);
44
+ totalSize += value.byteLength;
45
+ }
46
+ }
47
+
48
+ // Parse existing metadata
49
+ const existingMetadata =
50
+ typeof file.metadata === "string"
51
+ ? JSON.parse(file.metadata)
52
+ : file.metadata || {};
53
+
54
+ // Extract extension from fileName
55
+ const extension = file.fileName
56
+ ? file.fileName.split(".").pop()
57
+ : existingMetadata.extension;
58
+
59
+ // Create new UploadFile with merged metadata
60
+ return {
61
+ id: `uploaded-${Date.now()}-${Math.random().toString(36).substring(7)}`,
62
+ offset: totalSize,
63
+ size: totalSize,
64
+ storage: {
65
+ id: file.storageId,
66
+ type: "memory",
67
+ },
68
+ metadata: {
69
+ ...existingMetadata,
70
+ // Update with InputFile type and fileName
71
+ mimeType: file.type,
72
+ type: file.type,
73
+ "content-type": file.type,
74
+ fileName: file.fileName,
75
+ originalName: file.fileName,
76
+ name: file.fileName,
77
+ extension,
78
+ },
79
+ creationDate: new Date().toISOString(),
80
+ } satisfies UploadFile;
81
+ }),
82
+ delete: (_fileId: string, _clientId: string | null) => Effect.void,
83
+ createUpload: (file: InputFile, _clientId: string | null) =>
84
+ Effect.succeed({
85
+ id: `uploaded-${Date.now()}-${Math.random().toString(36).substring(7)}`,
86
+ offset: 0,
87
+ size: 0,
88
+ storage: { id: file.storageId, type: "memory" },
89
+ metadata:
90
+ typeof file.metadata === "string"
91
+ ? JSON.parse(file.metadata)
92
+ : file.metadata,
93
+ } satisfies UploadFile),
94
+ uploadChunk: (
95
+ uploadId: string,
96
+ _clientId: string | null,
97
+ chunk: ReadableStream,
98
+ ) =>
99
+ Effect.gen(function* () {
100
+ // Read stream to completion
101
+ const reader = chunk.getReader();
102
+ let totalSize = 0;
103
+ const chunks: Uint8Array[] = [];
104
+
105
+ while (true) {
106
+ const { done, value } = yield* Effect.promise(() => reader.read());
107
+ if (done) break;
108
+ if (value) {
109
+ chunks.push(value);
110
+ totalSize += value.byteLength;
111
+ }
112
+ }
113
+ return {
114
+ id: uploadId,
115
+ offset: totalSize,
116
+ size: totalSize,
117
+ storage: { id: "test-storage", type: "memory" },
118
+ metadata: { mimeType: "application/octet-stream", extension: "bin" },
119
+ creationDate: new Date().toISOString(),
120
+ } satisfies UploadFile;
121
+ }),
122
+ getCapabilities: (_storageId: string, _clientId: string | null) =>
123
+ Effect.succeed({
124
+ supportsParallelUploads: true,
125
+ supportsConcatenation: true,
126
+ supportsDeferredLength: true,
127
+ supportsResumableUploads: true,
128
+ supportsTransactionalUploads: false,
129
+ maxConcurrentUploads: 10,
130
+ minChunkSize: 5 * 1024 * 1024, // 5MB
131
+ maxChunkSize: 100 * 1024 * 1024, // 100MB
132
+ maxParts: 10000,
133
+ optimalChunkSize: 10 * 1024 * 1024, // 10MB
134
+ requiresOrderedChunks: false,
135
+ requiresMimeTypeValidation: false,
136
+ } satisfies DataStoreCapabilities),
137
+ uploadFromUrl: (
138
+ inputFile: InputFile,
139
+ _clientId: string | null,
140
+ url: string,
141
+ ) =>
142
+ Effect.succeed({
143
+ id: `uploaded-from-url-${Date.now()}-${Math.random().toString(36).substring(7)}`,
144
+ offset: 0,
145
+ size: 0,
146
+ storage: { id: inputFile.storageId, type: "memory" },
147
+ metadata:
148
+ typeof inputFile.metadata === "string"
149
+ ? JSON.parse(inputFile.metadata)
150
+ : inputFile.metadata,
151
+ url,
152
+ creationDate: new Date().toISOString(),
153
+ } satisfies UploadFile),
154
+ getUpload: (uploadId: string) =>
155
+ Effect.succeed({
156
+ id: uploadId,
157
+ offset: 0,
158
+ size: 1024,
159
+ storage: {
160
+ id: "test-storage",
161
+ type: "memory",
162
+ },
163
+ metadata: {
164
+ mimeType: "text/plain",
165
+ originalName: `file-${uploadId}.txt`,
166
+ extension: "txt",
167
+ },
168
+ creationDate: new Date().toISOString(),
169
+ } satisfies UploadFile),
170
+ subscribeToUploadEvents: (
171
+ _uploadId: string,
172
+ _connection: WebSocketConnection,
173
+ ) => Effect.void,
174
+ unsubscribeFromUploadEvents: (_uploadId: string) => Effect.void,
175
+ }),
176
+ );
@@ -0,0 +1,94 @@
1
+ import { Effect, Layer } from "effect";
2
+ import { VideoPlugin } from "../flow";
3
+ import type { DescribeVideoMetadata } from "../flow/plugins/types/describe-video-node";
4
+
5
+ /**
6
+ * Mock VideoPlugin implementation for testing.
7
+ *
8
+ * Provides simple mock implementations of video processing operations
9
+ * that return mock Uint8Array data without requiring FFmpeg or node-av.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { TestVideoPlugin } from "@uploadista/core/testing";
14
+ *
15
+ * const program = Effect.gen(function* () {
16
+ * const plugin = yield* VideoPlugin;
17
+ * const transcoded = yield* plugin.transcode(videoBytes, { format: "webm", codec: "vp9" });
18
+ * return transcoded;
19
+ * }).pipe(Effect.provide(TestVideoPlugin));
20
+ * ```
21
+ */
22
+ export const TestVideoPlugin = Layer.succeed(
23
+ VideoPlugin,
24
+ VideoPlugin.of({
25
+ describe: (input: Uint8Array) =>
26
+ Effect.sync(() => {
27
+ // Mock describe: return fake metadata based on input size
28
+ const metadata: DescribeVideoMetadata = {
29
+ duration: 120, // 2 minutes
30
+ width: 1920,
31
+ height: 1080,
32
+ codec: "h264",
33
+ format: "mp4",
34
+ bitrate: 5000000, // 5 Mbps
35
+ frameRate: 30,
36
+ aspectRatio: "16:9",
37
+ hasAudio: true,
38
+ audioCodec: "aac",
39
+ audioBitrate: 128000, // 128 kbps
40
+ size: input.byteLength,
41
+ };
42
+ return metadata;
43
+ }),
44
+ transcode: (_input: Uint8Array, options) =>
45
+ Effect.sync(() => {
46
+ // Mock transcode: return modified array
47
+ // Simulate different file sizes for different codecs
48
+ let sizeMultiplier = 1.0;
49
+ if (options.codec === "vp9") {
50
+ sizeMultiplier = 0.8; // VP9 is more efficient
51
+ } else if (options.codec === "h265") {
52
+ sizeMultiplier = 0.7; // H265 is even more efficient
53
+ }
54
+
55
+ const newSize = Math.floor(_input.byteLength * sizeMultiplier);
56
+ return new Uint8Array(newSize).fill(42);
57
+ }),
58
+ resize: (input: Uint8Array, options) =>
59
+ Effect.sync(() => {
60
+ // Mock resize: return array with size based on dimensions
61
+ const width = options.width || 1280;
62
+ const height = options.height || 720;
63
+ // Simulate file size roughly proportional to resolution
64
+ const mockSize = Math.floor((width * height) / 50);
65
+ return new Uint8Array(mockSize).fill(84);
66
+ }),
67
+ trim: (_input: Uint8Array, options) =>
68
+ Effect.sync(() => {
69
+ // Mock trim: return smaller array based on duration
70
+ let duration: number;
71
+ if (options.duration !== undefined) {
72
+ duration = options.duration;
73
+ } else if (options.endTime !== undefined) {
74
+ duration = options.endTime - options.startTime;
75
+ } else {
76
+ // Assume 120s total duration
77
+ duration = 120 - options.startTime;
78
+ }
79
+
80
+ // Simulate proportional file size based on duration
81
+ const ratio = duration / 120; // Assuming 120s original
82
+ const newSize = Math.floor(_input.byteLength * ratio);
83
+ return new Uint8Array(newSize).fill(63);
84
+ }),
85
+ extractFrame: (input: Uint8Array, options) =>
86
+ Effect.sync(() => {
87
+ // Mock extractFrame: return image bytes (smaller than video)
88
+ const format = options.format || "jpeg";
89
+ // JPEG typically smaller than PNG
90
+ const mockSize = format === "png" ? 50000 : 30000;
91
+ return new Uint8Array(mockSize).fill(255);
92
+ }),
93
+ }),
94
+ );
@@ -0,0 +1,41 @@
1
+ import { Effect, Layer } from "effect";
2
+ import { ZipPlugin } from "../flow";
3
+
4
+ /**
5
+ * Mock ZipPlugin implementation for testing.
6
+ *
7
+ * Provides a simple in-memory implementation that creates mock zip data
8
+ * by serializing file metadata as JSON.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { TestZipPlugin } from "@uploadista/core/testing";
13
+ *
14
+ * const program = Effect.gen(function* () {
15
+ * const zipPlugin = yield* ZipPlugin;
16
+ * const zipData = yield* zipPlugin.zip(inputs, options);
17
+ * return zipData;
18
+ * }).pipe(Effect.provide(TestZipPlugin));
19
+ * ```
20
+ */
21
+ export const TestZipPlugin = Layer.succeed(
22
+ ZipPlugin,
23
+ ZipPlugin.of({
24
+ zip: (inputs, options) =>
25
+ Effect.gen(function* () {
26
+ // Create mock zip data
27
+ const files = inputs.map((input) => ({
28
+ id: input.id,
29
+ size: input.data.byteLength,
30
+ }));
31
+
32
+ const zipContent = JSON.stringify({
33
+ zipName: options.zipName,
34
+ includeMetadata: options.includeMetadata,
35
+ files,
36
+ });
37
+
38
+ return new TextEncoder().encode(zipContent);
39
+ }),
40
+ }),
41
+ );
@@ -158,7 +158,7 @@ export type DataStore<TData = unknown> = {
158
158
  onProgress?: (chunkSize: number) => void;
159
159
  },
160
160
  ) => Effect.Effect<number, UploadistaError>;
161
- readonly deleteExpired?: Effect.Effect<number, UploadistaError>;
161
+ readonly deleteExpired?: () => Effect.Effect<number, UploadistaError>;
162
162
  readonly getCapabilities: () => DataStoreCapabilities;
163
163
  readonly validateUploadStrategy: (
164
164
  strategy: UploadStrategy,
@@ -1,4 +1,4 @@
1
- import { Effect } from "effect";
1
+ import { Effect, Exit } from "effect";
2
2
  import { describe, expect, it } from "vitest";
3
3
  import {
4
4
  ERROR_CATALOG,
@@ -6,7 +6,7 @@ import {
6
6
  isUploadistaError,
7
7
  UploadistaError,
8
8
  type UploadistaErrorCode,
9
- } from "../uploadista-error";
9
+ } from "../../src/errors";
10
10
 
11
11
  describe("ERROR_CATALOG", () => {
12
12
  it("should contain all error codes with status and body", () => {
@@ -76,7 +76,6 @@ describe("UploadistaError", () => {
76
76
  expect(error.status).toBe(404);
77
77
  expect(error.status_code).toBe(404); // legacy alias
78
78
  expect(error.body).toBe("File not found");
79
- expect(error.message).toBe("File not found");
80
79
  expect(error.details).toEqual({ fileId: "123" });
81
80
  expect((error as { cause?: unknown }).cause).toBeInstanceOf(Error);
82
81
  });
@@ -173,12 +172,14 @@ describe("UploadistaError", () => {
173
172
  });
174
173
 
175
174
  describe("toFailure", () => {
176
- it("should convert error to failure result", () => {
175
+ it("should convert error to failure result", async () => {
177
176
  const error = UploadistaError.fromCode("FILE_NOT_FOUND");
178
- const result = error.toEffect();
177
+ const effect = error.toEffect();
179
178
 
180
- expect(Effect.isFailure(result)).toBe(true);
181
- expect(Effect.isSuccess(result)).toBe(false);
179
+ const result = await Effect.runPromiseExit(effect);
180
+
181
+ expect(Exit.isFailure(result)).toBe(true);
182
+ expect(Exit.isSuccess(result)).toBe(false);
182
183
  });
183
184
  });
184
185
  });
@@ -216,25 +217,27 @@ describe("isUploadistaError", () => {
216
217
  });
217
218
 
218
219
  describe("httpFailure", () => {
219
- it("should create failure result from error code", () => {
220
- const result = httpFailure("FILE_NOT_FOUND");
220
+ it("should create failure result from error code", async () => {
221
+ const effect = httpFailure("FILE_NOT_FOUND");
222
+ const result = await Effect.runPromiseExit(effect);
221
223
 
222
- expect(Effect.isFailure(result)).toBe(true);
223
- expect(Effect.isSuccess(result)).toBe(false);
224
+ expect(Exit.isFailure(result)).toBe(true);
225
+ expect(Exit.isSuccess(result)).toBe(false);
224
226
  });
225
227
 
226
- it("should allow overriding error properties", () => {
227
- const result = httpFailure("UNKNOWN_ERROR", {
228
+ it("should allow overriding error properties", async () => {
229
+ const effect = httpFailure("UNKNOWN_ERROR", {
228
230
  status: 503,
229
231
  body: "Service unavailable",
230
232
  details: { retryAfter: 60 },
231
233
  });
234
+ const result = await Effect.runPromiseExit(effect);
232
235
 
233
- expect(Effect.isFailure(result)).toBe(true);
234
- expect(Effect.isSuccess(result)).toBe(false);
236
+ expect(Exit.isFailure(result)).toBe(true);
237
+ expect(Exit.isSuccess(result)).toBe(false);
235
238
  });
236
239
 
237
- it("should work with all error codes", () => {
240
+ it("should work with all error codes", async () => {
238
241
  const errorCodes: UploadistaErrorCode[] = [
239
242
  "MISSING_OFFSET",
240
243
  "FLOW_NODE_ERROR",
@@ -242,9 +245,10 @@ describe("httpFailure", () => {
242
245
  ];
243
246
 
244
247
  for (const code of errorCodes) {
245
- const result = httpFailure(code);
246
- expect(Effect.isFailure(result)).toBe(true);
247
- expect(Effect.isSuccess(result)).toBe(false);
248
+ const effect = httpFailure(code);
249
+ const result = await Effect.runPromiseExit(effect);
250
+ expect(Exit.isFailure(result)).toBe(true);
251
+ expect(Exit.isSuccess(result)).toBe(false);
248
252
  }
249
253
  });
250
254
  });
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { createFlowEdge, type FlowEdge } from "./edge";
2
+ import { createFlowEdge, type FlowEdge } from "../../src/flow/edge";
3
3
 
4
4
  describe("FlowEdge", () => {
5
5
  describe("createFlowEdge", () => {