@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,148 +0,0 @@
1
- import { describe, test, expect } from 'bun:test';
2
-
3
- import {
4
- brand,
5
- unbrand,
6
- uuid,
7
- email,
8
- nonEmptyString,
9
- positiveInt,
10
- shortId,
11
- deriveIdHash,
12
- } from '../branded';
13
-
14
- describe('branded', () => {
15
- describe('brand / unbrand', () => {
16
- test('brand wraps and unbrand unwraps', () => {
17
- const branded = brand('Tag', 'hello');
18
- expect(unbrand(branded)).toBe('hello');
19
- });
20
-
21
- test('branded value is still usable as base type at runtime', () => {
22
- const branded = brand('PositiveInt', 42);
23
- expect(branded + 1).toBe(43);
24
- });
25
- });
26
-
27
- describe('uuid()', () => {
28
- test('accepts a valid v4 UUID', () => {
29
- const result = uuid('550e8400-e29b-41d4-a716-446655440000');
30
- expect(result.isOk()).toBe(true);
31
- const value: string = unbrand(result.unwrap());
32
- expect(value).toBe('550e8400-e29b-41d4-a716-446655440000');
33
- });
34
-
35
- test('rejects an empty string', () => {
36
- const result = uuid('');
37
- expect(result.isErr()).toBe(true);
38
- });
39
-
40
- test('rejects a malformed UUID', () => {
41
- const result = uuid('not-a-uuid');
42
- expect(result.isErr()).toBe(true);
43
- });
44
-
45
- test('rejects UUID missing hyphens', () => {
46
- const result = uuid('550e8400e29b41d4a716446655440000');
47
- expect(result.isErr()).toBe(true);
48
- });
49
- });
50
-
51
- describe('email()', () => {
52
- test('accepts a simple email', () => {
53
- const result = email('test@example.com');
54
- expect(result.isOk()).toBe(true);
55
- });
56
-
57
- test('rejects missing @', () => {
58
- const result = email('testexample.com');
59
- expect(result.isErr()).toBe(true);
60
- });
61
-
62
- test('rejects missing domain', () => {
63
- const result = email('test@');
64
- expect(result.isErr()).toBe(true);
65
- });
66
-
67
- test('rejects empty string', () => {
68
- const result = email('');
69
- expect(result.isErr()).toBe(true);
70
- });
71
- });
72
-
73
- describe('nonEmptyString()', () => {
74
- test('accepts a non-empty string', () => {
75
- const result = nonEmptyString('hello');
76
- expect(result.isOk()).toBe(true);
77
- });
78
-
79
- test('rejects an empty string', () => {
80
- const result = nonEmptyString('');
81
- expect(result.isErr()).toBe(true);
82
- });
83
- });
84
-
85
- describe('positiveInt()', () => {
86
- test('accepts 1', () => {
87
- const result = positiveInt(1);
88
- expect(result.isOk()).toBe(true);
89
- });
90
-
91
- test('accepts a large integer', () => {
92
- const result = positiveInt(1_000_000);
93
- expect(result.isOk()).toBe(true);
94
- });
95
-
96
- test('rejects 0', () => {
97
- const result = positiveInt(0);
98
- expect(result.isErr()).toBe(true);
99
- });
100
-
101
- test('rejects negative numbers', () => {
102
- const result = positiveInt(-5);
103
- expect(result.isErr()).toBe(true);
104
- });
105
-
106
- test('rejects non-integers', () => {
107
- const result = positiveInt(1.5);
108
- expect(result.isErr()).toBe(true);
109
- });
110
- });
111
-
112
- describe('shortId()', () => {
113
- test('returns a string of default length 8', () => {
114
- const id = shortId();
115
- expect(id).toHaveLength(8);
116
- });
117
-
118
- test('respects custom length', () => {
119
- const id = shortId(16);
120
- expect(id).toHaveLength(16);
121
- });
122
-
123
- test('contains only alphanumeric characters', () => {
124
- const id = shortId(100);
125
- expect(id).toMatch(/^[A-Za-z0-9]+$/);
126
- });
127
-
128
- test('produces unique values', () => {
129
- const ids = new Set(Array.from({ length: 50 }, () => shortId()));
130
- expect(ids.size).toBe(50);
131
- });
132
- });
133
-
134
- describe('deriveIdHash()', () => {
135
- test('returns a hex string', () => {
136
- const id = deriveIdHash('test');
137
- expect(id).toMatch(/^[0-9a-f]{8}$/);
138
- });
139
-
140
- test('is deterministic', () => {
141
- expect(deriveIdHash('hello')).toBe(deriveIdHash('hello'));
142
- });
143
-
144
- test('different inputs produce different outputs', () => {
145
- expect(deriveIdHash('a')).not.toBe(deriveIdHash('b'));
146
- });
147
- });
148
- });
@@ -1,126 +0,0 @@
1
- import { describe, test, expect } from 'bun:test';
2
-
3
- import {
4
- chunk,
5
- dedupe,
6
- groupBy,
7
- sortBy,
8
- isNonEmptyArray,
9
- } from '../collections';
10
-
11
- describe('collections', () => {
12
- describe('chunk()', () => {
13
- test('splits into even chunks', () => {
14
- expect(chunk([1, 2, 3, 4], 2)).toEqual([
15
- [1, 2],
16
- [3, 4],
17
- ]);
18
- });
19
-
20
- test('handles remainder', () => {
21
- expect(chunk([1, 2, 3, 4, 5], 2)).toEqual([[1, 2], [3, 4], [5]]);
22
- });
23
-
24
- test('returns empty array for empty input', () => {
25
- expect(chunk([], 3)).toEqual([]);
26
- });
27
-
28
- test('single-element chunks', () => {
29
- expect(chunk([1, 2, 3], 1)).toEqual([[1], [2], [3]]);
30
- });
31
-
32
- test('chunk size larger than array', () => {
33
- expect(chunk([1, 2], 10)).toEqual([[1, 2]]);
34
- });
35
-
36
- test('throws on size < 1', () => {
37
- expect(() => chunk([1], 0)).toThrow(RangeError);
38
- });
39
- });
40
-
41
- describe('dedupe()', () => {
42
- test('removes primitive duplicates', () => {
43
- expect(dedupe([1, 2, 2, 3, 1])).toEqual([1, 2, 3]);
44
- });
45
-
46
- test('dedupes by key function', () => {
47
- const items = [
48
- { id: 1, name: 'a' },
49
- { id: 2, name: 'b' },
50
- { id: 1, name: 'c' },
51
- ];
52
- const result = dedupe(items, (i) => i.id);
53
- expect(result).toEqual([
54
- { id: 1, name: 'a' },
55
- { id: 2, name: 'b' },
56
- ]);
57
- });
58
-
59
- test('preserves order', () => {
60
- expect(dedupe([3, 1, 2, 1, 3])).toEqual([3, 1, 2]);
61
- });
62
-
63
- test('handles empty array', () => {
64
- expect(dedupe([])).toEqual([]);
65
- });
66
- });
67
-
68
- describe('groupBy()', () => {
69
- test('groups by key', () => {
70
- const items = [
71
- { type: 'a', value: 1 },
72
- { type: 'b', value: 2 },
73
- { type: 'a', value: 3 },
74
- ];
75
- const groups = groupBy(items, (i) => i.type);
76
- expect(groups).toEqual({
77
- a: [
78
- { type: 'a', value: 1 },
79
- { type: 'a', value: 3 },
80
- ],
81
- b: [{ type: 'b', value: 2 }],
82
- });
83
- });
84
-
85
- test('returns empty object for empty array', () => {
86
- expect(groupBy([], () => 'key')).toEqual({});
87
- });
88
- });
89
-
90
- describe('sortBy()', () => {
91
- test('sorts by numeric key ascending', () => {
92
- const items = [{ n: 3 }, { n: 1 }, { n: 2 }];
93
- expect(sortBy(items, (i) => i.n)).toEqual([{ n: 1 }, { n: 2 }, { n: 3 }]);
94
- });
95
-
96
- test('sorts by string key alphabetically', () => {
97
- const items = [{ name: 'charlie' }, { name: 'alice' }, { name: 'bob' }];
98
- expect(sortBy(items, (i) => i.name)).toEqual([
99
- { name: 'alice' },
100
- { name: 'bob' },
101
- { name: 'charlie' },
102
- ]);
103
- });
104
-
105
- test('does not mutate the original array', () => {
106
- const original = [{ n: 3 }, { n: 1 }];
107
- sortBy(original, (i) => i.n);
108
- expect(original[0]?.n).toBe(3);
109
- });
110
-
111
- test('returns empty array for empty input', () => {
112
- expect(sortBy([], () => 0)).toEqual([]);
113
- });
114
- });
115
-
116
- describe('isNonEmptyArray()', () => {
117
- test('returns true for arrays with elements', () => {
118
- expect(isNonEmptyArray([1])).toBe(true);
119
- expect(isNonEmptyArray([1, 2, 3])).toBe(true);
120
- });
121
-
122
- test('returns false for empty array', () => {
123
- expect(isNonEmptyArray([])).toBe(false);
124
- });
125
- });
126
- });
@@ -1,77 +0,0 @@
1
- import { describe, test, expect } from 'bun:test';
2
-
3
- import { createTrailContext } from '../context.js';
4
- import type { TrailContext } from '../types.js';
5
-
6
- const UUID_RE =
7
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8
-
9
- describe('createTrailContext', () => {
10
- test('generates a requestId in UUID format', () => {
11
- const ctx = createTrailContext();
12
- expect(ctx.requestId).toMatch(UUID_RE);
13
- });
14
-
15
- test('defaults cwd to process.cwd()', () => {
16
- const ctx = createTrailContext();
17
- expect(ctx.cwd).toBe(process.cwd());
18
- });
19
-
20
- test('defaults env to process.env', () => {
21
- const ctx = createTrailContext();
22
- expect(ctx.env).toBeTruthy();
23
- expect(ctx.env).toBe(process.env);
24
- });
25
-
26
- test('provides a non-aborted abortSignal by default', () => {
27
- const ctx = createTrailContext();
28
- expect(ctx.abortSignal).toBeInstanceOf(AbortSignal);
29
- expect(ctx.abortSignal.aborted).toBe(false);
30
- });
31
-
32
- test('override values take precedence', () => {
33
- const ac = new AbortController();
34
- ac.abort();
35
-
36
- const ctx = createTrailContext({
37
- abortSignal: ac.signal,
38
- cwd: '/custom/dir',
39
- env: { CUSTOM: 'yes' },
40
- requestId: 'custom-id',
41
- workspaceRoot: '/tmp',
42
- });
43
-
44
- expect(ctx.cwd).toBe('/custom/dir');
45
- expect(ctx.env).toEqual({ CUSTOM: 'yes' });
46
- expect(ctx.requestId).toBe('custom-id');
47
- expect(ctx.abortSignal.aborted).toBe(true);
48
- expect(ctx.workspaceRoot).toBe('/tmp');
49
- });
50
-
51
- test('TrailContext supports extensions for custom fields', () => {
52
- const ctx: TrailContext = createTrailContext({
53
- extensions: { customField: 42, nested: { key: 'value' } },
54
- });
55
-
56
- expect(ctx.extensions?.['customField']).toBe(42);
57
- expect(ctx.extensions?.['nested']).toEqual({ key: 'value' });
58
- });
59
-
60
- test('provides a resource accessor backed by extensions', () => {
61
- const widget = { id: 'widget-1' };
62
- const ctx = createTrailContext({
63
- extensions: { 'widget.main': widget },
64
- });
65
-
66
- expect(ctx.resource('widget.main')).toBe(widget);
67
- expect(ctx.resource<{ id: string }>({ id: 'widget.main' }).id).toBe(
68
- 'widget-1'
69
- );
70
- });
71
-
72
- test('each call generates a unique requestId', () => {
73
- const a = createTrailContext();
74
- const b = createTrailContext();
75
- expect(a.requestId).not.toBe(b.requestId);
76
- });
77
- });
@@ -1,263 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
-
3
- import { z } from 'zod';
4
-
5
- import {
6
- contour,
7
- getContourIdMetadata,
8
- getContourReferences,
9
- } from '../contour.js';
10
-
11
- const user = contour(
12
- 'user',
13
- {
14
- email: z.string().email(),
15
- id: z.string().uuid(),
16
- name: z.string(),
17
- },
18
- {
19
- examples: [
20
- {
21
- email: 'ada@example.com',
22
- id: '550e8400-e29b-41d4-a716-446655440000',
23
- name: 'Ada',
24
- },
25
- ],
26
- identity: 'id',
27
- }
28
- );
29
-
30
- const gist = contour(
31
- 'gist',
32
- {
33
- id: z.string().uuid(),
34
- ownerId: user.id(),
35
- title: z.string(),
36
- },
37
- { identity: 'id' }
38
- );
39
-
40
- type UserId = z.infer<ReturnType<typeof user.id>>;
41
- type GistId = z.infer<ReturnType<typeof gist.id>>;
42
-
43
- const _ownerId: z.infer<typeof gist.shape.ownerId> = {} as UserId;
44
- // @ts-expect-error distinct contour ids should not be assignable
45
- const _mixedIds: UserId = {} as GistId;
46
-
47
- describe('contour()', () => {
48
- describe('basics', () => {
49
- test("returns kind 'contour'", () => {
50
- expect(user.kind).toBe('contour');
51
- });
52
-
53
- test('preserves the contour name', () => {
54
- expect(user.name).toBe('user');
55
- });
56
-
57
- test('preserves schema behavior', () => {
58
- const parsed = user.safeParse({
59
- email: 'ada@example.com',
60
- id: '550e8400-e29b-41d4-a716-446655440000',
61
- name: 'Ada',
62
- });
63
-
64
- expect(parsed.success).toBe(true);
65
- });
66
- });
67
-
68
- describe('identity', () => {
69
- test('stores the identity field name', () => {
70
- expect(user.identity).toBe('id');
71
- });
72
-
73
- test('exposes the identity field schema', () => {
74
- expect(user.identitySchema).toBe(user.shape.id);
75
- });
76
-
77
- test('returns a branded identity schema', () => {
78
- const schema = user.id();
79
- expect(
80
- schema.safeParse('550e8400-e29b-41d4-a716-446655440000').success
81
- ).toBe(true);
82
- expect(schema).toBe(user.id());
83
- });
84
-
85
- test('attaches contour metadata to branded identity schemas', () => {
86
- expect(getContourIdMetadata(user.id())).toEqual({
87
- contour: 'user',
88
- identity: 'id',
89
- });
90
- });
91
-
92
- test('preserves first-write metadata when identity schemas are shared', () => {
93
- const baseUser = contour(
94
- 'user',
95
- { id: z.string().uuid(), name: z.string() },
96
- { identity: 'id' }
97
- );
98
- const admin = contour(
99
- 'admin',
100
- { id: baseUser.shape.id, role: z.string() },
101
- { identity: 'id' }
102
- );
103
-
104
- // The first contour to brand the schema wins — user, not admin.
105
- expect(getContourIdMetadata(baseUser.id())).toEqual({
106
- contour: 'user',
107
- identity: 'id',
108
- });
109
- // admin still has its own id() accessor
110
- expect(admin.id()).toBeDefined();
111
-
112
- // Neither contour should emit its own identity as a cross-contour reference
113
- expect(getContourReferences(baseUser)).toEqual([]);
114
- expect(getContourReferences(admin)).toEqual([]);
115
- });
116
-
117
- test('rejects identity keys that are not in the shape', () => {
118
- expect(() =>
119
- contour(
120
- 'broken',
121
- {
122
- id: z.string(),
123
- },
124
- { identity: 'missing' as 'id' }
125
- )
126
- ).toThrow('identity "missing" must match a declared field');
127
- });
128
- });
129
-
130
- describe('examples', () => {
131
- test('stores examples as a frozen array', () => {
132
- expect(user.examples).toHaveLength(1);
133
- expect(Object.isFrozen(user.examples)).toBe(true);
134
- });
135
-
136
- test('validates examples against the contour schema', () => {
137
- expect(() =>
138
- contour(
139
- 'broken',
140
- {
141
- id: z.string().uuid(),
142
- name: z.string(),
143
- },
144
- {
145
- examples: [{ id: 'not-a-uuid', name: 42 as never }],
146
- identity: 'id',
147
- }
148
- )
149
- ).toThrow('example 0 is invalid');
150
- });
151
- });
152
-
153
- describe('zod forwarding', () => {
154
- test('supports .pick()', () => {
155
- const schema = user.pick({ name: true });
156
- expect(schema.safeParse({ name: 'Ada' }).success).toBe(true);
157
- expect(schema.safeParse({ email: 'ada@example.com' }).success).toBe(
158
- false
159
- );
160
- });
161
-
162
- test('supports .extend()', () => {
163
- const schema = user.extend({ active: z.boolean() });
164
- expect(
165
- schema.safeParse({
166
- active: true,
167
- email: 'ada@example.com',
168
- id: '550e8400-e29b-41d4-a716-446655440000',
169
- name: 'Ada',
170
- }).success
171
- ).toBe(true);
172
- });
173
-
174
- test('supports .array()', () => {
175
- const schema = user.array();
176
- expect(
177
- schema.safeParse([
178
- {
179
- email: 'ada@example.com',
180
- id: '550e8400-e29b-41d4-a716-446655440000',
181
- name: 'Ada',
182
- },
183
- ]).success
184
- ).toBe(true);
185
- });
186
-
187
- test('supports cross-contour references via .id()', () => {
188
- expect(
189
- gist.safeParse({
190
- id: '0f31f6ba-6ff0-41ce-9f6b-8d132b6c4b81',
191
- ownerId: '550e8400-e29b-41d4-a716-446655440000',
192
- title: 'Hello',
193
- }).success
194
- ).toBe(true);
195
- expect(getContourIdMetadata(gist.shape.ownerId)).toEqual({
196
- contour: 'user',
197
- identity: 'id',
198
- });
199
- });
200
-
201
- test('lists declared contour references', () => {
202
- expect(getContourReferences(gist)).toEqual([
203
- {
204
- contour: 'user',
205
- field: 'ownerId',
206
- identity: 'id',
207
- },
208
- ]);
209
- });
210
- });
211
-
212
- describe('wrapper unwrapping', () => {
213
- test('resolves .optional() contour id metadata', () => {
214
- expect(getContourIdMetadata(user.id().optional())).toEqual({
215
- contour: 'user',
216
- identity: 'id',
217
- });
218
- });
219
-
220
- test('resolves .nullable() contour id metadata', () => {
221
- expect(getContourIdMetadata(user.id().nullable())).toEqual({
222
- contour: 'user',
223
- identity: 'id',
224
- });
225
- });
226
-
227
- test('resolves .nullish() contour id metadata (two-level wrapper)', () => {
228
- expect(getContourIdMetadata(user.id().nullish())).toEqual({
229
- contour: 'user',
230
- identity: 'id',
231
- });
232
- });
233
-
234
- test('resolves deeply nested wrappers', () => {
235
- expect(
236
- getContourIdMetadata(user.id().nullable().optional().default(null))
237
- ).toEqual({
238
- contour: 'user',
239
- identity: 'id',
240
- });
241
- });
242
-
243
- test('detects references through .nullish() wrappers in contour shapes', () => {
244
- const comment = contour(
245
- 'comment',
246
- {
247
- authorId: user.id().nullish(),
248
- id: z.string().uuid(),
249
- text: z.string(),
250
- },
251
- { identity: 'id' }
252
- );
253
-
254
- expect(getContourReferences(comment)).toEqual([
255
- {
256
- contour: 'user',
257
- field: 'authorId',
258
- identity: 'id',
259
- },
260
- ]);
261
- });
262
- });
263
- });