@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
package/src/webhook.ts ADDED
@@ -0,0 +1,276 @@
1
+ import type {
2
+ ActivationSource,
3
+ ActivationSourceMeta,
4
+ ActivationSourceParse,
5
+ } from './activation-source.js';
6
+ import { InternalError, ValidationError } from './errors.js';
7
+ import { Result } from './result.js';
8
+
9
+ export const webhookMethods = Object.freeze([
10
+ 'DELETE',
11
+ 'GET',
12
+ 'PATCH',
13
+ 'POST',
14
+ 'PUT',
15
+ ] as const);
16
+
17
+ export type WebhookMethod = (typeof webhookMethods)[number];
18
+ export type WebhookMethodInput = WebhookMethod | Lowercase<WebhookMethod>;
19
+
20
+ export type WebhookVerifyHeaders = Readonly<
21
+ Record<string, readonly string[] | string | undefined>
22
+ >;
23
+
24
+ export interface WebhookVerifyRequest {
25
+ readonly body: ArrayBuffer | Uint8Array | string;
26
+ readonly headers: WebhookVerifyHeaders;
27
+ readonly method: string;
28
+ readonly path: string;
29
+ }
30
+
31
+ export type WebhookVerify = (
32
+ request: WebhookVerifyRequest
33
+ ) => Promise<Result<void, Error>> | Result<void, Error>;
34
+
35
+ export interface WebhookSpec<TOutput = unknown> {
36
+ readonly meta?: ActivationSourceMeta | undefined;
37
+ readonly method?: WebhookMethodInput | undefined;
38
+ readonly parse: ActivationSourceParse<TOutput>;
39
+ readonly path: string;
40
+ readonly payload?: ActivationSource['payload'] | undefined;
41
+ readonly verify?: WebhookVerify | undefined;
42
+ }
43
+
44
+ export interface WebhookSource<TOutput = unknown> extends ActivationSource {
45
+ readonly kind: 'webhook';
46
+ readonly meta?: ActivationSourceMeta | undefined;
47
+ readonly method: WebhookMethod;
48
+ readonly parse: ActivationSourceParse<TOutput>;
49
+ readonly path: string;
50
+ readonly payload?: ActivationSource['payload'] | undefined;
51
+ readonly verify?: WebhookVerify | undefined;
52
+ }
53
+
54
+ export interface WebhookValidationIssue {
55
+ readonly field: 'method' | 'parse' | 'path' | 'verify';
56
+ readonly message: string;
57
+ }
58
+
59
+ const DEFAULT_WEBHOOK_METHOD = 'POST' as const;
60
+
61
+ const normalizeMethod = (
62
+ method: WebhookMethodInput | string | undefined
63
+ ): string => (method ?? DEFAULT_WEBHOOK_METHOD).trim().toUpperCase();
64
+
65
+ const normalizePath = (path: string): string => path.trim();
66
+
67
+ const isWebhookMethod = (method: string): method is WebhookMethod =>
68
+ (webhookMethods as readonly string[]).includes(method);
69
+
70
+ const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
71
+ typeof value === 'object' && value !== null && !Array.isArray(value);
72
+
73
+ const isZodSchema = (value: unknown): boolean =>
74
+ isObjectRecord(value) && typeof value['safeParse'] === 'function';
75
+
76
+ const validateMethod = (
77
+ method: WebhookMethodInput | string | undefined
78
+ ): WebhookValidationIssue[] => {
79
+ const normalized = normalizeMethod(method);
80
+ return isWebhookMethod(normalized)
81
+ ? []
82
+ : [
83
+ {
84
+ field: 'method',
85
+ message: `Webhook method must be one of ${webhookMethods.join(', ')}`,
86
+ },
87
+ ];
88
+ };
89
+
90
+ const validatePath = (path: unknown): WebhookValidationIssue[] => {
91
+ if (typeof path !== 'string' || path.trim().length === 0) {
92
+ return [
93
+ {
94
+ field: 'path',
95
+ message: 'Webhook path must be a non-empty absolute path',
96
+ },
97
+ ];
98
+ }
99
+
100
+ const normalized = normalizePath(path);
101
+ return normalized.startsWith('/')
102
+ ? []
103
+ : [
104
+ {
105
+ field: 'path',
106
+ message: 'Webhook path must start with "/"',
107
+ },
108
+ ];
109
+ };
110
+
111
+ const validateRequiredParse = (parse: unknown): WebhookValidationIssue[] =>
112
+ parse === undefined
113
+ ? [
114
+ {
115
+ field: 'parse',
116
+ message: 'Webhook sources must define parse',
117
+ },
118
+ ]
119
+ : [];
120
+
121
+ const validateParseShape = (parse: unknown): WebhookValidationIssue[] => {
122
+ if (parse === undefined || isZodSchema(parse)) {
123
+ return [];
124
+ }
125
+ if (isObjectRecord(parse) && isZodSchema(parse['output'])) {
126
+ return [];
127
+ }
128
+ return [
129
+ {
130
+ field: 'parse',
131
+ message: 'Webhook parse must be a Zod schema or define parse.output',
132
+ },
133
+ ];
134
+ };
135
+
136
+ const validateVerify = (verify: unknown): WebhookValidationIssue[] =>
137
+ verify === undefined || typeof verify === 'function'
138
+ ? []
139
+ : [
140
+ {
141
+ field: 'verify',
142
+ message: 'Webhook verify must be a function when provided',
143
+ },
144
+ ];
145
+
146
+ const webhookIssuesMessage = (
147
+ id: string,
148
+ issues: readonly WebhookValidationIssue[]
149
+ ): string =>
150
+ `webhook("${id}") is invalid: ${issues.map((issue) => `${issue.field}: ${issue.message}`).join('; ')}`;
151
+
152
+ const assertWebhookSpec = <TOutput>(
153
+ id: string,
154
+ spec: WebhookSpec<TOutput>
155
+ ): {
156
+ readonly method: WebhookMethod;
157
+ readonly path: string;
158
+ } => {
159
+ const issues = [
160
+ ...validateMethod(spec.method),
161
+ ...validatePath(spec.path),
162
+ ...validateRequiredParse(spec.parse),
163
+ ...validateParseShape(spec.parse),
164
+ ...validateVerify(spec.verify),
165
+ ];
166
+
167
+ if (issues.length > 0) {
168
+ throw new ValidationError(webhookIssuesMessage(id, issues), {
169
+ context: { issues },
170
+ });
171
+ }
172
+
173
+ return {
174
+ method: normalizeMethod(spec.method) as WebhookMethod,
175
+ path: normalizePath(spec.path),
176
+ };
177
+ };
178
+
179
+ export const validateWebhookSource = (
180
+ source: ActivationSource
181
+ ): readonly WebhookValidationIssue[] => {
182
+ if (source.kind !== 'webhook') {
183
+ return [];
184
+ }
185
+
186
+ return [
187
+ ...validateMethod(source.method),
188
+ ...validatePath(source.path),
189
+ ...validateRequiredParse(source.parse),
190
+ ...validateParseShape(source.parse),
191
+ ...validateVerify(source.verify),
192
+ ];
193
+ };
194
+
195
+ const errorFromUnknown = (error: unknown): Error =>
196
+ error instanceof Error ? error : new Error(String(error));
197
+
198
+ export const getWebhookHeaders = (
199
+ request: Pick<WebhookVerifyRequest, 'headers'>,
200
+ name: string
201
+ ): readonly string[] => {
202
+ const normalized = name.toLowerCase();
203
+ const matches: string[] = [];
204
+ for (const [headerName, value] of Object.entries(request.headers)) {
205
+ if (headerName.toLowerCase() !== normalized) {
206
+ continue;
207
+ }
208
+ if (value === undefined) {
209
+ continue;
210
+ }
211
+ if (typeof value === 'string') {
212
+ matches.push(value);
213
+ } else {
214
+ matches.push(...value);
215
+ }
216
+ }
217
+ return matches;
218
+ };
219
+
220
+ export const getWebhookHeader = (
221
+ request: Pick<WebhookVerifyRequest, 'headers'>,
222
+ name: string
223
+ ): string | undefined => {
224
+ const [first] = getWebhookHeaders(request, name);
225
+ return first;
226
+ };
227
+
228
+ export const verifyWebhookRequest = async (
229
+ source: Pick<WebhookSource, 'id' | 'verify'>,
230
+ request: WebhookVerifyRequest
231
+ ): Promise<Result<void, Error>> => {
232
+ if (source.verify === undefined) {
233
+ return Result.ok();
234
+ }
235
+
236
+ try {
237
+ return await source.verify(request);
238
+ } catch (error) {
239
+ const cause = errorFromUnknown(error);
240
+ return Result.err(
241
+ new InternalError(`Webhook source "${source.id}" verification threw`, {
242
+ cause,
243
+ })
244
+ );
245
+ }
246
+ };
247
+
248
+ export function webhook<TOutput>(
249
+ id: string,
250
+ spec: WebhookSpec<TOutput>
251
+ ): WebhookSource<TOutput>;
252
+ export function webhook<TOutput>(
253
+ spec: WebhookSpec<TOutput> & { readonly id: string }
254
+ ): WebhookSource<TOutput>;
255
+ export function webhook<TOutput>(
256
+ idOrSpec: string | (WebhookSpec<TOutput> & { readonly id: string }),
257
+ maybeSpec?: WebhookSpec<TOutput>
258
+ ): WebhookSource<TOutput> {
259
+ const id = typeof idOrSpec === 'string' ? idOrSpec : idOrSpec.id;
260
+ // oxlint-disable-next-line no-non-null-assertion -- overload guarantees maybeSpec when idOrSpec is string
261
+ const spec = typeof idOrSpec === 'string' ? maybeSpec! : idOrSpec;
262
+ const normalized = assertWebhookSpec(id, spec);
263
+
264
+ return Object.freeze({
265
+ id,
266
+ kind: 'webhook' as const,
267
+ method: normalized.method,
268
+ parse: spec.parse,
269
+ path: normalized.path,
270
+ ...(spec.meta === undefined
271
+ ? {}
272
+ : { meta: Object.freeze({ ...spec.meta }) }),
273
+ ...(spec.payload === undefined ? {} : { payload: spec.payload }),
274
+ ...(spec.verify === undefined ? {} : { verify: spec.verify }),
275
+ });
276
+ }
package/src/workspace.ts CHANGED
@@ -71,7 +71,7 @@ export const isInsideWorkspace = (
71
71
  /**
72
72
  * Returns the relative path from `workspaceRoot` to `filePath`.
73
73
  */
74
- export const getRelativePath = (
74
+ export const deriveRelativePath = (
75
75
  filePath: string,
76
76
  workspaceRoot: string
77
77
  ): string => relative(resolve(workspaceRoot), resolve(filePath));
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Shared Zod wrapper helpers.
3
+ *
4
+ * These helpers walk the wrapper layers of a Zod schema (default, optional,
5
+ * nullable) and rebuild the schema without any default wrappers so that
6
+ * downstream `.partial()` calls do not silently re-materialize defaults during
7
+ * validation. Both `@ontrails/core/trails/derive-trail.ts` and
8
+ * `@ontrails/store/src/store.ts` previously shipped near-identical copies of
9
+ * this walk — extracting them here keeps the wrapper semantics authored in one
10
+ * place so the two call sites cannot drift.
11
+ *
12
+ * Canonical behavior: `optional` wrappers are intentionally dropped. Every
13
+ * current call site applies `.partial()` to the stripped schema, which
14
+ * re-introduces `optional` across the whole shape. Preserving `optional` here
15
+ * would be redundant at best and would produce an `OptionalOptional<T>` shape
16
+ * in edge cases at worst. `nullable` wrappers are preserved because nullability
17
+ * is a semantic constraint that `.partial()` does not reintroduce.
18
+ */
19
+
20
+ import type { z } from 'zod';
21
+
22
+ const isWrapperType = (
23
+ type: string
24
+ ): type is 'default' | 'nullable' | 'optional' =>
25
+ type === 'default' || type === 'nullable' || type === 'optional';
26
+
27
+ const readInnerType = (schema: z.ZodType): z.ZodType =>
28
+ (schema.def as unknown as { innerType: z.ZodType }).innerType;
29
+
30
+ /**
31
+ * Strip `default` wrappers from a Zod type so that partial update schemas do
32
+ * not silently re-materialize defaults. Walks through all wrapper layers
33
+ * (`default`, `optional`, `nullable`), drops defaults, drops `optional` (the
34
+ * downstream `.partial()` reintroduces it across the whole shape), and
35
+ * preserves `nullable` so explicit nullability survives.
36
+ */
37
+ export const stripDefaultWrappers = (schema: z.ZodType): z.ZodType => {
38
+ let current = schema;
39
+ let isNullable = false;
40
+
41
+ while (isWrapperType(current.def.type)) {
42
+ if (current.def.type === 'nullable') {
43
+ isNullable = true;
44
+ }
45
+ current = readInnerType(current);
46
+ }
47
+
48
+ return isNullable ? current.nullable() : current;
49
+ };
50
+
51
+ type AnyObjectSchema = z.ZodObject<Record<string, z.ZodType>>;
52
+
53
+ const asObjectSchema = (schema: z.ZodType): AnyObjectSchema =>
54
+ schema as unknown as AnyObjectSchema;
55
+
56
+ /**
57
+ * Apply {@link stripDefaultWrappers} to every field in a Zod object shape and
58
+ * return the resulting shape record. Callers typically feed the result to
59
+ * `.extend()` + `.partial()`.
60
+ */
61
+ export const stripDefaultsFromShape = (
62
+ schema: z.ZodType
63
+ ): Record<string, z.ZodType> => {
64
+ const stripped: Record<string, z.ZodType> = {};
65
+ const objectSchema = asObjectSchema(schema);
66
+
67
+ for (const [field, value] of Object.entries(objectSchema.shape)) {
68
+ stripped[field] = stripDefaultWrappers(value);
69
+ }
70
+
71
+ return stripped;
72
+ };
@@ -1 +0,0 @@
1
- $ tsc -b
@@ -1,3 +0,0 @@
1
- $ oxlint ./src
2
- Found 0 warnings and 0 errors.
3
- Finished in 60ms on 76 files with 93 rules using 24 threads.
@@ -1 +0,0 @@
1
- $ tsc --noEmit
@@ -1,39 +0,0 @@
1
- import type { Result } from './result.js';
2
- /** Options for search queries */
3
- export interface SearchOptions {
4
- readonly limit?: number | undefined;
5
- readonly offset?: number | undefined;
6
- readonly filters?: Readonly<Record<string, unknown>> | undefined;
7
- }
8
- /** A single search hit */
9
- export interface SearchResult {
10
- readonly id: string;
11
- readonly score: number;
12
- readonly document: Readonly<Record<string, unknown>>;
13
- }
14
- /** Options for storage writes */
15
- export interface StorageOptions {
16
- /** Time-to-live in milliseconds */
17
- readonly ttl?: number | undefined;
18
- }
19
- /** Full-text / vector index adapter */
20
- export interface IndexAdapter {
21
- index(id: string, document: Readonly<Record<string, unknown>>): Promise<Result<void, Error>>;
22
- search(query: string, options?: SearchOptions): Promise<Result<readonly SearchResult[], Error>>;
23
- remove(id: string): Promise<Result<void, Error>>;
24
- }
25
- /** Key-value storage adapter */
26
- export interface StorageAdapter {
27
- get(key: string): Promise<Result<unknown, Error>>;
28
- set(key: string, value: unknown, options?: StorageOptions): Promise<Result<void, Error>>;
29
- delete(key: string): Promise<Result<void, Error>>;
30
- has(key: string): Promise<Result<boolean, Error>>;
31
- }
32
- /** Cache adapter with typed get/set and bulk clear */
33
- export interface CacheAdapter {
34
- get<T>(key: string): Promise<Result<T | undefined, Error>>;
35
- set<T>(key: string, value: T, options?: StorageOptions): Promise<Result<void, Error>>;
36
- delete(key: string): Promise<Result<void, Error>>;
37
- clear(): Promise<Result<void, Error>>;
38
- }
39
- //# sourceMappingURL=adapters.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAM1C,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;CAClE;AAED,0BAA0B;AAC1B,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACtD;AAED,iCAAiC;AACjC,MAAM,WAAW,cAAc;IAC7B,mCAAmC;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAMD,uCAAuC;AACvC,MAAM,WAAW,YAAY;IAC3B,KAAK,CACH,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAC1C,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IAEhC,MAAM,CACJ,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,MAAM,CAAC,SAAS,YAAY,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;IAEnD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;CAClD;AAED,gCAAgC;AAChC,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IAClD,GAAG,CACD,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IAChC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IAClD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;CACnD;AAED,sDAAsD;AACtD,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;IAC3D,GAAG,CAAC,CAAC,EACH,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IAChC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IAClD,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;CACvC"}
package/dist/adapters.js DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=adapters.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"adapters.js","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":""}
@@ -1,20 +0,0 @@
1
- /**
2
- * BlobRef — a frozen reference to binary data for @ontrails/core.
3
- */
4
- /** Immutable reference to a blob of binary data. */
5
- export interface BlobRef {
6
- readonly name: string;
7
- readonly mimeType: string;
8
- readonly size: number;
9
- readonly data: Uint8Array | ReadableStream<Uint8Array>;
10
- }
11
- /** Creates a frozen BlobRef. */
12
- export declare const createBlobRef: (options: {
13
- name: string;
14
- mimeType: string;
15
- size: number;
16
- data: Uint8Array | ReadableStream<Uint8Array>;
17
- }) => BlobRef;
18
- /** Type guard for BlobRef-shaped values. */
19
- export declare const isBlobRef: (value: unknown) => value is BlobRef;
20
- //# sourceMappingURL=blob-ref.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"blob-ref.d.ts","sourceRoot":"","sources":["../src/blob-ref.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,oDAAoD;AACpD,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;CACxD;AAED,gCAAgC;AAChC,eAAO,MAAM,aAAa,GAAI,SAAS;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;CAC/C,KAAG,OAMA,CAAC;AAEL,4CAA4C;AAC5C,eAAO,MAAM,SAAS,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,OAWnD,CAAC"}
package/dist/blob-ref.js DELETED
@@ -1,22 +0,0 @@
1
- /**
2
- * BlobRef — a frozen reference to binary data for @ontrails/core.
3
- */
4
- /** Creates a frozen BlobRef. */
5
- export const createBlobRef = (options) => Object.freeze({
6
- data: options.data,
7
- mimeType: options.mimeType,
8
- name: options.name,
9
- size: options.size,
10
- });
11
- /** Type guard for BlobRef-shaped values. */
12
- export const isBlobRef = (value) => {
13
- if (typeof value !== 'object' || value === null) {
14
- return false;
15
- }
16
- const obj = value;
17
- return (typeof obj['name'] === 'string' &&
18
- typeof obj['mimeType'] === 'string' &&
19
- typeof obj['size'] === 'number' &&
20
- (obj['data'] instanceof Uint8Array || obj['data'] instanceof ReadableStream));
21
- };
22
- //# sourceMappingURL=blob-ref.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"blob-ref.js","sourceRoot":"","sources":["../src/blob-ref.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,gCAAgC;AAChC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,OAK7B,EAAW,EAAE,CACZ,MAAM,CAAC,MAAM,CAAC;IACZ,IAAI,EAAE,OAAO,CAAC,IAAI;IAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;IAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;IAClB,IAAI,EAAE,OAAO,CAAC,IAAI;CACnB,CAAC,CAAC;AAEL,4CAA4C;AAC5C,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAc,EAAoB,EAAE;IAC5D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,GAAG,GAAG,KAAgC,CAAC;IAC7C,OAAO,CACL,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,QAAQ;QAC/B,OAAO,GAAG,CAAC,UAAU,CAAC,KAAK,QAAQ;QACnC,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,QAAQ;QAC/B,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,UAAU,IAAI,GAAG,CAAC,MAAM,CAAC,YAAY,cAAc,CAAC,CAC7E,CAAC;AACJ,CAAC,CAAC"}
package/dist/branded.d.ts DELETED
@@ -1,36 +0,0 @@
1
- /**
2
- * Branded types and validated constructors for @ontrails/core.
3
- *
4
- * Branded types enforce domain constraints at the type level while remaining
5
- * plain primitives at runtime. Factory functions return Result so callers
6
- * handle validation failures explicitly.
7
- */
8
- import { ValidationError } from './errors.js';
9
- import type { Result } from './result.js';
10
- /** Attach a phantom tag to a base type. */
11
- export type Branded<T, Tag extends string> = T & {
12
- readonly __brand: Tag;
13
- };
14
- /** Brand a value. No validation — use factory functions for safe construction. */
15
- export declare const brand: <T, Tag extends string>(_tag: Tag, value: T) => Branded<T, Tag>;
16
- /** Strip the brand and recover the underlying value. */
17
- export declare const unbrand: <T>(value: Branded<T, string>) => T;
18
- export type UUID = Branded<string, 'UUID'>;
19
- export type Email = Branded<string, 'Email'>;
20
- export type NonEmptyString = Branded<string, 'NonEmptyString'>;
21
- export type PositiveInt = Branded<number, 'PositiveInt'>;
22
- export declare const uuid: (value: string) => Result<UUID, ValidationError>;
23
- export declare const email: (value: string) => Result<Email, ValidationError>;
24
- export declare const nonEmptyString: (value: string) => Result<NonEmptyString, ValidationError>;
25
- export declare const positiveInt: (value: number) => Result<PositiveInt, ValidationError>;
26
- /**
27
- * Generate a random alphanumeric ID.
28
- * Runtime-agnostic: uses `crypto.getRandomValues`.
29
- */
30
- export declare const shortId: (length?: number) => string;
31
- /**
32
- * Produce a deterministic hex hash from an input string.
33
- * Uses a simple FNV-1a 32-bit hash — good enough for non-cryptographic IDs.
34
- */
35
- export declare const hashId: (input: string) => string;
36
- //# sourceMappingURL=branded.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"branded.d.ts","sourceRoot":"","sources":["../src/branded.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAO1C,2CAA2C;AAC3C,MAAM,MAAM,OAAO,CAAC,CAAC,EAAE,GAAG,SAAS,MAAM,IAAI,CAAC,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC;AAE3E,kFAAkF;AAClF,eAAO,MAAM,KAAK,GAAI,CAAC,EAAE,GAAG,SAAS,MAAM,EACzC,MAAM,GAAG,EACT,OAAO,CAAC,KACP,OAAO,CAAC,CAAC,EAAE,GAAG,CAA6B,CAAC;AAE/C,wDAAwD;AACxD,eAAO,MAAM,OAAO,GAAI,CAAC,EAAE,OAAO,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,KAAG,CAAe,CAAC;AAMvE,MAAM,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAC3C,MAAM,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC7C,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;AAC/D,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAezD,eAAO,MAAM,IAAI,GAAI,OAAO,MAAM,KAAG,MAAM,CAAC,IAAI,EAAE,eAAe,CAShE,CAAC;AAEF,eAAO,MAAM,KAAK,GAAI,OAAO,MAAM,KAAG,MAAM,CAAC,KAAK,EAAE,eAAe,CASlE,CAAC;AAEF,eAAO,MAAM,cAAc,GACzB,OAAO,MAAM,KACZ,MAAM,CAAC,cAAc,EAAE,eAAe,CAKxC,CAAC;AAEF,eAAO,MAAM,WAAW,GACtB,OAAO,MAAM,KACZ,MAAM,CAAC,WAAW,EAAE,eAAe,CASrC,CAAC;AASF;;;GAGG;AACH,eAAO,MAAM,OAAO,GAAI,eAAU,KAAG,MAWpC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,MAAM,GAAI,OAAO,MAAM,KAAG,MAYtC,CAAC"}
package/dist/branded.js DELETED
@@ -1,89 +0,0 @@
1
- /**
2
- * Branded types and validated constructors for @ontrails/core.
3
- *
4
- * Branded types enforce domain constraints at the type level while remaining
5
- * plain primitives at runtime. Factory functions return Result so callers
6
- * handle validation failures explicitly.
7
- */
8
- import { ValidationError } from './errors.js';
9
- import { Result as R } from './result.js';
10
- /** Brand a value. No validation — use factory functions for safe construction. */
11
- export const brand = (_tag, value) => value;
12
- /** Strip the brand and recover the underlying value. */
13
- export const unbrand = (value) => value;
14
- // ---------------------------------------------------------------------------
15
- // Validation patterns
16
- // ---------------------------------------------------------------------------
17
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
18
- const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
19
- // ---------------------------------------------------------------------------
20
- // Factory functions — each returns Result<BrandedType, ValidationError>
21
- // ---------------------------------------------------------------------------
22
- export const uuid = (value) => {
23
- if (!UUID_RE.test(value)) {
24
- return R.err(new ValidationError(`Invalid UUID: "${value}"`, {
25
- context: { value },
26
- }));
27
- }
28
- return R.ok(value);
29
- };
30
- export const email = (value) => {
31
- if (!EMAIL_RE.test(value)) {
32
- return R.err(new ValidationError(`Invalid email: "${value}"`, {
33
- context: { value },
34
- }));
35
- }
36
- return R.ok(value);
37
- };
38
- export const nonEmptyString = (value) => {
39
- if (value.length === 0) {
40
- return R.err(new ValidationError('String must not be empty'));
41
- }
42
- return R.ok(value);
43
- };
44
- export const positiveInt = (value) => {
45
- if (!Number.isInteger(value) || value <= 0) {
46
- return R.err(new ValidationError(`Expected positive integer, got ${value}`, {
47
- context: { value },
48
- }));
49
- }
50
- return R.ok(value);
51
- };
52
- // ---------------------------------------------------------------------------
53
- // ID utilities
54
- // ---------------------------------------------------------------------------
55
- const ALPHANUMERIC = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
56
- /**
57
- * Generate a random alphanumeric ID.
58
- * Runtime-agnostic: uses `crypto.getRandomValues`.
59
- */
60
- export const shortId = (length = 8) => {
61
- const bytes = new Uint8Array(length);
62
- crypto.getRandomValues(bytes);
63
- let id = '';
64
- for (let i = 0; i < length; i += 1) {
65
- const byte = bytes[i];
66
- if (byte !== undefined) {
67
- id += ALPHANUMERIC[byte % ALPHANUMERIC.length];
68
- }
69
- }
70
- return id;
71
- };
72
- /**
73
- * Produce a deterministic hex hash from an input string.
74
- * Uses a simple FNV-1a 32-bit hash — good enough for non-cryptographic IDs.
75
- */
76
- export const hashId = (input) => {
77
- // FNV offset basis
78
- let hash = 2_166_136_261;
79
- for (let i = 0; i < input.length; i += 1) {
80
- // oxlint-disable-next-line no-bitwise -- FNV-1a hash requires XOR
81
- hash ^= input.codePointAt(i) ?? 0;
82
- // FNV prime
83
- hash = Math.imul(hash, 0x01_00_01_93);
84
- }
85
- // Convert to unsigned 32-bit then hex
86
- // oxlint-disable-next-line no-bitwise, prefer-math-trunc -- unsigned right shift needed for u32 conversion (Math.trunc differs semantically)
87
- return (hash >>> 0).toString(16).padStart(8, '0');
88
- };
89
- //# sourceMappingURL=branded.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"branded.js","sourceRoot":"","sources":["../src/branded.ts"],"names":[],"mappings":"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"}
@@ -1,31 +0,0 @@
1
- /**
2
- * Collection utilities and type helpers for @ontrails/core.
3
- */
4
- /** Recursively make every property optional. */
5
- export type DeepPartial<T> = {
6
- [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
7
- };
8
- /** Flatten an intersection into a single object type for better IDE display. */
9
- export type Prettify<T> = {
10
- [K in keyof T]: T[K];
11
- } & {};
12
- /** Require at least one property from T. */
13
- export type AtLeastOne<T> = {
14
- [K in keyof T]-?: Pick<T, K> & Partial<Omit<T, K>>;
15
- }[keyof T];
16
- /** A tuple with at least one element. */
17
- export type NonEmptyArray<T> = [T, ...T[]];
18
- /** Narrows a readonly array to a NonEmptyArray. */
19
- export declare const isNonEmptyArray: <T>(array: readonly T[]) => array is NonEmptyArray<T>;
20
- /** Split an array into chunks of at most `size` elements. */
21
- export declare const chunk: <T>(array: readonly T[], size: number) => T[][];
22
- /**
23
- * Remove duplicate items. When `key` is provided, uniqueness is determined
24
- * by the return value of the key function; otherwise strict equality is used.
25
- */
26
- export declare const dedupe: <T>(array: readonly T[], key?: (item: T) => unknown) => T[];
27
- /** Group items by a string key. */
28
- export declare const groupBy: <T>(array: readonly T[], key: (item: T) => string) => Record<string, T[]>;
29
- /** Return a new sorted array based on a key function (ascending). */
30
- export declare const sortBy: <T>(array: readonly T[], key: (item: T) => string | number) => T[];
31
- //# sourceMappingURL=collections.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"collections.d.ts","sourceRoot":"","sources":["../src/collections.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,gDAAgD;AAChD,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;KAC1B,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAChE,CAAC;AAEF,gFAAgF;AAEhF,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG,EAAE,CAAC;AAExD,4CAA4C;AAC5C,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI;KACzB,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CACnD,CAAC,MAAM,CAAC,CAAC,CAAC;AAEX,yCAAyC;AACzC,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;AAM3C,mDAAmD;AACnD,eAAO,MAAM,eAAe,GAAI,CAAC,EAC/B,OAAO,SAAS,CAAC,EAAE,KAClB,KAAK,IAAI,aAAa,CAAC,CAAC,CAAqB,CAAC;AAMjD,6DAA6D;AAC7D,eAAO,MAAM,KAAK,GAAI,CAAC,EAAE,OAAO,SAAS,CAAC,EAAE,EAAE,MAAM,MAAM,KAAG,CAAC,EAAE,EAS/D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,EACtB,OAAO,SAAS,CAAC,EAAE,EACnB,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,KACzB,CAAC,EAcH,CAAC;AAEF,mCAAmC;AACnC,eAAO,MAAM,OAAO,GAAI,CAAC,EACvB,OAAO,SAAS,CAAC,EAAE,EACnB,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,KACvB,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,CAOpB,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,MAAM,GAAI,CAAC,EACtB,OAAO,SAAS,CAAC,EAAE,EACnB,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,KAChC,CAAC,EAQA,CAAC"}