@ontrails/core 1.0.0-beta.15 → 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 (348) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +61 -16
  3. package/package.json +9 -8
  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/context.ts +4 -0
  10. package/src/{internal/cross-batch.ts → cross-batch.ts} +3 -11
  11. package/src/detours.ts +8 -0
  12. package/src/draft.ts +4 -0
  13. package/src/error-projection.ts +51 -0
  14. package/src/errors.ts +192 -39
  15. package/src/execute.ts +443 -44
  16. package/src/fire.ts +965 -91
  17. package/src/index.ts +285 -38
  18. package/src/layer-projection.ts +192 -0
  19. package/src/layer.ts +32 -1
  20. package/src/observe.ts +361 -0
  21. package/src/permits.ts +12 -0
  22. package/src/redaction/patterns.ts +6 -3
  23. package/src/resilience.ts +8 -8
  24. package/src/resource-config.ts +477 -44
  25. package/src/resource.ts +21 -1
  26. package/src/result.ts +18 -0
  27. package/src/run.ts +2 -2
  28. package/src/schedule-runtime.ts +689 -0
  29. package/src/schedule.ts +324 -0
  30. package/src/serialization.ts +90 -57
  31. package/src/signal-diagnostics.ts +633 -0
  32. package/src/{internal/signal-ref.ts → signal-ref.ts} +1 -1
  33. package/src/signal.ts +37 -0
  34. package/src/store/accessor-protocol.ts +5 -5
  35. package/src/structured-examples.ts +245 -0
  36. package/src/surface-derivation.ts +91 -0
  37. package/src/surface-filter.ts +7 -13
  38. package/src/topo.ts +386 -6
  39. package/src/tracing.ts +345 -0
  40. package/src/trail.ts +168 -22
  41. package/src/trails/derive-trail.ts +1 -1
  42. package/src/trails/ingest.ts +7 -15
  43. package/src/{internal/trails-db.ts → trails-db.ts} +42 -21
  44. package/src/transport-error-map.ts +146 -28
  45. package/src/types.ts +82 -28
  46. package/src/validate-established-topo.ts +6 -0
  47. package/src/validate-topo.ts +212 -5
  48. package/src/validation.ts +58 -1
  49. package/src/webhook.ts +276 -0
  50. package/src/{internal/zod-wrappers.ts → zod-wrappers.ts} +0 -6
  51. package/.turbo/turbo-build.log +0 -1
  52. package/.turbo/turbo-lint.log +0 -3
  53. package/.turbo/turbo-typecheck.log +0 -1
  54. package/dist/blob-ref.d.ts +0 -20
  55. package/dist/blob-ref.d.ts.map +0 -1
  56. package/dist/blob-ref.js +0 -22
  57. package/dist/blob-ref.js.map +0 -1
  58. package/dist/branded.d.ts +0 -36
  59. package/dist/branded.d.ts.map +0 -1
  60. package/dist/branded.js +0 -89
  61. package/dist/branded.js.map +0 -1
  62. package/dist/collections.d.ts +0 -31
  63. package/dist/collections.d.ts.map +0 -1
  64. package/dist/collections.js +0 -60
  65. package/dist/collections.js.map +0 -1
  66. package/dist/context.d.ts +0 -22
  67. package/dist/context.d.ts.map +0 -1
  68. package/dist/context.js +0 -37
  69. package/dist/context.js.map +0 -1
  70. package/dist/contour.d.ts +0 -81
  71. package/dist/contour.d.ts.map +0 -1
  72. package/dist/contour.js +0 -176
  73. package/dist/contour.js.map +0 -1
  74. package/dist/cross-schema.d.ts +0 -18
  75. package/dist/cross-schema.d.ts.map +0 -1
  76. package/dist/cross-schema.js +0 -29
  77. package/dist/cross-schema.js.map +0 -1
  78. package/dist/derive.d.ts +0 -44
  79. package/dist/derive.d.ts.map +0 -1
  80. package/dist/derive.js +0 -149
  81. package/dist/derive.js.map +0 -1
  82. package/dist/draft.d.ts +0 -28
  83. package/dist/draft.d.ts.map +0 -1
  84. package/dist/draft.js +0 -157
  85. package/dist/draft.js.map +0 -1
  86. package/dist/errors.d.ts +0 -141
  87. package/dist/errors.d.ts.map +0 -1
  88. package/dist/errors.js +0 -185
  89. package/dist/errors.js.map +0 -1
  90. package/dist/execute.d.ts +0 -52
  91. package/dist/execute.d.ts.map +0 -1
  92. package/dist/execute.js +0 -511
  93. package/dist/execute.js.map +0 -1
  94. package/dist/fetch.d.ts +0 -15
  95. package/dist/fetch.d.ts.map +0 -1
  96. package/dist/fetch.js +0 -102
  97. package/dist/fetch.js.map +0 -1
  98. package/dist/fire.d.ts +0 -43
  99. package/dist/fire.d.ts.map +0 -1
  100. package/dist/fire.js +0 -185
  101. package/dist/fire.js.map +0 -1
  102. package/dist/guards.d.ts +0 -17
  103. package/dist/guards.d.ts.map +0 -1
  104. package/dist/guards.js +0 -25
  105. package/dist/guards.js.map +0 -1
  106. package/dist/index.d.ts +0 -59
  107. package/dist/index.d.ts.map +0 -1
  108. package/dist/index.js +0 -61
  109. package/dist/index.js.map +0 -1
  110. package/dist/internal/cross-batch.d.ts +0 -44
  111. package/dist/internal/cross-batch.d.ts.map +0 -1
  112. package/dist/internal/cross-batch.js +0 -58
  113. package/dist/internal/cross-batch.js.map +0 -1
  114. package/dist/internal/fork-ctx.d.ts +0 -41
  115. package/dist/internal/fork-ctx.d.ts.map +0 -1
  116. package/dist/internal/fork-ctx.js +0 -47
  117. package/dist/internal/fork-ctx.js.map +0 -1
  118. package/dist/internal/signal-ref.d.ts +0 -15
  119. package/dist/internal/signal-ref.d.ts.map +0 -1
  120. package/dist/internal/signal-ref.js +0 -41
  121. package/dist/internal/signal-ref.js.map +0 -1
  122. package/dist/internal/topo-saves.d.ts +0 -53
  123. package/dist/internal/topo-saves.d.ts.map +0 -1
  124. package/dist/internal/topo-saves.js +0 -449
  125. package/dist/internal/topo-saves.js.map +0 -1
  126. package/dist/internal/topo-snapshots.d.ts +0 -57
  127. package/dist/internal/topo-snapshots.d.ts.map +0 -1
  128. package/dist/internal/topo-snapshots.js +0 -372
  129. package/dist/internal/topo-snapshots.js.map +0 -1
  130. package/dist/internal/topo-store-read.d.ts +0 -70
  131. package/dist/internal/topo-store-read.d.ts.map +0 -1
  132. package/dist/internal/topo-store-read.js +0 -221
  133. package/dist/internal/topo-store-read.js.map +0 -1
  134. package/dist/internal/topo-store.d.ts +0 -26
  135. package/dist/internal/topo-store.d.ts.map +0 -1
  136. package/dist/internal/topo-store.js +0 -621
  137. package/dist/internal/topo-store.js.map +0 -1
  138. package/dist/internal/tracing.d.ts +0 -98
  139. package/dist/internal/tracing.d.ts.map +0 -1
  140. package/dist/internal/tracing.js +0 -102
  141. package/dist/internal/tracing.js.map +0 -1
  142. package/dist/internal/trails-db.d.ts +0 -16
  143. package/dist/internal/trails-db.d.ts.map +0 -1
  144. package/dist/internal/trails-db.js +0 -118
  145. package/dist/internal/trails-db.js.map +0 -1
  146. package/dist/internal/zod-wrappers.d.ts +0 -40
  147. package/dist/internal/zod-wrappers.d.ts.map +0 -1
  148. package/dist/internal/zod-wrappers.js +0 -59
  149. package/dist/internal/zod-wrappers.js.map +0 -1
  150. package/dist/layer.d.ts +0 -23
  151. package/dist/layer.d.ts.map +0 -1
  152. package/dist/layer.js +0 -21
  153. package/dist/layer.js.map +0 -1
  154. package/dist/path-security.d.ts +0 -28
  155. package/dist/path-security.d.ts.map +0 -1
  156. package/dist/path-security.js +0 -63
  157. package/dist/path-security.js.map +0 -1
  158. package/dist/patterns/bulk.d.ts +0 -15
  159. package/dist/patterns/bulk.d.ts.map +0 -1
  160. package/dist/patterns/bulk.js +0 -14
  161. package/dist/patterns/bulk.js.map +0 -1
  162. package/dist/patterns/change.d.ts +0 -10
  163. package/dist/patterns/change.d.ts.map +0 -1
  164. package/dist/patterns/change.js +0 -10
  165. package/dist/patterns/change.js.map +0 -1
  166. package/dist/patterns/date-range.d.ts +0 -10
  167. package/dist/patterns/date-range.d.ts.map +0 -1
  168. package/dist/patterns/date-range.js +0 -10
  169. package/dist/patterns/date-range.js.map +0 -1
  170. package/dist/patterns/index.d.ts +0 -9
  171. package/dist/patterns/index.d.ts.map +0 -1
  172. package/dist/patterns/index.js +0 -9
  173. package/dist/patterns/index.js.map +0 -1
  174. package/dist/patterns/pagination.d.ts +0 -18
  175. package/dist/patterns/pagination.d.ts.map +0 -1
  176. package/dist/patterns/pagination.js +0 -18
  177. package/dist/patterns/pagination.js.map +0 -1
  178. package/dist/patterns/progress.d.ts +0 -11
  179. package/dist/patterns/progress.d.ts.map +0 -1
  180. package/dist/patterns/progress.js +0 -11
  181. package/dist/patterns/progress.js.map +0 -1
  182. package/dist/patterns/sorting.d.ts +0 -13
  183. package/dist/patterns/sorting.d.ts.map +0 -1
  184. package/dist/patterns/sorting.js +0 -10
  185. package/dist/patterns/sorting.js.map +0 -1
  186. package/dist/patterns/status.d.ts +0 -15
  187. package/dist/patterns/status.d.ts.map +0 -1
  188. package/dist/patterns/status.js +0 -9
  189. package/dist/patterns/status.js.map +0 -1
  190. package/dist/patterns/timestamps.d.ts +0 -10
  191. package/dist/patterns/timestamps.d.ts.map +0 -1
  192. package/dist/patterns/timestamps.js +0 -10
  193. package/dist/patterns/timestamps.js.map +0 -1
  194. package/dist/redaction/index.d.ts +0 -4
  195. package/dist/redaction/index.d.ts.map +0 -1
  196. package/dist/redaction/index.js +0 -3
  197. package/dist/redaction/index.js.map +0 -1
  198. package/dist/redaction/patterns.d.ts +0 -9
  199. package/dist/redaction/patterns.d.ts.map +0 -1
  200. package/dist/redaction/patterns.js +0 -39
  201. package/dist/redaction/patterns.js.map +0 -1
  202. package/dist/redaction/redactor.d.ts +0 -27
  203. package/dist/redaction/redactor.d.ts.map +0 -1
  204. package/dist/redaction/redactor.js +0 -89
  205. package/dist/redaction/redactor.js.map +0 -1
  206. package/dist/resilience.d.ts +0 -34
  207. package/dist/resilience.d.ts.map +0 -1
  208. package/dist/resilience.js +0 -164
  209. package/dist/resilience.js.map +0 -1
  210. package/dist/resource-config.d.ts +0 -22
  211. package/dist/resource-config.d.ts.map +0 -1
  212. package/dist/resource-config.js +0 -210
  213. package/dist/resource-config.js.map +0 -1
  214. package/dist/resource.d.ts +0 -74
  215. package/dist/resource.d.ts.map +0 -1
  216. package/dist/resource.js +0 -61
  217. package/dist/resource.js.map +0 -1
  218. package/dist/result.d.ts +0 -57
  219. package/dist/result.d.ts.map +0 -1
  220. package/dist/result.js +0 -156
  221. package/dist/result.js.map +0 -1
  222. package/dist/run.d.ts +0 -28
  223. package/dist/run.d.ts.map +0 -1
  224. package/dist/run.js +0 -35
  225. package/dist/run.js.map +0 -1
  226. package/dist/serialization.d.ts +0 -27
  227. package/dist/serialization.d.ts.map +0 -1
  228. package/dist/serialization.js +0 -154
  229. package/dist/serialization.js.map +0 -1
  230. package/dist/signal.d.ts +0 -33
  231. package/dist/signal.d.ts.map +0 -1
  232. package/dist/signal.js +0 -17
  233. package/dist/signal.js.map +0 -1
  234. package/dist/store/accessor-protocol.d.ts +0 -51
  235. package/dist/store/accessor-protocol.d.ts.map +0 -1
  236. package/dist/store/accessor-protocol.js +0 -2
  237. package/dist/store/accessor-protocol.js.map +0 -1
  238. package/dist/store/index.d.ts +0 -2
  239. package/dist/store/index.d.ts.map +0 -1
  240. package/dist/store/index.js +0 -2
  241. package/dist/store/index.js.map +0 -1
  242. package/dist/surface-filter.d.ts +0 -13
  243. package/dist/surface-filter.d.ts.map +0 -1
  244. package/dist/surface-filter.js +0 -76
  245. package/dist/surface-filter.js.map +0 -1
  246. package/dist/topo-store.d.ts +0 -62
  247. package/dist/topo-store.d.ts.map +0 -1
  248. package/dist/topo-store.js +0 -319
  249. package/dist/topo-store.js.map +0 -1
  250. package/dist/topo.d.ts +0 -39
  251. package/dist/topo.d.ts.map +0 -1
  252. package/dist/topo.js +0 -241
  253. package/dist/topo.js.map +0 -1
  254. package/dist/trail.d.ts +0 -163
  255. package/dist/trail.d.ts.map +0 -1
  256. package/dist/trail.js +0 -75
  257. package/dist/trail.js.map +0 -1
  258. package/dist/trails/derive-trail.d.ts +0 -66
  259. package/dist/trails/derive-trail.d.ts.map +0 -1
  260. package/dist/trails/derive-trail.js +0 -378
  261. package/dist/trails/derive-trail.js.map +0 -1
  262. package/dist/trails/index.d.ts +0 -5
  263. package/dist/trails/index.d.ts.map +0 -1
  264. package/dist/trails/index.js +0 -3
  265. package/dist/trails/index.js.map +0 -1
  266. package/dist/trails/ingest.d.ts +0 -24
  267. package/dist/trails/ingest.d.ts.map +0 -1
  268. package/dist/trails/ingest.js +0 -63
  269. package/dist/trails/ingest.js.map +0 -1
  270. package/dist/transport-error-map.d.ts +0 -106
  271. package/dist/transport-error-map.d.ts.map +0 -1
  272. package/dist/transport-error-map.js +0 -24
  273. package/dist/transport-error-map.js.map +0 -1
  274. package/dist/type-checks.test-d.d.ts +0 -71
  275. package/dist/type-checks.test-d.d.ts.map +0 -1
  276. package/dist/type-checks.test-d.js +0 -12
  277. package/dist/type-checks.test-d.js.map +0 -1
  278. package/dist/type-utils.d.ts +0 -33
  279. package/dist/type-utils.d.ts.map +0 -1
  280. package/dist/type-utils.js +0 -12
  281. package/dist/type-utils.js.map +0 -1
  282. package/dist/types.d.ts +0 -177
  283. package/dist/types.d.ts.map +0 -1
  284. package/dist/types.js +0 -3
  285. package/dist/types.js.map +0 -1
  286. package/dist/validate-established-topo.d.ts +0 -76
  287. package/dist/validate-established-topo.d.ts.map +0 -1
  288. package/dist/validate-established-topo.js +0 -43
  289. package/dist/validate-established-topo.js.map +0 -1
  290. package/dist/validate-topo.d.ts +0 -24
  291. package/dist/validate-topo.d.ts.map +0 -1
  292. package/dist/validate-topo.js +0 -193
  293. package/dist/validate-topo.js.map +0 -1
  294. package/dist/validation.d.ts +0 -27
  295. package/dist/validation.d.ts.map +0 -1
  296. package/dist/validation.js +0 -165
  297. package/dist/validation.js.map +0 -1
  298. package/dist/workspace.d.ts +0 -25
  299. package/dist/workspace.d.ts.map +0 -1
  300. package/dist/workspace.js +0 -57
  301. package/dist/workspace.js.map +0 -1
  302. package/src/__tests__/blob-ref.test.ts +0 -103
  303. package/src/__tests__/branded.test.ts +0 -148
  304. package/src/__tests__/collections.test.ts +0 -126
  305. package/src/__tests__/context.test.ts +0 -77
  306. package/src/__tests__/contour.test.ts +0 -263
  307. package/src/__tests__/derive-trail.test.ts +0 -668
  308. package/src/__tests__/derive.test.ts +0 -260
  309. package/src/__tests__/errors.test.ts +0 -427
  310. package/src/__tests__/execute.test.ts +0 -1563
  311. package/src/__tests__/fetch.test.ts +0 -217
  312. package/src/__tests__/fire.test.ts +0 -1056
  313. package/src/__tests__/fork-ctx.test.ts +0 -83
  314. package/src/__tests__/guards.test.ts +0 -102
  315. package/src/__tests__/ingest.test.ts +0 -191
  316. package/src/__tests__/layer.test.ts +0 -125
  317. package/src/__tests__/path-security.test.ts +0 -114
  318. package/src/__tests__/patterns.test.ts +0 -273
  319. package/src/__tests__/redaction.test.ts +0 -244
  320. package/src/__tests__/resilience.test.ts +0 -246
  321. package/src/__tests__/resource.test.ts +0 -204
  322. package/src/__tests__/result.test.ts +0 -155
  323. package/src/__tests__/run.test.ts +0 -181
  324. package/src/__tests__/serialization.test.ts +0 -401
  325. package/src/__tests__/service-config.test.ts +0 -224
  326. package/src/__tests__/signal.test.ts +0 -82
  327. package/src/__tests__/surface-filter.test.ts +0 -196
  328. package/src/__tests__/topo-store-read.test.ts +0 -256
  329. package/src/__tests__/topo-store.test.ts +0 -869
  330. package/src/__tests__/topo.test.ts +0 -573
  331. package/src/__tests__/trail-permit.test.ts +0 -60
  332. package/src/__tests__/trail.test.ts +0 -426
  333. package/src/__tests__/trails-db.test.ts +0 -198
  334. package/src/__tests__/transport-error-map.test.ts +0 -82
  335. package/src/__tests__/type-utils.test.ts +0 -118
  336. package/src/__tests__/validate-topo.test.ts +0 -611
  337. package/src/__tests__/validation.test.ts +0 -336
  338. package/src/__tests__/workspace.test.ts +0 -183
  339. package/src/__tests__/zod-wrappers.test.ts +0 -62
  340. package/src/internal/topo-snapshots.ts +0 -517
  341. package/src/internal/topo-store-read.ts +0 -481
  342. package/src/internal/topo-store.ts +0 -1192
  343. package/src/internal/tracing.ts +0 -187
  344. package/src/topo-store.ts +0 -510
  345. package/src/type-checks.test-d.ts +0 -106
  346. package/tsconfig.json +0 -9
  347. package/tsconfig.tests.json +0 -10
  348. package/tsconfig.tsbuildinfo +0 -1
@@ -1,336 +0,0 @@
1
- import { describe, test, expect } from 'bun:test';
2
-
3
- import { z } from 'zod';
4
-
5
- import { ValidationError } from '../errors.js';
6
- import {
7
- validateInput,
8
- validateOutput,
9
- formatZodIssues,
10
- zodToJsonSchema,
11
- } from '../validation.js';
12
-
13
- // ---------------------------------------------------------------------------
14
- // validateInput
15
- // ---------------------------------------------------------------------------
16
-
17
- describe('validateInput', () => {
18
- const schema = z.object({
19
- age: z.number().min(0),
20
- name: z.string(),
21
- });
22
-
23
- test('returns Ok for valid data', () => {
24
- const result = validateInput(schema, { age: 30, name: 'Alice' });
25
- expect(result.isOk()).toBe(true);
26
- expect(result.unwrap()).toEqual({ age: 30, name: 'Alice' });
27
- });
28
-
29
- test('returns Err with ValidationError for invalid data', () => {
30
- const result = validateInput(schema, { name: 123 });
31
- expect(result.isErr()).toBe(true);
32
- const err = result as unknown as { error: ValidationError };
33
- expect(err.error).toBeInstanceOf(ValidationError);
34
- expect(err.error.category).toBe('validation');
35
- });
36
-
37
- test('includes formatted issues in error message', () => {
38
- const result = validateInput(schema, {});
39
- expect(result.isErr()).toBe(true);
40
- const err = result as unknown as { error: ValidationError };
41
- expect(err.error.message).toContain('name');
42
- });
43
-
44
- test('attaches ZodError as cause', () => {
45
- const result = validateInput(schema, {});
46
- expect(result.isErr()).toBe(true);
47
- const err = result as unknown as { error: ValidationError };
48
- expect(err.error.cause).toBeInstanceOf(z.ZodError);
49
- });
50
-
51
- test('attaches issues in context', () => {
52
- const result = validateInput(schema, {});
53
- expect(result.isErr()).toBe(true);
54
- const err = result as unknown as { error: ValidationError };
55
- expect(err.error.context).toBeDefined();
56
- expect(Array.isArray(err.error.context?.['issues'])).toBe(true);
57
- });
58
-
59
- test('works with simple string schema', () => {
60
- const str = z.string().min(1);
61
- expect(validateInput(str, 'hello').isOk()).toBe(true);
62
- expect(validateInput(str, '').isErr()).toBe(true);
63
- expect(validateInput(str, 42).isErr()).toBe(true);
64
- });
65
- });
66
-
67
- // ---------------------------------------------------------------------------
68
- // validateOutput
69
- // ---------------------------------------------------------------------------
70
-
71
- describe('validateOutput', () => {
72
- const schema = z.object({
73
- id: z.string(),
74
- score: z.number(),
75
- });
76
-
77
- test('returns Ok for valid data', () => {
78
- const result = validateOutput(schema, { id: 'abc', score: 42 });
79
- expect(result.isOk()).toBe(true);
80
- expect(result.unwrap()).toEqual({ id: 'abc', score: 42 });
81
- });
82
-
83
- test('returns Err with ValidationError for invalid data', () => {
84
- const result = validateOutput(schema, { id: 123 });
85
- expect(result.isErr()).toBe(true);
86
- const err = result as unknown as { error: ValidationError };
87
- expect(err.error).toBeInstanceOf(ValidationError);
88
- expect(err.error.category).toBe('validation');
89
- });
90
-
91
- test('error message includes "Output validation failed" prefix', () => {
92
- const result = validateOutput(schema, {});
93
- expect(result.isErr()).toBe(true);
94
- const err = result as unknown as { error: ValidationError };
95
- expect(err.error.message).toContain('Output validation failed');
96
- });
97
-
98
- test('attaches ZodError as cause', () => {
99
- const result = validateOutput(schema, {});
100
- expect(result.isErr()).toBe(true);
101
- const err = result as unknown as { error: ValidationError };
102
- expect(err.error.cause).toBeInstanceOf(z.ZodError);
103
- });
104
-
105
- test('attaches issues in context', () => {
106
- const result = validateOutput(schema, {});
107
- expect(result.isErr()).toBe(true);
108
- const err = result as unknown as { error: ValidationError };
109
- expect(err.error.context).toBeDefined();
110
- expect(Array.isArray(err.error.context?.['issues'])).toBe(true);
111
- });
112
- });
113
-
114
- // ---------------------------------------------------------------------------
115
- // formatZodIssues
116
- // ---------------------------------------------------------------------------
117
-
118
- describe('formatZodIssues', () => {
119
- test('formats issues with paths', () => {
120
- const schema = z.object({ email: z.string().email() });
121
- const parsed = schema.safeParse({ email: 'not-email' });
122
- expect(parsed.success).toBe(false);
123
- const failed1 = parsed as unknown as { error: z.ZodError };
124
- const messages1 = formatZodIssues(failed1.error.issues);
125
- expect(messages1.length).toBeGreaterThan(0);
126
- expect(messages1[0]).toMatch(/^email: /);
127
- });
128
-
129
- test('formats root-level issues without path prefix', () => {
130
- const schema = z.string();
131
- const parsed = schema.safeParse(42);
132
- expect(parsed.success).toBe(false);
133
- const failed = parsed as unknown as { error: z.ZodError };
134
- const messages = formatZodIssues(failed.error.issues);
135
- expect(messages.length).toBeGreaterThan(0);
136
- // Root issues have no "path: " prefix — message starts directly
137
- // (the message itself may contain colons, but won't start with "path: ")
138
- expect(messages[0]).not.toMatch(/^\w+: /);
139
- });
140
-
141
- test('formats nested paths with dot notation', () => {
142
- const schema = z.object({
143
- user: z.object({ name: z.string() }),
144
- });
145
- const parsed = schema.safeParse({ user: { name: 123 } });
146
- expect(parsed.success).toBe(false);
147
- const failed = parsed as unknown as { error: z.ZodError };
148
- const messages = formatZodIssues(failed.error.issues);
149
- expect(messages[0]).toMatch(/^user\.name: /);
150
- });
151
- });
152
-
153
- // ---------------------------------------------------------------------------
154
- // zodToJsonSchema
155
- // ---------------------------------------------------------------------------
156
-
157
- describe('zodToJsonSchema', () => {
158
- describe('primitives', () => {
159
- test('converts z.string()', () => {
160
- expect(zodToJsonSchema(z.string())).toEqual({ type: 'string' });
161
- });
162
-
163
- test('converts z.number()', () => {
164
- expect(zodToJsonSchema(z.number())).toEqual({ type: 'number' });
165
- });
166
-
167
- test('converts z.boolean()', () => {
168
- expect(zodToJsonSchema(z.boolean())).toEqual({ type: 'boolean' });
169
- });
170
-
171
- test('converts z.literal()', () => {
172
- expect(zodToJsonSchema(z.literal('hello'))).toEqual({ const: 'hello' });
173
- expect(zodToJsonSchema(z.literal(42))).toEqual({ const: 42 });
174
- });
175
-
176
- test('converts z.enum()', () => {
177
- expect(zodToJsonSchema(z.enum(['a', 'b', 'c']))).toEqual({
178
- enum: ['a', 'b', 'c'],
179
- type: 'string',
180
- });
181
- });
182
-
183
- test('converts z.array()', () => {
184
- expect(zodToJsonSchema(z.array(z.string()))).toEqual({
185
- items: { type: 'string' },
186
- type: 'array',
187
- });
188
- });
189
- });
190
-
191
- describe('objects', () => {
192
- test('converts z.object() with required fields', () => {
193
- const schema = z.object({ age: z.number(), name: z.string() });
194
- const result = zodToJsonSchema(schema);
195
- expect(result).toEqual({
196
- properties: {
197
- age: { type: 'number' },
198
- name: { type: 'string' },
199
- },
200
- required: ['age', 'name'],
201
- type: 'object',
202
- });
203
- });
204
-
205
- test('converts z.object() with optional fields', () => {
206
- const schema = z.object({
207
- name: z.string(),
208
- nickname: z.string().optional(),
209
- });
210
- const result = zodToJsonSchema(schema);
211
- expect(result).toEqual({
212
- properties: {
213
- name: { type: 'string' },
214
- nickname: { type: 'string' },
215
- },
216
- required: ['name'],
217
- type: 'object',
218
- });
219
- });
220
-
221
- test('converts z.object() with default fields', () => {
222
- const schema = z.object({
223
- name: z.string(),
224
- role: z.string().default('user'),
225
- });
226
- const result = zodToJsonSchema(schema);
227
- expect(result).toEqual({
228
- properties: {
229
- name: { type: 'string' },
230
- role: { default: 'user', type: 'string' },
231
- },
232
- required: ['name'],
233
- type: 'object',
234
- });
235
- });
236
-
237
- test('handles nested objects', () => {
238
- const schema = z.object({
239
- user: z.object({ name: z.string() }),
240
- });
241
- expect(zodToJsonSchema(schema)).toEqual({
242
- properties: {
243
- user: {
244
- properties: { name: { type: 'string' } },
245
- required: ['name'],
246
- type: 'object',
247
- },
248
- },
249
- required: ['user'],
250
- type: 'object',
251
- });
252
- });
253
- });
254
-
255
- describe('modifiers and combinators', () => {
256
- test('converts z.union()', () => {
257
- const schema = z.union([z.string(), z.number()]);
258
- expect(zodToJsonSchema(schema)).toEqual({
259
- anyOf: [{ type: 'string' }, { type: 'number' }],
260
- });
261
- });
262
-
263
- test('preserves z.describe()', () => {
264
- const schema = z.string().describe('A user name');
265
- expect(zodToJsonSchema(schema)).toEqual({
266
- description: 'A user name',
267
- type: 'string',
268
- });
269
- });
270
-
271
- test('handles z.nullable()', () => {
272
- const schema = z.string().nullable();
273
- expect(zodToJsonSchema(schema)).toEqual({
274
- anyOf: [{ type: 'string' }, { type: 'null' }],
275
- });
276
- });
277
-
278
- test('returns empty object for unknown types', () => {
279
- // z.any() is not in our coverage list
280
- expect(zodToJsonSchema(z.any())).toEqual({});
281
- });
282
- });
283
-
284
- describe('default values', () => {
285
- test('preserves static defaults as-is', () => {
286
- const schema = z.string().default('hello');
287
- expect(zodToJsonSchema(schema)).toEqual({
288
- default: 'hello',
289
- type: 'string',
290
- });
291
- });
292
-
293
- test('omits dynamic defaults that produce different values', () => {
294
- let counter = 0;
295
- const schema = z.string().default(() => {
296
- counter += 1;
297
- return `id-${counter}`;
298
- });
299
- const result = zodToJsonSchema(schema);
300
- expect(result).toEqual({ type: 'string' });
301
- expect(result['default']).toBeUndefined();
302
- });
303
-
304
- test('preserves stable functional defaults that return constant values', () => {
305
- const schema = z.string().default(() => 'constant');
306
- const result = zodToJsonSchema(schema);
307
- expect(result).toEqual({ default: 'constant', type: 'string' });
308
- });
309
-
310
- test('preserves stable functional defaults that return equivalent objects', () => {
311
- const schema = z
312
- .object({ key: z.string() })
313
- .default(() => ({ key: 'val' }));
314
- const result = zodToJsonSchema(schema);
315
- expect(result['default']).toEqual({ key: 'val' });
316
- });
317
-
318
- test('preserves stable functional defaults that return equivalent arrays', () => {
319
- const schema = z.array(z.string()).default(() => ['a', 'b']);
320
- const result = zodToJsonSchema(schema);
321
- expect(result['default']).toEqual(['a', 'b']);
322
- });
323
-
324
- test('produces identical output across repeated calls', () => {
325
- let counter = 0;
326
- const schema = z.string().default(() => {
327
- counter += 1;
328
- return `id-${counter}`;
329
- });
330
- const first = zodToJsonSchema(schema);
331
- const second = zodToJsonSchema(schema);
332
- // Key invariant: repeated calls always produce the same schema
333
- expect(first).toEqual(second);
334
- });
335
- });
336
- });
@@ -1,183 +0,0 @@
1
- import { describe, test, expect } from 'bun:test';
2
- import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
3
- import { tmpdir } from 'node:os';
4
- import { resolve, join } from 'node:path';
5
-
6
- import { NotFoundError } from '../errors.js';
7
- import {
8
- findWorkspaceRoot,
9
- isInsideWorkspace,
10
- deriveRelativePath,
11
- } from '../workspace.js';
12
-
13
- // ---------------------------------------------------------------------------
14
- // Helpers
15
- // ---------------------------------------------------------------------------
16
-
17
- const createTempDir = (): string =>
18
- mkdtempSync(join(tmpdir(), 'trails-ws-test-'));
19
-
20
- const writeJson = (dir: string, filename: string, data: unknown): void => {
21
- writeFileSync(join(dir, filename), JSON.stringify(data, null, 2));
22
- };
23
-
24
- /** Set up nested workspaces: outer > inner > packages/lib. Returns the deepest path. */
25
- const setupNestedWorkspaces = (outer: string): string => {
26
- writeJson(outer, 'package.json', { name: 'outer', workspaces: ['inner/*'] });
27
- const inner = join(outer, 'inner', 'nested');
28
- mkdirSync(inner, { recursive: true });
29
- writeJson(join(outer, 'inner'), 'package.json', {
30
- name: 'inner',
31
- workspaces: ['packages/*'],
32
- });
33
- const deep = join(inner, 'packages', 'lib');
34
- mkdirSync(deep, { recursive: true });
35
- return deep;
36
- };
37
-
38
- // ---------------------------------------------------------------------------
39
- // findWorkspaceRoot
40
- // ---------------------------------------------------------------------------
41
-
42
- describe('findWorkspaceRoot', () => {
43
- test('finds workspace root when started inside a nested directory', async () => {
44
- const root = createTempDir();
45
- try {
46
- writeJson(root, 'package.json', {
47
- name: 'my-workspace',
48
- workspaces: ['packages/*'],
49
- });
50
-
51
- const nested = join(root, 'packages', 'core', 'src');
52
- mkdirSync(nested, { recursive: true });
53
-
54
- const result = await findWorkspaceRoot(nested);
55
- expect(result.isOk()).toBe(true);
56
- expect(result.unwrap()).toBe(resolve(root));
57
- } finally {
58
- rmSync(root, { force: true, recursive: true });
59
- }
60
- });
61
-
62
- test('skips package.json without workspaces field', async () => {
63
- const root = createTempDir();
64
- try {
65
- // Root has workspaces
66
- writeJson(root, 'package.json', {
67
- name: 'root',
68
- workspaces: ['packages/*'],
69
- });
70
-
71
- // Nested package has no workspaces
72
- const pkg = join(root, 'packages', 'core');
73
- mkdirSync(pkg, { recursive: true });
74
- writeJson(pkg, 'package.json', { name: '@scope/core' });
75
-
76
- const result = await findWorkspaceRoot(pkg);
77
- expect(result.isOk()).toBe(true);
78
- expect(result.unwrap()).toBe(resolve(root));
79
- } finally {
80
- rmSync(root, { force: true, recursive: true });
81
- }
82
- });
83
-
84
- test('returns the closest workspace root', async () => {
85
- const outer = createTempDir();
86
- try {
87
- const deep = setupNestedWorkspaces(outer);
88
- const result = await findWorkspaceRoot(deep);
89
- expect(result.isOk()).toBe(true);
90
- // Should find inner workspace first (closest ancestor)
91
- expect(result.unwrap()).toBe(resolve(join(outer, 'inner')));
92
- } finally {
93
- rmSync(outer, { force: true, recursive: true });
94
- }
95
- });
96
-
97
- test('returns NotFoundError when no workspace root exists', async () => {
98
- const dir = createTempDir();
99
- try {
100
- // No package.json at all
101
- const result = await findWorkspaceRoot(dir);
102
- // This will either find one above in the actual filesystem or fail.
103
- // We walk up to /, so it depends on the host. Use a deep isolated path.
104
- // Since tmpdir might be under a workspace, let's just check the type
105
- // is correct if it does fail. When the host has a workspace root above
106
- // tmpdir, the result will be Ok — both outcomes are acceptable.
107
- const isAcceptable =
108
- // oxlint-disable-next-line no-conditional-in-test -- host-dependent: tmpdir may sit under a real workspace
109
- result.isOk() ||
110
- (result as unknown as { error: Error }).error instanceof NotFoundError;
111
- expect(isAcceptable).toBe(true);
112
- } finally {
113
- rmSync(dir, { force: true, recursive: true });
114
- }
115
- });
116
-
117
- test('handles malformed package.json gracefully', async () => {
118
- const root = createTempDir();
119
- try {
120
- // Write invalid JSON
121
- writeFileSync(join(root, 'package.json'), 'not json {{{');
122
-
123
- const result = await findWorkspaceRoot(root);
124
- // Should not throw — just skip and keep walking.
125
- // On some hosts tmpdir sits under a real workspace, so Ok is valid too.
126
- const isAcceptable =
127
- // oxlint-disable-next-line no-conditional-in-test -- host-dependent: tmpdir may sit under a real workspace
128
- result.isOk() ||
129
- (result as unknown as { error: Error }).error instanceof NotFoundError;
130
- expect(isAcceptable).toBe(true);
131
- } finally {
132
- rmSync(root, { force: true, recursive: true });
133
- }
134
- });
135
- });
136
-
137
- // ---------------------------------------------------------------------------
138
- // isInsideWorkspace
139
- // ---------------------------------------------------------------------------
140
-
141
- describe('isInsideWorkspace', () => {
142
- test('returns true for a file inside the workspace', () => {
143
- expect(
144
- isInsideWorkspace('/project/packages/core/src/index.ts', '/project')
145
- ).toBe(true);
146
- });
147
-
148
- test('returns false for a file outside the workspace', () => {
149
- expect(isInsideWorkspace('/other/file.ts', '/project')).toBe(false);
150
- });
151
-
152
- test('returns false for the workspace root itself', () => {
153
- // The root directory itself is not \"inside\" the workspace
154
- expect(isInsideWorkspace('/project', '/project')).toBe(false);
155
- });
156
-
157
- test('handles relative-looking paths by resolving them', () => {
158
- expect(
159
- isInsideWorkspace('/project/packages/../packages/core', '/project')
160
- ).toBe(true);
161
- });
162
- });
163
-
164
- // ---------------------------------------------------------------------------
165
- // deriveRelativePath
166
- // ---------------------------------------------------------------------------
167
-
168
- describe('deriveRelativePath', () => {
169
- test('returns relative path from workspace root', () => {
170
- expect(
171
- deriveRelativePath('/project/packages/core/src/index.ts', '/project')
172
- ).toBe('packages/core/src/index.ts');
173
- });
174
-
175
- test('returns .. segments for paths outside workspace', () => {
176
- const rel = deriveRelativePath('/other/file.ts', '/project');
177
- expect(rel.startsWith('..')).toBe(true);
178
- });
179
-
180
- test('returns empty string for the root itself', () => {
181
- expect(deriveRelativePath('/project', '/project')).toBe('');
182
- });
183
- });
@@ -1,62 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import { z } from 'zod';
3
-
4
- import {
5
- stripDefaultsFromShape,
6
- stripDefaultWrappers,
7
- } from '../internal/zod-wrappers.js';
8
-
9
- describe('stripDefaultWrappers', () => {
10
- test('drops plain default wrappers', () => {
11
- const withDefault = z.string().default('x');
12
- const stripped = stripDefaultWrappers(withDefault);
13
-
14
- expect(stripped.def.type).toBe('string');
15
- // Feeding an undefined through the stripped schema no longer
16
- // materializes the default.
17
- expect(stripped.safeParse().success).toBe(false);
18
- expect(stripped.safeParse('value').success).toBe(true);
19
- });
20
-
21
- test('drops optional + default layers together', () => {
22
- const schema = z.string().optional().default('x');
23
- const stripped = stripDefaultWrappers(schema);
24
-
25
- // Every wrapper around the underlying string is gone — optional is
26
- // dropped on purpose because downstream `.partial()` calls re-add it.
27
- expect(stripped.def.type).toBe('string');
28
- expect(stripped.safeParse().success).toBe(false);
29
- expect(stripped.safeParse('value').success).toBe(true);
30
- });
31
-
32
- test('preserves nullable constraints through the strip', () => {
33
- const schema = z.string().nullable().default('x');
34
- const stripped = stripDefaultWrappers(schema);
35
-
36
- // Default is gone; null is still a legal input.
37
- expect(stripped.def.type).toBe('nullable');
38
- expect(stripped.safeParse(null).success).toBe(true);
39
- expect(stripped.safeParse('value').success).toBe(true);
40
- });
41
-
42
- test('is load-bearing for .partial() on both call sites', () => {
43
- const shape = {
44
- author: z.string().optional().default('anon'),
45
- title: z.string().default('untitled'),
46
- };
47
- const schema = z.object(shape);
48
-
49
- // Both the store `deriveUpdateSchema` helper and the core derive-trail
50
- // `toPartialSchema` helper compose the same pattern: extend the schema
51
- // with the stripped shape, then call `.partial()`. Both call sites
52
- // must produce a schema that accepts an empty object without
53
- // re-materializing defaults.
54
- const stripped = stripDefaultsFromShape(schema);
55
- const partial = schema.extend(stripped).partial();
56
-
57
- const parsed = partial.parse({});
58
- expect(parsed).toEqual({});
59
- expect('author' in parsed).toBe(false);
60
- expect('title' in parsed).toBe(false);
61
- });
62
- });