@ontrails/core 1.0.0-beta.14 → 1.0.0-beta.16

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 (314) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +73 -24
  3. package/package.json +11 -4
  4. package/src/activation-provenance.ts +111 -0
  5. package/src/activation-source-compatibility.ts +430 -0
  6. package/src/activation-source-projection.ts +221 -0
  7. package/src/activation-source.ts +91 -0
  8. package/src/blob-ref.ts +51 -0
  9. package/src/branded.ts +1 -1
  10. package/src/context.ts +28 -4
  11. package/src/contour.ts +344 -0
  12. package/src/cross-batch.ts +69 -0
  13. package/src/cross-schema.ts +36 -0
  14. package/src/detours.ts +8 -0
  15. package/src/draft.ts +60 -52
  16. package/src/error-projection.ts +51 -0
  17. package/src/errors.ts +245 -48
  18. package/src/execute.ts +1250 -40
  19. package/src/fire.ts +1169 -0
  20. package/src/index.ts +383 -49
  21. package/src/internal/fork-ctx.ts +69 -0
  22. package/src/layer-projection.ts +192 -0
  23. package/src/layer.ts +81 -0
  24. package/src/observe.ts +361 -0
  25. package/src/path-security.ts +1 -1
  26. package/src/permits.ts +12 -0
  27. package/src/redaction/patterns.ts +6 -3
  28. package/src/resilience.ts +11 -11
  29. package/src/resource-config.ts +792 -0
  30. package/src/resource.ts +175 -0
  31. package/src/result.ts +18 -0
  32. package/src/run.ts +7 -6
  33. package/src/schedule-runtime.ts +689 -0
  34. package/src/schedule.ts +324 -0
  35. package/src/serialization.ts +90 -55
  36. package/src/signal-diagnostics.ts +633 -0
  37. package/src/signal-ref.ts +87 -0
  38. package/src/signal.ts +37 -0
  39. package/src/store/accessor-protocol.ts +56 -0
  40. package/src/store/index.ts +4 -0
  41. package/src/structured-examples.ts +245 -0
  42. package/src/surface-derivation.ts +91 -0
  43. package/src/surface-filter.ts +176 -0
  44. package/src/topo.ts +710 -59
  45. package/src/tracing.ts +345 -0
  46. package/src/trail.ts +347 -33
  47. package/src/trails/derive-trail.ts +842 -0
  48. package/src/trails/index.ts +9 -0
  49. package/src/trails/ingest.ts +138 -0
  50. package/src/{internal/trails-db.ts → trails-db.ts} +50 -31
  51. package/src/transport-error-map.ts +174 -0
  52. package/src/type-utils.ts +17 -2
  53. package/src/types.ts +208 -17
  54. package/src/validate-established-topo.ts +8 -2
  55. package/src/validate-topo.ts +244 -13
  56. package/src/validation.ts +58 -1
  57. package/src/webhook.ts +276 -0
  58. package/src/workspace.ts +1 -1
  59. package/src/zod-wrappers.ts +72 -0
  60. package/.turbo/turbo-build.log +0 -1
  61. package/.turbo/turbo-lint.log +0 -3
  62. package/.turbo/turbo-typecheck.log +0 -1
  63. package/dist/adapters.d.ts +0 -39
  64. package/dist/adapters.d.ts.map +0 -1
  65. package/dist/adapters.js +0 -2
  66. package/dist/adapters.js.map +0 -1
  67. package/dist/blob-ref.d.ts +0 -20
  68. package/dist/blob-ref.d.ts.map +0 -1
  69. package/dist/blob-ref.js +0 -22
  70. package/dist/blob-ref.js.map +0 -1
  71. package/dist/branded.d.ts +0 -36
  72. package/dist/branded.d.ts.map +0 -1
  73. package/dist/branded.js +0 -89
  74. package/dist/branded.js.map +0 -1
  75. package/dist/collections.d.ts +0 -31
  76. package/dist/collections.d.ts.map +0 -1
  77. package/dist/collections.js +0 -60
  78. package/dist/collections.js.map +0 -1
  79. package/dist/context.d.ts +0 -10
  80. package/dist/context.d.ts.map +0 -1
  81. package/dist/context.js +0 -21
  82. package/dist/context.js.map +0 -1
  83. package/dist/derive.d.ts +0 -44
  84. package/dist/derive.d.ts.map +0 -1
  85. package/dist/derive.js +0 -149
  86. package/dist/derive.js.map +0 -1
  87. package/dist/dispatch.d.ts +0 -27
  88. package/dist/dispatch.d.ts.map +0 -1
  89. package/dist/dispatch.js +0 -34
  90. package/dist/dispatch.js.map +0 -1
  91. package/dist/draft.d.ts +0 -28
  92. package/dist/draft.d.ts.map +0 -1
  93. package/dist/draft.js +0 -156
  94. package/dist/draft.js.map +0 -1
  95. package/dist/errors.d.ts +0 -83
  96. package/dist/errors.d.ts.map +0 -1
  97. package/dist/errors.js +0 -142
  98. package/dist/errors.js.map +0 -1
  99. package/dist/event.d.ts +0 -8
  100. package/dist/event.d.ts.map +0 -1
  101. package/dist/event.js +0 -7
  102. package/dist/event.js.map +0 -1
  103. package/dist/execute.d.ts +0 -35
  104. package/dist/execute.d.ts.map +0 -1
  105. package/dist/execute.js +0 -90
  106. package/dist/execute.js.map +0 -1
  107. package/dist/fetch.d.ts +0 -15
  108. package/dist/fetch.d.ts.map +0 -1
  109. package/dist/fetch.js +0 -102
  110. package/dist/fetch.js.map +0 -1
  111. package/dist/gate.d.ts +0 -17
  112. package/dist/gate.d.ts.map +0 -1
  113. package/dist/gate.js +0 -21
  114. package/dist/gate.js.map +0 -1
  115. package/dist/guards.d.ts +0 -17
  116. package/dist/guards.d.ts.map +0 -1
  117. package/dist/guards.js +0 -25
  118. package/dist/guards.js.map +0 -1
  119. package/dist/health.d.ts +0 -18
  120. package/dist/health.d.ts.map +0 -1
  121. package/dist/health.js +0 -5
  122. package/dist/health.js.map +0 -1
  123. package/dist/index.d.ts +0 -48
  124. package/dist/index.d.ts.map +0 -1
  125. package/dist/index.js +0 -51
  126. package/dist/index.js.map +0 -1
  127. package/dist/internal/topo-saves.d.ts +0 -47
  128. package/dist/internal/topo-saves.d.ts.map +0 -1
  129. package/dist/internal/topo-saves.js +0 -310
  130. package/dist/internal/topo-saves.js.map +0 -1
  131. package/dist/internal/topo-store-read.d.ts +0 -67
  132. package/dist/internal/topo-store-read.d.ts.map +0 -1
  133. package/dist/internal/topo-store-read.js +0 -222
  134. package/dist/internal/topo-store-read.js.map +0 -1
  135. package/dist/internal/topo-store.d.ts +0 -12
  136. package/dist/internal/topo-store.d.ts.map +0 -1
  137. package/dist/internal/topo-store.js +0 -571
  138. package/dist/internal/topo-store.js.map +0 -1
  139. package/dist/internal/trails-db.d.ts +0 -16
  140. package/dist/internal/trails-db.d.ts.map +0 -1
  141. package/dist/internal/trails-db.js +0 -118
  142. package/dist/internal/trails-db.js.map +0 -1
  143. package/dist/job.d.ts +0 -24
  144. package/dist/job.d.ts.map +0 -1
  145. package/dist/job.js +0 -17
  146. package/dist/job.js.map +0 -1
  147. package/dist/layer.d.ts +0 -6
  148. package/dist/layer.d.ts.map +0 -1
  149. package/dist/layer.js +0 -4
  150. package/dist/layer.js.map +0 -1
  151. package/dist/path-security.d.ts +0 -28
  152. package/dist/path-security.d.ts.map +0 -1
  153. package/dist/path-security.js +0 -63
  154. package/dist/path-security.js.map +0 -1
  155. package/dist/patterns/bulk.d.ts +0 -15
  156. package/dist/patterns/bulk.d.ts.map +0 -1
  157. package/dist/patterns/bulk.js +0 -14
  158. package/dist/patterns/bulk.js.map +0 -1
  159. package/dist/patterns/change.d.ts +0 -10
  160. package/dist/patterns/change.d.ts.map +0 -1
  161. package/dist/patterns/change.js +0 -10
  162. package/dist/patterns/change.js.map +0 -1
  163. package/dist/patterns/date-range.d.ts +0 -10
  164. package/dist/patterns/date-range.d.ts.map +0 -1
  165. package/dist/patterns/date-range.js +0 -10
  166. package/dist/patterns/date-range.js.map +0 -1
  167. package/dist/patterns/index.d.ts +0 -9
  168. package/dist/patterns/index.d.ts.map +0 -1
  169. package/dist/patterns/index.js +0 -9
  170. package/dist/patterns/index.js.map +0 -1
  171. package/dist/patterns/pagination.d.ts +0 -18
  172. package/dist/patterns/pagination.d.ts.map +0 -1
  173. package/dist/patterns/pagination.js +0 -18
  174. package/dist/patterns/pagination.js.map +0 -1
  175. package/dist/patterns/progress.d.ts +0 -11
  176. package/dist/patterns/progress.d.ts.map +0 -1
  177. package/dist/patterns/progress.js +0 -11
  178. package/dist/patterns/progress.js.map +0 -1
  179. package/dist/patterns/sorting.d.ts +0 -13
  180. package/dist/patterns/sorting.d.ts.map +0 -1
  181. package/dist/patterns/sorting.js +0 -10
  182. package/dist/patterns/sorting.js.map +0 -1
  183. package/dist/patterns/status.d.ts +0 -15
  184. package/dist/patterns/status.d.ts.map +0 -1
  185. package/dist/patterns/status.js +0 -9
  186. package/dist/patterns/status.js.map +0 -1
  187. package/dist/patterns/timestamps.d.ts +0 -10
  188. package/dist/patterns/timestamps.d.ts.map +0 -1
  189. package/dist/patterns/timestamps.js +0 -10
  190. package/dist/patterns/timestamps.js.map +0 -1
  191. package/dist/provision-config.d.ts +0 -22
  192. package/dist/provision-config.d.ts.map +0 -1
  193. package/dist/provision-config.js +0 -210
  194. package/dist/provision-config.js.map +0 -1
  195. package/dist/provision.d.ts +0 -71
  196. package/dist/provision.d.ts.map +0 -1
  197. package/dist/provision.js +0 -56
  198. package/dist/provision.js.map +0 -1
  199. package/dist/redaction/index.d.ts +0 -4
  200. package/dist/redaction/index.d.ts.map +0 -1
  201. package/dist/redaction/index.js +0 -3
  202. package/dist/redaction/index.js.map +0 -1
  203. package/dist/redaction/patterns.d.ts +0 -9
  204. package/dist/redaction/patterns.d.ts.map +0 -1
  205. package/dist/redaction/patterns.js +0 -39
  206. package/dist/redaction/patterns.js.map +0 -1
  207. package/dist/redaction/redactor.d.ts +0 -27
  208. package/dist/redaction/redactor.d.ts.map +0 -1
  209. package/dist/redaction/redactor.js +0 -89
  210. package/dist/redaction/redactor.js.map +0 -1
  211. package/dist/resilience.d.ts +0 -34
  212. package/dist/resilience.d.ts.map +0 -1
  213. package/dist/resilience.js +0 -164
  214. package/dist/resilience.js.map +0 -1
  215. package/dist/result.d.ts +0 -57
  216. package/dist/result.d.ts.map +0 -1
  217. package/dist/result.js +0 -156
  218. package/dist/result.js.map +0 -1
  219. package/dist/run.d.ts +0 -27
  220. package/dist/run.d.ts.map +0 -1
  221. package/dist/run.js +0 -34
  222. package/dist/run.js.map +0 -1
  223. package/dist/serialization.d.ts +0 -27
  224. package/dist/serialization.d.ts.map +0 -1
  225. package/dist/serialization.js +0 -153
  226. package/dist/serialization.js.map +0 -1
  227. package/dist/service-config.d.ts +0 -22
  228. package/dist/service-config.d.ts.map +0 -1
  229. package/dist/service-config.js +0 -210
  230. package/dist/service-config.js.map +0 -1
  231. package/dist/service.d.ts +0 -71
  232. package/dist/service.d.ts.map +0 -1
  233. package/dist/service.js +0 -56
  234. package/dist/service.js.map +0 -1
  235. package/dist/signal.d.ts +0 -33
  236. package/dist/signal.d.ts.map +0 -1
  237. package/dist/signal.js +0 -17
  238. package/dist/signal.js.map +0 -1
  239. package/dist/topo-store.d.ts +0 -48
  240. package/dist/topo-store.d.ts.map +0 -1
  241. package/dist/topo-store.js +0 -175
  242. package/dist/topo-store.js.map +0 -1
  243. package/dist/topo.d.ts +0 -25
  244. package/dist/topo.d.ts.map +0 -1
  245. package/dist/topo.js +0 -112
  246. package/dist/topo.js.map +0 -1
  247. package/dist/trail.d.ts +0 -96
  248. package/dist/trail.d.ts.map +0 -1
  249. package/dist/trail.js +0 -20
  250. package/dist/trail.js.map +0 -1
  251. package/dist/type-utils.d.ts +0 -24
  252. package/dist/type-utils.d.ts.map +0 -1
  253. package/dist/type-utils.js +0 -12
  254. package/dist/type-utils.js.map +0 -1
  255. package/dist/types.d.ts +0 -71
  256. package/dist/types.d.ts.map +0 -1
  257. package/dist/types.js +0 -3
  258. package/dist/types.js.map +0 -1
  259. package/dist/validate-established-topo.d.ts +0 -76
  260. package/dist/validate-established-topo.d.ts.map +0 -1
  261. package/dist/validate-established-topo.js +0 -43
  262. package/dist/validate-established-topo.js.map +0 -1
  263. package/dist/validate-topo.d.ts +0 -24
  264. package/dist/validate-topo.d.ts.map +0 -1
  265. package/dist/validate-topo.js +0 -176
  266. package/dist/validate-topo.js.map +0 -1
  267. package/dist/validation.d.ts +0 -27
  268. package/dist/validation.d.ts.map +0 -1
  269. package/dist/validation.js +0 -165
  270. package/dist/validation.js.map +0 -1
  271. package/dist/workspace.d.ts +0 -25
  272. package/dist/workspace.d.ts.map +0 -1
  273. package/dist/workspace.js +0 -57
  274. package/dist/workspace.js.map +0 -1
  275. package/src/__tests__/blob-ref.test.ts +0 -103
  276. package/src/__tests__/branded.test.ts +0 -148
  277. package/src/__tests__/collections.test.ts +0 -126
  278. package/src/__tests__/context.test.ts +0 -77
  279. package/src/__tests__/derive.test.ts +0 -260
  280. package/src/__tests__/errors.test.ts +0 -309
  281. package/src/__tests__/execute.test.ts +0 -525
  282. package/src/__tests__/fetch.test.ts +0 -217
  283. package/src/__tests__/gate.test.ts +0 -125
  284. package/src/__tests__/guards.test.ts +0 -102
  285. package/src/__tests__/path-security.test.ts +0 -114
  286. package/src/__tests__/patterns.test.ts +0 -273
  287. package/src/__tests__/redaction.test.ts +0 -244
  288. package/src/__tests__/resilience.test.ts +0 -246
  289. package/src/__tests__/result.test.ts +0 -155
  290. package/src/__tests__/run.test.ts +0 -181
  291. package/src/__tests__/serialization.test.ts +0 -401
  292. package/src/__tests__/service-config.test.ts +0 -228
  293. package/src/__tests__/service.test.ts +0 -197
  294. package/src/__tests__/signal.test.ts +0 -82
  295. package/src/__tests__/topo-store-read.test.ts +0 -251
  296. package/src/__tests__/topo-store.test.ts +0 -469
  297. package/src/__tests__/topo.test.ts +0 -277
  298. package/src/__tests__/trail-permit.test.ts +0 -60
  299. package/src/__tests__/trail.test.ts +0 -261
  300. package/src/__tests__/trails-db.test.ts +0 -191
  301. package/src/__tests__/type-utils.test.ts +0 -90
  302. package/src/__tests__/validate-topo.test.ts +0 -489
  303. package/src/__tests__/validation.test.ts +0 -336
  304. package/src/__tests__/workspace.test.ts +0 -183
  305. package/src/event.ts +0 -15
  306. package/src/gate.ts +0 -44
  307. package/src/internal/topo-saves.ts +0 -429
  308. package/src/internal/topo-store-read.ts +0 -473
  309. package/src/internal/topo-store.ts +0 -1087
  310. package/src/provision-config.ts +0 -373
  311. package/src/provision.ts +0 -148
  312. package/src/topo-store.ts +0 -301
  313. package/tsconfig.json +0 -9
  314. package/tsconfig.tsbuildinfo +0 -1
@@ -1,217 +0,0 @@
1
- /* oxlint-disable require-await -- test mocks satisfy async interface without awaiting */
2
- import { describe, test, expect, afterEach } from 'bun:test';
3
-
4
- import {
5
- AuthError,
6
- CancelledError,
7
- InternalError,
8
- NetworkError,
9
- NotFoundError,
10
- PermissionError,
11
- ValidationError,
12
- RateLimitError,
13
- TimeoutError,
14
- } from '../errors.js';
15
- import { Result } from '../result.js';
16
-
17
- // ---------------------------------------------------------------------------
18
- // Mock fetch
19
- // ---------------------------------------------------------------------------
20
-
21
- const originalFetch = globalThis.fetch;
22
-
23
- const mockFetch = (impl: () => Promise<Response>) => {
24
- globalThis.fetch = impl as unknown as typeof globalThis.fetch;
25
- };
26
-
27
- afterEach(() => {
28
- globalThis.fetch = originalFetch;
29
- });
30
-
31
- const fakeResponse = (
32
- status: number,
33
- options?: { headers?: Record<string, string> }
34
- ): Response => {
35
- const init: ResponseInit = { status };
36
- if (options?.headers) {
37
- init.headers = options.headers;
38
- }
39
- return new Response(null, init);
40
- };
41
-
42
- // ---------------------------------------------------------------------------
43
- // Successful responses
44
- // ---------------------------------------------------------------------------
45
-
46
- describe('Result.fromFetch — success', () => {
47
- test('returns Ok with Response for 200', async () => {
48
- mockFetch(async () => fakeResponse(200));
49
- const result = await Result.fromFetch('https://example.com/api');
50
- expect(result.isOk()).toBe(true);
51
- expect(result.unwrap().status).toBe(200);
52
- });
53
-
54
- test('returns Ok for 201', async () => {
55
- mockFetch(async () => fakeResponse(201));
56
- const result = await Result.fromFetch('https://example.com/api');
57
- expect(result.isOk()).toBe(true);
58
- });
59
-
60
- test('returns Ok for 204', async () => {
61
- mockFetch(async () => fakeResponse(204));
62
- const result = await Result.fromFetch('https://example.com/api');
63
- expect(result.isOk()).toBe(true);
64
- });
65
- });
66
-
67
- // ---------------------------------------------------------------------------
68
- // HTTP error status mapping
69
- // ---------------------------------------------------------------------------
70
-
71
- describe('Result.fromFetch — status code mapping', () => {
72
- test('401 → AuthError', async () => {
73
- mockFetch(async () => fakeResponse(401));
74
- const result = await Result.fromFetch('https://example.com/api');
75
- expect(result.isErr()).toBe(true);
76
- expect((result as unknown as { error: Error }).error).toBeInstanceOf(
77
- AuthError
78
- );
79
- });
80
-
81
- test('403 → PermissionError', async () => {
82
- mockFetch(async () => fakeResponse(403));
83
- const result = await Result.fromFetch('https://example.com/api');
84
- expect(result.isErr()).toBe(true);
85
- expect((result as unknown as { error: Error }).error).toBeInstanceOf(
86
- PermissionError
87
- );
88
- });
89
-
90
- test('404 → NotFoundError', async () => {
91
- mockFetch(async () => fakeResponse(404));
92
- const result = await Result.fromFetch('https://example.com/api');
93
- expect(result.isErr()).toBe(true);
94
- expect((result as unknown as { error: Error }).error).toBeInstanceOf(
95
- NotFoundError
96
- );
97
- });
98
-
99
- test('429 → RateLimitError', async () => {
100
- mockFetch(async () => fakeResponse(429));
101
- const result = await Result.fromFetch('https://example.com/api');
102
- expect(result.isErr()).toBe(true);
103
- expect((result as unknown as { error: Error }).error).toBeInstanceOf(
104
- RateLimitError
105
- );
106
- });
107
-
108
- test('429 with retry-after header', async () => {
109
- mockFetch(async () =>
110
- fakeResponse(429, { headers: { 'retry-after': '30' } })
111
- );
112
- const result = await Result.fromFetch('https://example.com/api');
113
- expect(result.isErr()).toBe(true);
114
- const err = (result as unknown as { error: RateLimitError }).error;
115
- expect(err).toBeInstanceOf(RateLimitError);
116
- expect(err.retryAfter).toBe(30);
117
- });
118
-
119
- test('500 → InternalError', async () => {
120
- mockFetch(async () => fakeResponse(500));
121
- const result = await Result.fromFetch('https://example.com/api');
122
- expect(result.isErr()).toBe(true);
123
- expect((result as unknown as { error: Error }).error).toBeInstanceOf(
124
- InternalError
125
- );
126
- });
127
-
128
- test('502 → NetworkError', async () => {
129
- mockFetch(async () => fakeResponse(502));
130
- const result = await Result.fromFetch('https://example.com/api');
131
- expect(result.isErr()).toBe(true);
132
- expect((result as unknown as { error: Error }).error).toBeInstanceOf(
133
- NetworkError
134
- );
135
- });
136
-
137
- test('504 → TimeoutError', async () => {
138
- mockFetch(async () => fakeResponse(504));
139
- const result = await Result.fromFetch('https://example.com/api');
140
- expect(result.isErr()).toBe(true);
141
- expect((result as unknown as { error: Error }).error).toBeInstanceOf(
142
- TimeoutError
143
- );
144
- });
145
-
146
- test('503 → InternalError (generic 5xx)', async () => {
147
- mockFetch(async () => fakeResponse(503));
148
- const result = await Result.fromFetch('https://example.com/api');
149
- expect(result.isErr()).toBe(true);
150
- expect((result as unknown as { error: Error }).error).toBeInstanceOf(
151
- InternalError
152
- );
153
- });
154
-
155
- test('400 → ValidationError', async () => {
156
- mockFetch(async () => fakeResponse(400));
157
- const result = await Result.fromFetch('https://example.com/api');
158
- expect(result.isErr()).toBe(true);
159
- expect((result as unknown as { error: Error }).error).toBeInstanceOf(
160
- ValidationError
161
- );
162
- });
163
- });
164
-
165
- // ---------------------------------------------------------------------------
166
- // Network / abort errors
167
- // ---------------------------------------------------------------------------
168
-
169
- describe('Result.fromFetch — network errors', () => {
170
- test('TypeError → NetworkError', async () => {
171
- mockFetch(async () => {
172
- throw new TypeError('Failed to fetch');
173
- });
174
- const result = await Result.fromFetch('https://example.com/api');
175
- expect(result.isErr()).toBe(true);
176
- expect((result as unknown as { error: Error }).error).toBeInstanceOf(
177
- NetworkError
178
- );
179
- });
180
-
181
- test('AbortError → CancelledError', async () => {
182
- mockFetch(async () => {
183
- throw new DOMException('The operation was aborted', 'AbortError');
184
- });
185
- const result = await Result.fromFetch('https://example.com/api');
186
- expect(result.isErr()).toBe(true);
187
- expect((result as unknown as { error: Error }).error).toBeInstanceOf(
188
- CancelledError
189
- );
190
- });
191
-
192
- test('unknown thrown value → NetworkError', async () => {
193
- mockFetch(async () => {
194
- // oxlint-disable-next-line no-throw-literal -- intentionally testing non-Error rejection handling
195
- throw 'string error';
196
- });
197
- const result = await Result.fromFetch('https://example.com/api');
198
- expect(result.isErr()).toBe(true);
199
- expect((result as unknown as { error: Error }).error).toBeInstanceOf(
200
- NetworkError
201
- );
202
- });
203
- });
204
-
205
- // ---------------------------------------------------------------------------
206
- // Error context
207
- // ---------------------------------------------------------------------------
208
-
209
- describe('Result.fromFetch — error context', () => {
210
- test('includes status in error context', async () => {
211
- mockFetch(async () => fakeResponse(404));
212
- const result = await Result.fromFetch('https://example.com/api');
213
- expect(result.isErr()).toBe(true);
214
- const err = (result as unknown as { error: NotFoundError }).error;
215
- expect(err.context?.['status']).toBe(404);
216
- });
217
- });
@@ -1,125 +0,0 @@
1
- /* oxlint-disable require-await -- gate wrappers satisfy async interfaces without awaiting */
2
- import { describe, test, expect } from 'bun:test';
3
-
4
- import { z } from 'zod';
5
-
6
- import { createTrailContext } from '../context';
7
- import { composeGates } from '../gate';
8
- import type { Gate } from '../gate';
9
- import { Result } from '../result';
10
- import { trail } from '../trail';
11
- import type { TrailContext } from '../types';
12
-
13
- const stubCtx: TrailContext = createTrailContext({
14
- abortSignal: AbortSignal.timeout(5000),
15
- requestId: 'test-gate',
16
- });
17
-
18
- const echoTrail = trail('echo', {
19
- blaze: (input) => Result.ok({ value: input.value }),
20
- input: z.object({ value: z.string() }),
21
- meta: { domain: 'test' },
22
- output: z.object({ value: z.string() }),
23
- });
24
-
25
- // ---------------------------------------------------------------------------
26
- // Gate tests
27
- // ---------------------------------------------------------------------------
28
-
29
- describe('Gate', () => {
30
- test('single gate wraps implementation', async () => {
31
- const prefixGate: Gate = {
32
- name: 'prefix',
33
- wrap(_trail, impl) {
34
- return async (input, ctx) => {
35
- const result = await impl(input, ctx);
36
- return result.map((out) => ({
37
- ...out,
38
- value: `prefixed:${(out as { value: string }).value}`,
39
- }));
40
- };
41
- },
42
- };
43
-
44
- const wrapped = composeGates([prefixGate], echoTrail, echoTrail.blaze);
45
- const result = await wrapped({ value: 'hello' }, stubCtx);
46
-
47
- expect(result.isOk()).toBe(true);
48
- expect(result.unwrap()).toEqual({ value: 'prefixed:hello' });
49
- });
50
-
51
- test('multiple gates compose in outermost-first order', async () => {
52
- const log: string[] = [];
53
-
54
- const outer: Gate = {
55
- name: 'outer',
56
- wrap(_trail, impl) {
57
- return async (input, ctx) => {
58
- log.push('outer:before');
59
- const r = await impl(input, ctx);
60
- log.push('outer:after');
61
- return r;
62
- };
63
- },
64
- };
65
-
66
- const inner: Gate = {
67
- name: 'inner',
68
- wrap(_trail, impl) {
69
- return async (input, ctx) => {
70
- log.push('inner:before');
71
- const r = await impl(input, ctx);
72
- log.push('inner:after');
73
- return r;
74
- };
75
- },
76
- };
77
-
78
- const wrapped = composeGates([outer, inner], echoTrail, echoTrail.blaze);
79
- await wrapped({ value: 'x' }, stubCtx);
80
-
81
- expect(log).toEqual([
82
- 'outer:before',
83
- 'inner:before',
84
- 'inner:after',
85
- 'outer:after',
86
- ]);
87
- });
88
-
89
- test('gate can short-circuit without calling inner implementation', async () => {
90
- const shortCircuit: Gate = {
91
- name: 'short-circuit',
92
- wrap() {
93
- return async () => Result.err(new Error('blocked'));
94
- },
95
- };
96
-
97
- const wrapped = composeGates([shortCircuit], echoTrail, echoTrail.blaze);
98
- const result = await wrapped({ value: 'hello' }, stubCtx);
99
-
100
- expect(result.isErr()).toBe(true);
101
- const err = result as unknown as { error: Error };
102
- expect(err.error.message).toBe('blocked');
103
- });
104
-
105
- test('gate can inspect trail meta', () => {
106
- let capturedDomain: unknown;
107
-
108
- const inspectGate: Gate = {
109
- name: 'inspect',
110
- wrap(t, impl) {
111
- capturedDomain = t.meta?.['domain'];
112
- return impl;
113
- },
114
- };
115
-
116
- composeGates([inspectGate], echoTrail, echoTrail.blaze);
117
-
118
- expect(capturedDomain).toBe('test');
119
- });
120
-
121
- test('empty gates array returns implementation unchanged', () => {
122
- const wrapped = composeGates([], echoTrail, echoTrail.blaze);
123
- expect(wrapped).toBe(echoTrail.blaze);
124
- });
125
- });
@@ -1,102 +0,0 @@
1
- import { describe, test, expect } from 'bun:test';
2
-
3
- import {
4
- isDefined,
5
- isNonEmptyString,
6
- isPlainObject,
7
- hasProperty,
8
- assertNever,
9
- } from '../guards';
10
-
11
- describe('guards', () => {
12
- describe('isDefined()', () => {
13
- test('returns true for non-nullish values', () => {
14
- expect(isDefined(0)).toBe(true);
15
- expect(isDefined('')).toBe(true);
16
- expect(isDefined(false)).toBe(true);
17
- expect(isDefined([])).toBe(true);
18
- });
19
-
20
- test('returns false for null', () => {
21
- expect(isDefined(null)).toBe(false);
22
- });
23
-
24
- test('returns false for undefined', () => {
25
- expect(isDefined()).toBe(false);
26
- });
27
- });
28
-
29
- describe('isNonEmptyString()', () => {
30
- test('returns true for non-empty strings', () => {
31
- expect(isNonEmptyString('hello')).toBe(true);
32
- expect(isNonEmptyString(' ')).toBe(true);
33
- });
34
-
35
- test('returns false for empty string', () => {
36
- expect(isNonEmptyString('')).toBe(false);
37
- });
38
-
39
- test('returns false for non-string types', () => {
40
- expect(isNonEmptyString(42)).toBe(false);
41
- expect(isNonEmptyString(null)).toBe(false);
42
- expect(isNonEmptyString()).toBe(false);
43
- expect(isNonEmptyString([])).toBe(false);
44
- });
45
- });
46
-
47
- describe('isPlainObject()', () => {
48
- test('returns true for plain objects', () => {
49
- expect(isPlainObject({})).toBe(true);
50
- expect(isPlainObject({ a: 1 })).toBe(true);
51
- expect(isPlainObject(Object.create(null))).toBe(true);
52
- });
53
-
54
- test('returns false for arrays', () => {
55
- expect(isPlainObject([])).toBe(false);
56
- });
57
-
58
- test('returns false for class instances', () => {
59
- expect(isPlainObject(new Date())).toBe(false);
60
- expect(isPlainObject(new Map())).toBe(false);
61
- });
62
-
63
- test('returns false for null', () => {
64
- expect(isPlainObject(null)).toBe(false);
65
- });
66
-
67
- test('returns false for primitives', () => {
68
- expect(isPlainObject('string')).toBe(false);
69
- expect(isPlainObject(42)).toBe(false);
70
- });
71
- });
72
-
73
- describe('hasProperty()', () => {
74
- test('returns true when key exists', () => {
75
- expect(hasProperty({ name: 'test' }, 'name')).toBe(true);
76
- });
77
-
78
- test('returns true for keys with undefined values', () => {
79
- expect(hasProperty({ x: undefined }, 'x')).toBe(true);
80
- });
81
-
82
- test('returns false when key is missing', () => {
83
- expect(hasProperty({}, 'missing')).toBe(false);
84
- });
85
-
86
- test('returns false for null', () => {
87
- expect(hasProperty(null, 'key')).toBe(false);
88
- });
89
-
90
- test('returns false for primitives', () => {
91
- expect(hasProperty(42, 'key')).toBe(false);
92
- });
93
- });
94
-
95
- describe('assertNever()', () => {
96
- test('throws with a descriptive message', () => {
97
- expect(() => assertNever('unexpected' as never)).toThrow(
98
- 'Unexpected value: unexpected'
99
- );
100
- });
101
- });
102
- });
@@ -1,114 +0,0 @@
1
- import { describe, test, expect } from 'bun:test';
2
- import { resolve, join } from 'node:path';
3
-
4
- import { PermissionError } from '../errors.js';
5
- import { securePath, isPathSafe, resolveSafePath } from '../path-security.js';
6
-
7
- // ---------------------------------------------------------------------------
8
- // securePath
9
- // ---------------------------------------------------------------------------
10
-
11
- describe('securePath', () => {
12
- const base = '/project/workspace';
13
-
14
- test('resolves a simple relative path', () => {
15
- const result = securePath(base, 'src/index.ts');
16
- expect(result.isOk()).toBe(true);
17
- expect(result.unwrap()).toBe(resolve(base, 'src/index.ts'));
18
- });
19
-
20
- test('resolves nested paths', () => {
21
- const result = securePath(base, 'src/../lib/utils.ts');
22
- expect(result.isOk()).toBe(true);
23
- expect(result.unwrap()).toBe(resolve(base, 'lib/utils.ts'));
24
- });
25
-
26
- test('accepts the base directory itself', () => {
27
- const result = securePath(base, '.');
28
- expect(result.isOk()).toBe(true);
29
- expect(result.unwrap()).toBe(resolve(base));
30
- });
31
-
32
- test('rejects path traversal with ..', () => {
33
- const result = securePath(base, '../../etc/passwd');
34
- expect(result.isErr()).toBe(true);
35
- const err = result as unknown as { error: PermissionError };
36
- expect(err.error).toBeInstanceOf(PermissionError);
37
- expect(err.error.message).toContain('Path traversal detected');
38
- });
39
-
40
- test('rejects absolute paths outside base', () => {
41
- const result = securePath(base, '/etc/passwd');
42
- expect(result.isErr()).toBe(true);
43
- const err = result as unknown as { error: PermissionError };
44
- expect(err.error).toBeInstanceOf(PermissionError);
45
- });
46
-
47
- test('rejects sneaky traversal (subdir/../../..)', () => {
48
- const result = securePath(base, 'src/../../..');
49
- expect(result.isErr()).toBe(true);
50
- });
51
-
52
- test('allows paths that mention .. but stay inside', () => {
53
- const result = securePath(base, 'src/../src/file.ts');
54
- expect(result.isOk()).toBe(true);
55
- expect(result.unwrap()).toBe(resolve(base, 'src/file.ts'));
56
- });
57
- });
58
-
59
- // ---------------------------------------------------------------------------
60
- // isPathSafe
61
- // ---------------------------------------------------------------------------
62
-
63
- describe('isPathSafe', () => {
64
- const base = '/project/workspace';
65
-
66
- test('returns true for child paths', () => {
67
- expect(isPathSafe(base, 'src/index.ts')).toBe(true);
68
- });
69
-
70
- test('returns true for the base directory', () => {
71
- expect(isPathSafe(base, '.')).toBe(true);
72
- });
73
-
74
- test('returns false for traversal', () => {
75
- expect(isPathSafe(base, '../secret')).toBe(false);
76
- });
77
-
78
- test('returns false for absolute paths outside base', () => {
79
- expect(isPathSafe(base, '/tmp/evil')).toBe(false);
80
- });
81
- });
82
-
83
- // ---------------------------------------------------------------------------
84
- // resolveSafePath
85
- // ---------------------------------------------------------------------------
86
-
87
- describe('resolveSafePath', () => {
88
- const base = '/project/workspace';
89
-
90
- test('joins multiple segments safely', () => {
91
- const result = resolveSafePath(base, 'src', 'lib', 'utils.ts');
92
- expect(result.isOk()).toBe(true);
93
- expect(result.unwrap()).toBe(join(resolve(base), 'src', 'lib', 'utils.ts'));
94
- });
95
-
96
- test('rejects when segments escape the base', () => {
97
- const result = resolveSafePath(base, 'src', '../../../etc');
98
- expect(result.isErr()).toBe(true);
99
- const err = result as unknown as { error: PermissionError };
100
- expect(err.error).toBeInstanceOf(PermissionError);
101
- });
102
-
103
- test('handles single segment', () => {
104
- const result = resolveSafePath(base, 'file.txt');
105
- expect(result.isOk()).toBe(true);
106
- expect(result.unwrap()).toBe(resolve(base, 'file.txt'));
107
- });
108
-
109
- test('handles empty segments', () => {
110
- const result = resolveSafePath(base);
111
- expect(result.isOk()).toBe(true);
112
- expect(result.unwrap()).toBe(resolve(base));
113
- });
114
- });