@ontrails/core 1.0.0-beta.4 → 1.0.0-beta.41

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 (247) hide show
  1. package/CHANGELOG.md +412 -12
  2. package/README.md +112 -28
  3. package/package.json +11 -1
  4. package/src/activation-provenance.ts +116 -0
  5. package/src/activation-source-compatibility.ts +430 -0
  6. package/src/activation-source-projection.ts +227 -0
  7. package/src/activation-source.ts +93 -0
  8. package/src/blob-ref.ts +51 -0
  9. package/src/branded.ts +1 -1
  10. package/src/compose-batch.ts +69 -0
  11. package/src/compose-schema.ts +36 -0
  12. package/src/context.ts +59 -11
  13. package/src/derive.ts +271 -19
  14. package/src/detours.ts +8 -0
  15. package/src/diagnostics.ts +21 -0
  16. package/src/draft.ts +350 -0
  17. package/src/entity.ts +346 -0
  18. package/src/error-projection.ts +51 -0
  19. package/src/errors.ts +335 -48
  20. package/src/execute.ts +1577 -0
  21. package/src/fire.ts +1172 -0
  22. package/src/glob.ts +81 -0
  23. package/src/index.ts +568 -32
  24. package/src/internal/fork-ctx.ts +69 -0
  25. package/src/layer-projection.ts +193 -0
  26. package/src/layer.ts +43 -6
  27. package/src/observe.ts +361 -0
  28. package/src/path-scope.ts +66 -0
  29. package/src/path-security.ts +11 -3
  30. package/src/permits.ts +12 -0
  31. package/src/queue.ts +163 -0
  32. package/src/redaction/patterns.ts +6 -3
  33. package/src/resilience.ts +11 -11
  34. package/src/resource-config.ts +804 -0
  35. package/src/resource.ts +194 -0
  36. package/src/result.ts +18 -0
  37. package/src/run.ts +76 -0
  38. package/src/runtime-builtins.ts +69 -0
  39. package/src/schedule-runtime.ts +689 -0
  40. package/src/schedule.ts +326 -0
  41. package/src/serialization.ts +92 -55
  42. package/src/sha256.ts +136 -0
  43. package/src/signal-diagnostics.ts +633 -0
  44. package/src/signal-ref.ts +111 -0
  45. package/src/signal.ts +104 -0
  46. package/src/store/accessor-protocol.ts +56 -0
  47. package/src/store/index.ts +4 -0
  48. package/src/structured-examples.ts +248 -0
  49. package/src/surface-derivation.ts +91 -0
  50. package/src/surface-filter.ts +101 -0
  51. package/src/surface-overlay.ts +694 -0
  52. package/src/surface-versioning.ts +42 -0
  53. package/src/topo.ts +762 -38
  54. package/src/tracing.ts +346 -0
  55. package/src/trail-id-glob.ts +15 -0
  56. package/src/trail.ts +1244 -34
  57. package/src/trails/derive-trail.ts +835 -0
  58. package/src/trails/index.ts +9 -0
  59. package/src/trails/ingest.ts +152 -0
  60. package/src/trails-db.ts +212 -0
  61. package/src/transport-error-map.ts +174 -0
  62. package/src/type-utils.ts +87 -0
  63. package/src/types.ts +254 -12
  64. package/src/validate-established-topo.ts +73 -0
  65. package/src/validate-topo.ts +569 -55
  66. package/src/validation.ts +152 -4
  67. package/src/version-marker.ts +716 -0
  68. package/src/version-resolution.ts +308 -0
  69. package/src/version-runtime.ts +120 -0
  70. package/src/webhook.ts +461 -0
  71. package/src/workspace.ts +183 -16
  72. package/src/zod-wrappers.ts +72 -0
  73. package/.turbo/turbo-build.log +0 -1
  74. package/.turbo/turbo-lint.log +0 -3
  75. package/.turbo/turbo-typecheck.log +0 -1
  76. package/dist/adapters.d.ts +0 -39
  77. package/dist/adapters.d.ts.map +0 -1
  78. package/dist/adapters.js +0 -2
  79. package/dist/adapters.js.map +0 -1
  80. package/dist/blob-ref.d.ts +0 -20
  81. package/dist/blob-ref.d.ts.map +0 -1
  82. package/dist/blob-ref.js +0 -22
  83. package/dist/blob-ref.js.map +0 -1
  84. package/dist/branded.d.ts +0 -36
  85. package/dist/branded.d.ts.map +0 -1
  86. package/dist/branded.js +0 -89
  87. package/dist/branded.js.map +0 -1
  88. package/dist/collections.d.ts +0 -31
  89. package/dist/collections.d.ts.map +0 -1
  90. package/dist/collections.js +0 -60
  91. package/dist/collections.js.map +0 -1
  92. package/dist/context.d.ts +0 -10
  93. package/dist/context.d.ts.map +0 -1
  94. package/dist/context.js +0 -15
  95. package/dist/context.js.map +0 -1
  96. package/dist/derive.d.ts +0 -33
  97. package/dist/derive.d.ts.map +0 -1
  98. package/dist/derive.js +0 -125
  99. package/dist/derive.js.map +0 -1
  100. package/dist/errors.d.ts +0 -83
  101. package/dist/errors.d.ts.map +0 -1
  102. package/dist/errors.js +0 -142
  103. package/dist/errors.js.map +0 -1
  104. package/dist/event.d.ts +0 -45
  105. package/dist/event.d.ts.map +0 -1
  106. package/dist/event.js +0 -17
  107. package/dist/event.js.map +0 -1
  108. package/dist/fetch.d.ts +0 -15
  109. package/dist/fetch.d.ts.map +0 -1
  110. package/dist/fetch.js +0 -102
  111. package/dist/fetch.js.map +0 -1
  112. package/dist/guards.d.ts +0 -17
  113. package/dist/guards.d.ts.map +0 -1
  114. package/dist/guards.js +0 -25
  115. package/dist/guards.js.map +0 -1
  116. package/dist/health.d.ts +0 -18
  117. package/dist/health.d.ts.map +0 -1
  118. package/dist/health.js +0 -5
  119. package/dist/health.js.map +0 -1
  120. package/dist/index.d.ts +0 -34
  121. package/dist/index.d.ts.map +0 -1
  122. package/dist/index.js +0 -38
  123. package/dist/index.js.map +0 -1
  124. package/dist/job.d.ts +0 -24
  125. package/dist/job.d.ts.map +0 -1
  126. package/dist/job.js +0 -17
  127. package/dist/job.js.map +0 -1
  128. package/dist/layer.d.ts +0 -17
  129. package/dist/layer.d.ts.map +0 -1
  130. package/dist/layer.js +0 -21
  131. package/dist/layer.js.map +0 -1
  132. package/dist/path-security.d.ts +0 -28
  133. package/dist/path-security.d.ts.map +0 -1
  134. package/dist/path-security.js +0 -63
  135. package/dist/path-security.js.map +0 -1
  136. package/dist/patterns/bulk.d.ts +0 -15
  137. package/dist/patterns/bulk.d.ts.map +0 -1
  138. package/dist/patterns/bulk.js +0 -14
  139. package/dist/patterns/bulk.js.map +0 -1
  140. package/dist/patterns/change.d.ts +0 -10
  141. package/dist/patterns/change.d.ts.map +0 -1
  142. package/dist/patterns/change.js +0 -10
  143. package/dist/patterns/change.js.map +0 -1
  144. package/dist/patterns/date-range.d.ts +0 -10
  145. package/dist/patterns/date-range.d.ts.map +0 -1
  146. package/dist/patterns/date-range.js +0 -10
  147. package/dist/patterns/date-range.js.map +0 -1
  148. package/dist/patterns/index.d.ts +0 -9
  149. package/dist/patterns/index.d.ts.map +0 -1
  150. package/dist/patterns/index.js +0 -9
  151. package/dist/patterns/index.js.map +0 -1
  152. package/dist/patterns/pagination.d.ts +0 -18
  153. package/dist/patterns/pagination.d.ts.map +0 -1
  154. package/dist/patterns/pagination.js +0 -18
  155. package/dist/patterns/pagination.js.map +0 -1
  156. package/dist/patterns/progress.d.ts +0 -11
  157. package/dist/patterns/progress.d.ts.map +0 -1
  158. package/dist/patterns/progress.js +0 -11
  159. package/dist/patterns/progress.js.map +0 -1
  160. package/dist/patterns/sorting.d.ts +0 -13
  161. package/dist/patterns/sorting.d.ts.map +0 -1
  162. package/dist/patterns/sorting.js +0 -10
  163. package/dist/patterns/sorting.js.map +0 -1
  164. package/dist/patterns/status.d.ts +0 -15
  165. package/dist/patterns/status.d.ts.map +0 -1
  166. package/dist/patterns/status.js +0 -9
  167. package/dist/patterns/status.js.map +0 -1
  168. package/dist/patterns/timestamps.d.ts +0 -10
  169. package/dist/patterns/timestamps.d.ts.map +0 -1
  170. package/dist/patterns/timestamps.js +0 -10
  171. package/dist/patterns/timestamps.js.map +0 -1
  172. package/dist/redaction/index.d.ts +0 -4
  173. package/dist/redaction/index.d.ts.map +0 -1
  174. package/dist/redaction/index.js +0 -3
  175. package/dist/redaction/index.js.map +0 -1
  176. package/dist/redaction/patterns.d.ts +0 -9
  177. package/dist/redaction/patterns.d.ts.map +0 -1
  178. package/dist/redaction/patterns.js +0 -39
  179. package/dist/redaction/patterns.js.map +0 -1
  180. package/dist/redaction/redactor.d.ts +0 -27
  181. package/dist/redaction/redactor.d.ts.map +0 -1
  182. package/dist/redaction/redactor.js +0 -89
  183. package/dist/redaction/redactor.js.map +0 -1
  184. package/dist/resilience.d.ts +0 -34
  185. package/dist/resilience.d.ts.map +0 -1
  186. package/dist/resilience.js +0 -164
  187. package/dist/resilience.js.map +0 -1
  188. package/dist/result.d.ts +0 -57
  189. package/dist/result.d.ts.map +0 -1
  190. package/dist/result.js +0 -156
  191. package/dist/result.js.map +0 -1
  192. package/dist/serialization.d.ts +0 -27
  193. package/dist/serialization.d.ts.map +0 -1
  194. package/dist/serialization.js +0 -153
  195. package/dist/serialization.js.map +0 -1
  196. package/dist/topo.d.ts +0 -16
  197. package/dist/topo.d.ts.map +0 -1
  198. package/dist/topo.js +0 -66
  199. package/dist/topo.js.map +0 -1
  200. package/dist/trail.d.ts +0 -89
  201. package/dist/trail.d.ts.map +0 -1
  202. package/dist/trail.js +0 -18
  203. package/dist/trail.js.map +0 -1
  204. package/dist/types.d.ts +0 -46
  205. package/dist/types.d.ts.map +0 -1
  206. package/dist/types.js +0 -2
  207. package/dist/types.js.map +0 -1
  208. package/dist/validate-topo.d.ts +0 -24
  209. package/dist/validate-topo.d.ts.map +0 -1
  210. package/dist/validate-topo.js +0 -158
  211. package/dist/validate-topo.js.map +0 -1
  212. package/dist/validation.d.ts +0 -27
  213. package/dist/validation.d.ts.map +0 -1
  214. package/dist/validation.js +0 -134
  215. package/dist/validation.js.map +0 -1
  216. package/dist/workspace.d.ts +0 -25
  217. package/dist/workspace.d.ts.map +0 -1
  218. package/dist/workspace.js +0 -57
  219. package/dist/workspace.js.map +0 -1
  220. package/src/__tests__/blob-ref.test.ts +0 -103
  221. package/src/__tests__/branded.test.ts +0 -148
  222. package/src/__tests__/collections.test.ts +0 -126
  223. package/src/__tests__/context.test.ts +0 -66
  224. package/src/__tests__/derive.test.ts +0 -203
  225. package/src/__tests__/errors.test.ts +0 -309
  226. package/src/__tests__/event.test.ts +0 -82
  227. package/src/__tests__/fetch.test.ts +0 -217
  228. package/src/__tests__/guards.test.ts +0 -102
  229. package/src/__tests__/job.test.ts +0 -98
  230. package/src/__tests__/layer.test.ts +0 -212
  231. package/src/__tests__/path-security.test.ts +0 -114
  232. package/src/__tests__/patterns.test.ts +0 -273
  233. package/src/__tests__/redaction.test.ts +0 -244
  234. package/src/__tests__/resilience.test.ts +0 -246
  235. package/src/__tests__/result.test.ts +0 -155
  236. package/src/__tests__/serialization.test.ts +0 -401
  237. package/src/__tests__/topo.test.ts +0 -181
  238. package/src/__tests__/trail.test.ts +0 -217
  239. package/src/__tests__/validate-topo.test.ts +0 -278
  240. package/src/__tests__/validation.test.ts +0 -283
  241. package/src/__tests__/workspace.test.ts +0 -183
  242. package/src/adapters.ts +0 -68
  243. package/src/event.ts +0 -77
  244. package/src/health.ts +0 -23
  245. package/src/job.ts +0 -20
  246. package/tsconfig.json +0 -9
  247. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,9 @@
1
+ export { deriveTrail } from './derive-trail.js';
2
+ export type {
3
+ DeriveTrailInput,
4
+ DeriveTrailOperation,
5
+ DeriveTrailOutput,
6
+ DeriveTrailSpec,
7
+ } from './derive-trail.js';
8
+ export { ingest } from './ingest.js';
9
+ export type { IngestOptions, IngestTransform } from './ingest.js';
@@ -0,0 +1,152 @@
1
+ import { z } from 'zod';
2
+
3
+ import { InternalError } from '../errors.js';
4
+ import { composeLayers } from '../layer.js';
5
+ import type { Layer } from '../layer.js';
6
+ import { Result } from '../result.js';
7
+ import type { Signal } from '../signal.js';
8
+ import { trail } from '../trail.js';
9
+ import type { Trail, TrailExample, TrailSpec } from '../trail.js';
10
+ import type { TrailContext } from '../types.js';
11
+
12
+ type SchemaValue<TSchema extends z.ZodType> = z.output<TSchema>;
13
+
14
+ type ExampleBearingSchema<TSchema extends z.ZodType> = TSchema & {
15
+ readonly examples?: readonly Partial<SchemaValue<TSchema>>[] | undefined;
16
+ };
17
+
18
+ interface IngestBaseOptions<TSchema extends z.ZodType, TSignal> extends Omit<
19
+ TrailSpec<SchemaValue<TSchema>, void>,
20
+ | 'implementation'
21
+ | 'examples'
22
+ | 'fires'
23
+ | 'input'
24
+ | 'intent'
25
+ | 'output'
26
+ | 'pattern'
27
+ > {
28
+ /** Override the derived trail id. Defaults to `${signal}.ingest`. */
29
+ readonly id?: string | undefined;
30
+ /** Validated external payload shape. */
31
+ readonly schema: TSchema;
32
+ /** Signal to fire after verification and optional transformation. */
33
+ readonly signal: Signal<TSignal>;
34
+ /** Optional per-trail verification layer, e.g. HMAC signature checks. */
35
+ readonly verify?: Layer | undefined;
36
+ }
37
+
38
+ export type IngestTransform<TInput, TSignal> = (
39
+ payload: TInput,
40
+ ctx: TrailContext
41
+ ) => TSignal | Promise<TSignal>;
42
+
43
+ export interface IngestOptions<
44
+ TSchema extends z.ZodType,
45
+ TSignal,
46
+ > extends IngestBaseOptions<TSchema, TSignal> {
47
+ readonly transform?:
48
+ | IngestTransform<SchemaValue<TSchema>, TSignal>
49
+ | undefined;
50
+ }
51
+
52
+ const deriveExampleName = (signalId: string, index: number): string =>
53
+ `Ingest ${signalId} ${index + 1}`;
54
+
55
+ const deriveExamples = <TSchema extends z.ZodType>(
56
+ schema: ExampleBearingSchema<TSchema>,
57
+ signalId: string
58
+ ): readonly TrailExample<SchemaValue<TSchema>, void>[] | undefined => {
59
+ const { examples } = schema;
60
+ if (examples === undefined || examples.length === 0) {
61
+ return undefined;
62
+ }
63
+
64
+ return Object.freeze(
65
+ examples.map((example, index) => ({
66
+ input: example,
67
+ name: deriveExampleName(signalId, index),
68
+ }))
69
+ );
70
+ };
71
+
72
+ const createIngestImplementation =
73
+ <TSchema extends z.ZodType, TSignal>(
74
+ signalRef: Signal<TSignal>,
75
+ signalId: string,
76
+ trailId: string,
77
+ transform: IngestTransform<SchemaValue<TSchema>, TSignal> | undefined
78
+ ) =>
79
+ async (
80
+ input: SchemaValue<TSchema>,
81
+ ctx: TrailContext
82
+ ): Promise<Result<void, Error>> => {
83
+ if (ctx.fire === undefined) {
84
+ return Result.err(
85
+ new InternalError(
86
+ `ingest("${trailId}") requires topo-backed execution to fire "${signalId}"`
87
+ )
88
+ );
89
+ }
90
+
91
+ try {
92
+ const payload =
93
+ transform === undefined
94
+ ? (input as TSignal)
95
+ : await transform(input, ctx);
96
+ await ctx.fire(signalRef, payload);
97
+ return Result.ok();
98
+ } catch (error) {
99
+ const message = `ingest("${trailId}"): ${error instanceof Error ? error.message : String(error)}`;
100
+ return Result.err(
101
+ error instanceof Error
102
+ ? new InternalError(message, { cause: error })
103
+ : new InternalError(message)
104
+ );
105
+ }
106
+ };
107
+
108
+ export const ingest = <
109
+ TSchema extends z.ZodType,
110
+ TSignal = SchemaValue<TSchema>,
111
+ >(
112
+ options: IngestOptions<TSchema, TSignal>
113
+ ): Trail<SchemaValue<TSchema>, void> => {
114
+ const signalId = options.signal.id;
115
+ const id = options.id ?? `${signalId}.ingest`;
116
+ const { id: _id, schema, signal, transform, verify, ...trailSpec } = options;
117
+ const baseImplementation = createIngestImplementation<TSchema, TSignal>(
118
+ signal,
119
+ signalId,
120
+ id,
121
+ transform
122
+ );
123
+ const baseSpec = {
124
+ ...trailSpec,
125
+ examples: deriveExamples(schema as ExampleBearingSchema<TSchema>, signalId),
126
+ fires: [signal],
127
+ implementation: baseImplementation as TrailSpec<
128
+ unknown,
129
+ unknown
130
+ >['implementation'],
131
+ input: schema as z.ZodType<unknown>,
132
+ intent: 'write',
133
+ output: z.void(),
134
+ pattern: 'ingest',
135
+ } as TrailSpec<unknown, unknown>;
136
+ const baseTrail = trail(id, baseSpec) as Trail<SchemaValue<TSchema>, void>;
137
+
138
+ if (verify === undefined) {
139
+ return baseTrail;
140
+ }
141
+
142
+ // Verification is a per-factory concern, so compose it locally instead of
143
+ // mutating runner-wide layer configuration.
144
+ return Object.freeze({
145
+ ...baseTrail,
146
+ implementation: composeLayers(
147
+ [verify],
148
+ baseTrail,
149
+ baseTrail.implementation
150
+ ),
151
+ }) as Trail<SchemaValue<TSchema>, void>;
152
+ };
@@ -0,0 +1,212 @@
1
+ import type { Database } from 'bun:sqlite';
2
+
3
+ import { NotFoundError } from './errors.js';
4
+ import { loadRuntimeBuiltin } from './runtime-builtins.js';
5
+ import { sha256Hex } from './sha256.js';
6
+
7
+ // Altitude ruling (TRL-1198, ADR-0051 lens): trails-db stays core-owned
8
+ // shared framework infrastructure (ADR-0014) and stays on the barrel —
9
+ // topography, tracing, warden, wayfinder, and the operator app all
10
+ // consume it from `@ontrails/core`. What it may NOT do is assume runtime
11
+ // capabilities eagerly: `bun:sqlite` and the node builtins load lazily at
12
+ // first use so the barrel's module graph stays execution-portable.
13
+ const sqlite = () => loadRuntimeBuiltin('bun:sqlite');
14
+ const fs = () => loadRuntimeBuiltin('node:fs');
15
+ const os = () => loadRuntimeBuiltin('node:os');
16
+ const nodePath = () => loadRuntimeBuiltin('node:path');
17
+
18
+ const TRAILS_DIR = '.trails';
19
+ const TRAILS_DB_FILE = 'trails.db';
20
+ const TRAILS_STORE_DIR = 'trails';
21
+ const TRAILS_PROJECTS_DIR = 'projects';
22
+ const SCHEMA_VERSION_TABLE = 'meta_schema_versions';
23
+ const SQLITE_BUSY_TIMEOUT_MS = 5000;
24
+ const PROJECT_KEY_HASH_LENGTH = 16;
25
+ const PROJECT_KEY_NAME_FALLBACK = 'project';
26
+
27
+ /**
28
+ * Legacy no-op compatibility export.
29
+ *
30
+ * `.trails/` is committed project control, not disposable cache/state. New
31
+ * code should not write a `.trails/.gitignore`; keep this export available for
32
+ * older callers during the pre-1.0 cutover.
33
+ */
34
+ export const WORKSPACE_GITIGNORE_LINES = [] as const;
35
+
36
+ /**
37
+ * Legacy no-op compatibility export. See {@link WORKSPACE_GITIGNORE_LINES}.
38
+ */
39
+ export const WORKSPACE_GITIGNORE_CONTENT = '';
40
+
41
+ export interface TrailsDbLocationOptions {
42
+ readonly env?: Record<string, string | undefined>;
43
+ readonly path?: string;
44
+ readonly rootDir?: string;
45
+ }
46
+
47
+ export interface EnsureSubsystemSchemaOptions {
48
+ readonly migrate: (currentVersion: number) => void;
49
+ readonly subsystem: string;
50
+ readonly version: number;
51
+ }
52
+
53
+ interface SchemaVersionRow {
54
+ readonly version: number;
55
+ }
56
+
57
+ const deriveRootDir = (rootDir?: string): string =>
58
+ nodePath().resolve(rootDir ?? process.cwd());
59
+
60
+ const sanitizeProjectKeyName = (name: string): string => {
61
+ const normalized = name.replaceAll(/[^a-zA-Z0-9._-]+/g, '-');
62
+ return normalized.length > 0 ? normalized : PROJECT_KEY_NAME_FALLBACK;
63
+ };
64
+
65
+ const projectHash = (rootDir: string): string =>
66
+ sha256Hex(rootDir).slice(0, PROJECT_KEY_HASH_LENGTH);
67
+
68
+ export const deriveTrailsProjectKey = (
69
+ options?: TrailsDbLocationOptions
70
+ ): string => {
71
+ const rootDir = deriveRootDir(options?.rootDir);
72
+ return `${sanitizeProjectKeyName(nodePath().basename(rootDir))}-${projectHash(rootDir)}`;
73
+ };
74
+
75
+ export const deriveTrailsStateHome = (
76
+ options?: TrailsDbLocationOptions
77
+ ): string => {
78
+ const env = options?.env ?? process.env;
79
+ return nodePath().resolve(
80
+ env['TRAILS_STATE_HOME'] ??
81
+ env['XDG_STATE_HOME'] ??
82
+ nodePath().join(os().homedir(), '.local', 'state')
83
+ );
84
+ };
85
+
86
+ export const deriveTrailsStateDir = (
87
+ options?: TrailsDbLocationOptions
88
+ ): string =>
89
+ nodePath().join(
90
+ deriveTrailsStateHome(options),
91
+ TRAILS_STORE_DIR,
92
+ TRAILS_PROJECTS_DIR,
93
+ deriveTrailsProjectKey(options)
94
+ );
95
+
96
+ export const deriveTrailsDir = (options?: TrailsDbLocationOptions): string =>
97
+ nodePath().join(deriveRootDir(options?.rootDir), TRAILS_DIR);
98
+
99
+ export const deriveTrailsDbPath = (options?: TrailsDbLocationOptions): string =>
100
+ options?.path
101
+ ? nodePath().resolve(options.path)
102
+ : nodePath().join(deriveTrailsStateDir(options), TRAILS_DB_FILE);
103
+
104
+ const ensureDbParentDir = (dbPath: string): void => {
105
+ fs().mkdirSync(nodePath().dirname(dbPath), { recursive: true });
106
+ };
107
+
108
+ /**
109
+ * Bootstrap the `.trails/` workspace at `rootDir`.
110
+ *
111
+ * Creates only the committed-control directory. Derived cache and observed
112
+ * state live in the per-user Trails store, so this helper intentionally does
113
+ * not create `.trails/cache`, `.trails/state`, or `.trails/.gitignore`.
114
+ */
115
+ export const ensureTrailsWorkspace = (rootDir: string): void => {
116
+ const trailsDir = deriveTrailsDir({ rootDir });
117
+ fs().mkdirSync(trailsDir, { recursive: true });
118
+ };
119
+
120
+ const initializeWritePragmas = (db: Database): void => {
121
+ db.run(`PRAGMA busy_timeout = ${SQLITE_BUSY_TIMEOUT_MS.toString()}`);
122
+ db.run('PRAGMA journal_mode = WAL');
123
+ db.run('PRAGMA synchronous = NORMAL');
124
+ db.run('PRAGMA foreign_keys = ON');
125
+ };
126
+
127
+ const initializeReadPragmas = (db: Database): void => {
128
+ db.run(`PRAGMA busy_timeout = ${SQLITE_BUSY_TIMEOUT_MS.toString()}`);
129
+ db.run('PRAGMA foreign_keys = ON');
130
+ };
131
+
132
+ const ensureSchemaVersionTable = (db: Database): void => {
133
+ db.run(`CREATE TABLE IF NOT EXISTS ${SCHEMA_VERSION_TABLE} (
134
+ subsystem TEXT PRIMARY KEY,
135
+ version INTEGER NOT NULL,
136
+ updated_at TEXT NOT NULL
137
+ )`);
138
+ };
139
+
140
+ const readSubsystemVersion = (db: Database, subsystem: string): number => {
141
+ const row = db
142
+ .query<SchemaVersionRow, [string]>(
143
+ `SELECT version FROM ${SCHEMA_VERSION_TABLE} WHERE subsystem = ?`
144
+ )
145
+ .get(subsystem);
146
+ return row?.version ?? 0;
147
+ };
148
+
149
+ const writeSubsystemVersion = (
150
+ db: Database,
151
+ subsystem: string,
152
+ version: number
153
+ ): void => {
154
+ db.run(
155
+ `INSERT INTO ${SCHEMA_VERSION_TABLE} (subsystem, version, updated_at)
156
+ VALUES (?, ?, ?)
157
+ ON CONFLICT(subsystem) DO UPDATE SET
158
+ version = excluded.version,
159
+ updated_at = excluded.updated_at`,
160
+ [subsystem, version, new Date().toISOString()]
161
+ );
162
+ };
163
+
164
+ export const openWriteTrailsDb = (
165
+ options?: TrailsDbLocationOptions
166
+ ): Database => {
167
+ const rootDir = deriveRootDir(options?.rootDir);
168
+ const locationOptions: TrailsDbLocationOptions = {
169
+ ...(options?.env === undefined ? {} : { env: options.env }),
170
+ ...(options?.path === undefined ? {} : { path: options.path }),
171
+ rootDir,
172
+ };
173
+ const dbPath = deriveTrailsDbPath(locationOptions);
174
+
175
+ ensureDbParentDir(dbPath);
176
+
177
+ const db = new (sqlite().Database)(dbPath, { create: true });
178
+ initializeWritePragmas(db);
179
+ ensureSchemaVersionTable(db);
180
+ return db;
181
+ };
182
+
183
+ export const openReadTrailsDb = (
184
+ options?: TrailsDbLocationOptions
185
+ ): Database => {
186
+ const dbPath = deriveTrailsDbPath(options);
187
+ if (!fs().existsSync(dbPath)) {
188
+ throw new NotFoundError(
189
+ `Trails database not found at "${dbPath}". Run a write operation first to initialize it.`
190
+ );
191
+ }
192
+ const db = new (sqlite().Database)(dbPath, { readonly: true });
193
+ initializeReadPragmas(db);
194
+ return db;
195
+ };
196
+
197
+ export const ensureSubsystemSchema = (
198
+ db: Database,
199
+ options: EnsureSubsystemSchemaOptions
200
+ ): void => {
201
+ ensureSchemaVersionTable(db);
202
+
203
+ db.transaction(() => {
204
+ const currentVersion = readSubsystemVersion(db, options.subsystem);
205
+ if (currentVersion >= options.version) {
206
+ return;
207
+ }
208
+
209
+ options.migrate(currentVersion);
210
+ writeSubsystemVersion(db, options.subsystem, options.version);
211
+ })();
212
+ };
@@ -0,0 +1,174 @@
1
+ import type {
2
+ ErrorCategory,
3
+ ErrorCategoryCodes,
4
+ ErrorClassRegistryEntry,
5
+ FixedErrorClassRegistryEntry,
6
+ TrailsError,
7
+ } from './errors.js';
8
+ import {
9
+ codesByCategory,
10
+ errorClasses,
11
+ exitCodeMap,
12
+ isTrailsError,
13
+ jsonRpcCodeMap,
14
+ statusCodeMap,
15
+ } from './errors.js';
16
+ import {
17
+ INTERNAL_ERROR_PUBLIC_MESSAGE,
18
+ redactErrorString,
19
+ } from './error-projection.js';
20
+
21
+ export const surfaceNames = ['cli', 'http', 'jsonRpc', 'mcp'] as const;
22
+
23
+ export type SurfaceName = (typeof surfaceNames)[number];
24
+
25
+ const surfaceCodeKeys = {
26
+ cli: 'exit',
27
+ http: 'http',
28
+ jsonRpc: 'jsonRpc',
29
+ mcp: 'jsonRpc',
30
+ } as const satisfies Record<SurfaceName, keyof ErrorCategoryCodes>;
31
+
32
+ export type SurfaceErrorMapper<T> = (error: TrailsError) => T;
33
+
34
+ export type SurfaceErrorMappings<T> = Record<ErrorCategory, T>;
35
+
36
+ /**
37
+ * Union of every surface-specific error code emitted by {@link surfaceErrorMap}.
38
+ *
39
+ * @remarks
40
+ * Previously parameterized by surface (`SurfaceErrorCode<'cli'>` etc.), but
41
+ * the generic collapsed to `number` because the underlying maps were typed as
42
+ * `Record<ErrorCategory, number>`. With `as const satisfies` on the maps the
43
+ * per-surface literals are now observable, but TypeScript cannot narrow
44
+ * `surfaceErrorMap[surface][error.category]` through a generic `TSurface`
45
+ * without an unsound cast. The non-generic union honestly reflects what
46
+ * `mapSurfaceError` returns at the call site.
47
+ */
48
+ export type SurfaceErrorCode =
49
+ (typeof codesByCategory)[ErrorCategory][(typeof surfaceCodeKeys)[SurfaceName]];
50
+
51
+ export interface SurfaceErrorProjection {
52
+ readonly category: ErrorCategory;
53
+ readonly code: SurfaceErrorCode;
54
+ readonly message: string;
55
+ readonly name: string;
56
+ readonly retryable: boolean;
57
+ readonly surface: SurfaceName;
58
+ }
59
+
60
+ export interface ErrorClassSurfaceProjection {
61
+ readonly category: ErrorCategory;
62
+ readonly code: SurfaceErrorCode;
63
+ readonly name: string;
64
+ readonly retryable: boolean;
65
+ readonly surface: SurfaceName;
66
+ }
67
+
68
+ export const createSurfaceErrorMapper =
69
+ <T>(mappings: SurfaceErrorMappings<T>): SurfaceErrorMapper<T> =>
70
+ (error) =>
71
+ mappings[error.category];
72
+
73
+ export const surfaceErrorMap = {
74
+ cli: exitCodeMap,
75
+ http: statusCodeMap,
76
+ jsonRpc: jsonRpcCodeMap,
77
+ mcp: jsonRpcCodeMap,
78
+ } as const satisfies Record<SurfaceName, SurfaceErrorMappings<number>>;
79
+
80
+ export const surfaceErrorRegistry = {
81
+ cli: {
82
+ map: createSurfaceErrorMapper(surfaceErrorMap.cli),
83
+ values: surfaceErrorMap.cli,
84
+ },
85
+ http: {
86
+ map: createSurfaceErrorMapper(surfaceErrorMap.http),
87
+ values: surfaceErrorMap.http,
88
+ },
89
+ jsonRpc: {
90
+ map: createSurfaceErrorMapper(surfaceErrorMap.jsonRpc),
91
+ values: surfaceErrorMap.jsonRpc,
92
+ },
93
+ mcp: {
94
+ map: createSurfaceErrorMapper(surfaceErrorMap.mcp),
95
+ values: surfaceErrorMap.mcp,
96
+ },
97
+ } as const;
98
+
99
+ export const mapSurfaceError = (
100
+ surface: SurfaceName,
101
+ error: TrailsError
102
+ ): SurfaceErrorCode =>
103
+ codesByCategory[error.category][surfaceCodeKeys[surface]];
104
+
105
+ export const projectSurfaceError = (
106
+ surface: SurfaceName,
107
+ error: TrailsError
108
+ ): SurfaceErrorProjection => ({
109
+ category: error.category,
110
+ code: mapSurfaceError(surface, error),
111
+ message: error.message,
112
+ name: error.name,
113
+ retryable: error.retryable,
114
+ surface,
115
+ });
116
+
117
+ export const projectPublicSurfaceError = (
118
+ surface: SurfaceName,
119
+ error: Error
120
+ ): SurfaceErrorProjection => {
121
+ if (isTrailsError(error)) {
122
+ const projection = projectSurfaceError(surface, error);
123
+ return {
124
+ ...projection,
125
+ message:
126
+ projection.category === 'internal'
127
+ ? INTERNAL_ERROR_PUBLIC_MESSAGE
128
+ : redactErrorString(projection.message),
129
+ };
130
+ }
131
+
132
+ return {
133
+ category: 'internal',
134
+ code: codesByCategory.internal[surfaceCodeKeys[surface]],
135
+ message: INTERNAL_ERROR_PUBLIC_MESSAGE,
136
+ name: 'InternalError',
137
+ retryable: false,
138
+ surface,
139
+ };
140
+ };
141
+
142
+ const isFixedErrorClassEntry = (
143
+ entry: ErrorClassRegistryEntry
144
+ ): entry is FixedErrorClassRegistryEntry => entry.category !== 'dynamic';
145
+
146
+ const fixedErrorClassByName: ReadonlyMap<string, FixedErrorClassRegistryEntry> =
147
+ new Map(
148
+ errorClasses.flatMap((entry): [string, FixedErrorClassRegistryEntry][] =>
149
+ isFixedErrorClassEntry(entry) ? [[entry.name, entry]] : []
150
+ )
151
+ );
152
+
153
+ /**
154
+ * Project a known error class name onto a surface without constructing it.
155
+ *
156
+ * Dynamic-category errors such as `RetryExhaustedError` return `undefined`
157
+ * because their surface code depends on the wrapped runtime error.
158
+ */
159
+ export const projectErrorClassSurface = (
160
+ surface: SurfaceName,
161
+ errorName: string
162
+ ): ErrorClassSurfaceProjection | undefined => {
163
+ const entry = fixedErrorClassByName.get(errorName);
164
+ if (entry === undefined) {
165
+ return undefined;
166
+ }
167
+ return {
168
+ category: entry.category,
169
+ code: codesByCategory[entry.category][surfaceCodeKeys[surface]],
170
+ name: entry.name,
171
+ retryable: entry.retryable,
172
+ surface,
173
+ };
174
+ };
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Type utilities for extracting input/output types from trails.
3
+ */
4
+
5
+ import type { Result } from './result.js';
6
+ import type { AnyTrail, Trail } from './trail.js';
7
+ import type { Implementation } from './types.js';
8
+ import type { z } from 'zod';
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Utility types
12
+ // ---------------------------------------------------------------------------
13
+
14
+ /* oxlint-disable no-explicit-any -- `any` required for conditional type inference; `unknown` breaks inference */
15
+
16
+ type SchemaInputOrFallback<TSchema extends z.ZodType, TFallback> =
17
+ unknown extends z.input<TSchema> ? TFallback : z.input<TSchema>;
18
+
19
+ type ComposeSchemaOf<T extends AnyTrail> = T extends {
20
+ readonly composeInput?: (infer TComposeSchema) | undefined;
21
+ }
22
+ ? NonNullable<TComposeSchema>
23
+ : never;
24
+
25
+ type ComposeInputPart<T extends AnyTrail> =
26
+ ComposeSchemaOf<T> extends z.ZodType
27
+ ? T extends Trail<any, any, infer CI>
28
+ ? SchemaInputOrFallback<ComposeSchemaOf<T>, CI>
29
+ : z.input<ComposeSchemaOf<T>>
30
+ : T extends Trail<any, any, infer CI>
31
+ ? CI
32
+ : never;
33
+
34
+ /** Extract the input type from a Trail. */
35
+ export type TrailInput<T extends AnyTrail> = T extends {
36
+ readonly input: infer TInputSchema extends z.ZodType;
37
+ }
38
+ ? T extends Trail<infer I, any, any>
39
+ ? SchemaInputOrFallback<TInputSchema, I>
40
+ : z.input<TInputSchema>
41
+ : never;
42
+
43
+ /** Extract the output type from a Trail. */
44
+ export type TrailOutput<T extends AnyTrail> = T extends {
45
+ readonly implementation: Implementation<any, infer O>;
46
+ }
47
+ ? O
48
+ : never;
49
+
50
+ /**
51
+ * Extract the compose-callable input type from a trail.
52
+ *
53
+ * When a trail declares `composeInput`, callers via `ctx.compose()` must pass
54
+ * both the public input fields and the composition-only fields. This type
55
+ * merges both schemas so the compiler enforces the full shape at the call
56
+ * site. Falls back to plain `TrailInput<T>` when no `composeInput` exists.
57
+ */
58
+ export type ComposeInput<T extends AnyTrail> = [ComposeInputPart<T>] extends [
59
+ never,
60
+ ]
61
+ ? TrailInput<T>
62
+ : TrailInput<T> & ComposeInputPart<T>;
63
+
64
+ /**
65
+ * Extracts the full `Result<Output, Error>` type from a trail definition.
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * type SearchResult = TrailResult<typeof searchTrail>;
70
+ * // Result<{ results: Item[]; count: number }, Error>
71
+ * ```
72
+ */
73
+ export type TrailResult<T extends AnyTrail> = Result<TrailOutput<T>, Error>;
74
+
75
+ /* oxlint-enable no-explicit-any */
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // Runtime schema accessors
79
+ // ---------------------------------------------------------------------------
80
+
81
+ /** Get the input Zod schema from a trail, preserving the specific schema type. */
82
+ export const inputOf = <T extends AnyTrail>(trail: T): T['input'] =>
83
+ trail.input;
84
+
85
+ /** Get the output Zod schema from a trail, if defined, preserving the specific schema type. */
86
+ export const outputOf = <T extends AnyTrail>(trail: T): T['output'] =>
87
+ trail.output;