@ontrails/core 1.0.0-beta.1 → 1.0.0-beta.11
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.
- package/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +121 -0
- package/README.md +54 -11
- package/dist/context.d.ts +2 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +12 -7
- package/dist/context.js.map +1 -1
- package/dist/derive.d.ts +1 -1
- package/dist/derive.d.ts.map +1 -1
- package/dist/derive.js +4 -1
- package/dist/derive.js.map +1 -1
- package/dist/dispatch.d.ts +27 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +34 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/event.d.ts +2 -2
- package/dist/event.d.ts.map +1 -1
- package/dist/event.js +1 -1
- package/dist/event.js.map +1 -1
- package/dist/execute.d.ts +33 -0
- package/dist/execute.d.ts.map +1 -0
- package/dist/execute.js +207 -0
- package/dist/execute.js.map +1 -0
- package/dist/index.d.ts +12 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/patterns/status.d.ts +1 -1
- package/dist/result.d.ts.map +1 -1
- package/dist/result.js +15 -4
- package/dist/result.js.map +1 -1
- package/dist/serialization.d.ts.map +1 -1
- package/dist/serialization.js +45 -7
- package/dist/serialization.js.map +1 -1
- package/dist/service.d.ts +69 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +56 -0
- package/dist/service.js.map +1 -0
- package/dist/topo.d.ts +11 -4
- package/dist/topo.d.ts.map +1 -1
- package/dist/topo.js +43 -18
- package/dist/topo.js.map +1 -1
- package/dist/trail.d.ts +21 -10
- package/dist/trail.d.ts.map +1 -1
- package/dist/trail.js +5 -2
- package/dist/trail.js.map +1 -1
- package/dist/type-utils.d.ts +24 -0
- package/dist/type-utils.d.ts.map +1 -0
- package/dist/type-utils.js +12 -0
- package/dist/type-utils.js.map +1 -0
- package/dist/types.d.ts +10 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/validate-topo.d.ts +2 -2
- package/dist/validate-topo.d.ts.map +1 -1
- package/dist/validate-topo.js +75 -9
- package/dist/validate-topo.js.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +34 -3
- package/dist/validation.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/context.test.ts +16 -5
- package/src/__tests__/derive.test.ts +44 -0
- package/src/__tests__/dispatch.test.ts +181 -0
- package/src/__tests__/event.test.ts +5 -5
- package/src/__tests__/execute.test.ts +523 -0
- package/src/__tests__/layer.test.ts +14 -113
- package/src/__tests__/serialization.test.ts +166 -1
- package/src/__tests__/service.test.ts +197 -0
- package/src/__tests__/topo.test.ts +171 -78
- package/src/__tests__/trail.test.ts +119 -37
- package/src/__tests__/type-utils.test.ts +90 -0
- package/src/__tests__/validate-topo.test.ts +140 -19
- package/src/__tests__/validation.test.ts +53 -0
- package/src/context.ts +18 -9
- package/src/derive.ts +12 -2
- package/src/dispatch.ts +54 -0
- package/src/event.ts +3 -3
- package/src/execute.ts +345 -0
- package/src/index.ts +39 -18
- package/src/result.ts +18 -4
- package/src/serialization.ts +56 -11
- package/src/service.ts +139 -0
- package/src/topo.ts +66 -27
- package/src/trail.ts +36 -13
- package/src/type-utils.ts +45 -0
- package/src/types.ts +11 -2
- package/src/validate-topo.ts +92 -10
- package/src/validation.ts +35 -3
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/hike.d.ts +0 -36
- package/dist/hike.d.ts.map +0 -1
- package/dist/hike.js +0 -20
- package/dist/hike.js.map +0 -1
- package/src/__tests__/hike.test.ts +0 -117
- package/src/__tests__/job.test.ts +0 -98
- package/src/adapters.ts +0 -68
- package/src/health.ts +0 -23
- package/src/hike.ts +0 -77
- package/src/job.ts +0 -20
package/src/validation.ts
CHANGED
|
@@ -94,6 +94,34 @@ export const validateOutput = <T>(
|
|
|
94
94
|
// Zod → JSON Schema (public API)
|
|
95
95
|
// ---------------------------------------------------------------------------
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Sentinel indicating a dynamic default that should be omitted from schema
|
|
99
|
+
* exports. Zod v4 wraps all defaults in getters; dynamic ones (functions)
|
|
100
|
+
* produce new values on each access. We detect this by reading the getter
|
|
101
|
+
* twice and comparing with `Object.is`. If values differ, the default is
|
|
102
|
+
* dynamic and we cache this sentinel to skip it in future calls.
|
|
103
|
+
*/
|
|
104
|
+
const DYNAMIC_DEFAULT = Symbol('DYNAMIC_DEFAULT');
|
|
105
|
+
const defaultValueCache = new WeakMap<object, unknown>();
|
|
106
|
+
|
|
107
|
+
/** Read a Zod v4 default getter twice and decide if it's stable.
|
|
108
|
+
* Uses Object.is for primitives and JSON.stringify for objects/arrays. */
|
|
109
|
+
const resolveDefault = (def: Record<string, unknown>): unknown => {
|
|
110
|
+
try {
|
|
111
|
+
const a = def['defaultValue'];
|
|
112
|
+
const b = def['defaultValue'];
|
|
113
|
+
if (Object.is(a, b)) {
|
|
114
|
+
return a;
|
|
115
|
+
}
|
|
116
|
+
// Object/array defaults produce new references each call but may
|
|
117
|
+
// still be structurally identical (e.g. `() => ({ key: 'val' })`).
|
|
118
|
+
return JSON.stringify(a) === JSON.stringify(b) ? a : DYNAMIC_DEFAULT;
|
|
119
|
+
} catch {
|
|
120
|
+
// BigInt, circular refs, or other non-serializable defaults
|
|
121
|
+
return DYNAMIC_DEFAULT;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
97
125
|
/**
|
|
98
126
|
* Convert common Zod types to a JSON Schema object.
|
|
99
127
|
*
|
|
@@ -142,9 +170,13 @@ export const zodToJsonSchema: JsonSchemaConverter = (
|
|
|
142
170
|
default: (value) => {
|
|
143
171
|
const inner = value._zod.def['innerType'] as unknown as z.ZodType;
|
|
144
172
|
const innerSchema = zodToJsonSchema(inner);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
173
|
+
if (!defaultValueCache.has(value._zod.def)) {
|
|
174
|
+
defaultValueCache.set(value._zod.def, resolveDefault(value._zod.def));
|
|
175
|
+
}
|
|
176
|
+
const cached = defaultValueCache.get(value._zod.def);
|
|
177
|
+
if (cached !== DYNAMIC_DEFAULT) {
|
|
178
|
+
innerSchema['default'] = cached;
|
|
179
|
+
}
|
|
148
180
|
return innerSchema;
|
|
149
181
|
},
|
|
150
182
|
enum: (value) => {
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/
|
|
1
|
+
{"root":["./src/blob-ref.ts","./src/branded.ts","./src/collections.ts","./src/context.ts","./src/derive.ts","./src/dispatch.ts","./src/errors.ts","./src/event.ts","./src/execute.ts","./src/fetch.ts","./src/guards.ts","./src/index.ts","./src/layer.ts","./src/path-security.ts","./src/resilience.ts","./src/result.ts","./src/serialization.ts","./src/service.ts","./src/topo.ts","./src/trail.ts","./src/type-utils.ts","./src/types.ts","./src/validate-topo.ts","./src/validation.ts","./src/workspace.ts","./src/patterns/bulk.ts","./src/patterns/change.ts","./src/patterns/date-range.ts","./src/patterns/index.ts","./src/patterns/pagination.ts","./src/patterns/progress.ts","./src/patterns/sorting.ts","./src/patterns/status.ts","./src/patterns/timestamps.ts","./src/redaction/index.ts","./src/redaction/patterns.ts","./src/redaction/redactor.ts"],"version":"5.9.3"}
|
package/dist/hike.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hike — a composition that follows trails.
|
|
3
|
-
*/
|
|
4
|
-
import type { Trail, TrailSpec } from './trail.js';
|
|
5
|
-
export interface HikeSpec<I, O> extends TrailSpec<I, O> {
|
|
6
|
-
readonly follows: readonly string[];
|
|
7
|
-
}
|
|
8
|
-
export interface Hike<I, O> extends Omit<Trail<I, O>, 'kind'> {
|
|
9
|
-
readonly kind: 'hike';
|
|
10
|
-
readonly follows: readonly string[];
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Create a hike definition.
|
|
14
|
-
*
|
|
15
|
-
* A hike is a composition that declares which trails it follows.
|
|
16
|
-
* Returns a frozen object with `kind: "hike"` and all spec fields.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```typescript
|
|
20
|
-
* // ID as first argument
|
|
21
|
-
* const onboard = hike("entity.onboard", {
|
|
22
|
-
* follows: ["entity.add", "entity.relate"],
|
|
23
|
-
* input: z.object({ name: z.string() }),
|
|
24
|
-
* implementation: (input, ctx) => Result.ok(...),
|
|
25
|
-
* });
|
|
26
|
-
*
|
|
27
|
-
* // Full spec object (programmatic)
|
|
28
|
-
* const onboard = hike({ id: "entity.onboard", follows: [...], ... });
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
export declare function hike<I, O>(id: string, spec: HikeSpec<I, O>): Hike<I, O>;
|
|
32
|
-
export declare function hike<I, O>(spec: HikeSpec<I, O> & {
|
|
33
|
-
readonly id: string;
|
|
34
|
-
}): Hike<I, O>;
|
|
35
|
-
export type AnyHike = Hike<any, any>;
|
|
36
|
-
//# sourceMappingURL=hike.d.ts.map
|
package/dist/hike.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hike.d.ts","sourceRoot":"","sources":["../src/hike.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAOnD,MAAM,WAAW,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAE,SAAQ,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC;IACrD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAMD,MAAM,WAAW,IAAI,CAAC,CAAC,EAAE,CAAC,CAAE,SAAQ,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC;IAC3D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAMD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACzE,wBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,EACvB,IAAI,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC7C,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AA0Bd,MAAM,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC"}
|
package/dist/hike.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hike — a composition that follows trails.
|
|
3
|
-
*/
|
|
4
|
-
export function hike(idOrSpec, maybeSpec) {
|
|
5
|
-
const resolved = typeof idOrSpec === 'string'
|
|
6
|
-
? { id: idOrSpec, spec: maybeSpec }
|
|
7
|
-
: { id: idOrSpec.id, spec: idOrSpec };
|
|
8
|
-
if (!resolved.spec) {
|
|
9
|
-
throw new TypeError('hike() requires a spec when an id is provided');
|
|
10
|
-
}
|
|
11
|
-
const { follows, implementation, ...rest } = resolved.spec;
|
|
12
|
-
return Object.freeze({
|
|
13
|
-
...rest,
|
|
14
|
-
follows: Object.freeze([...follows]),
|
|
15
|
-
id: resolved.id,
|
|
16
|
-
implementation: async (input, ctx) => await implementation(input, ctx),
|
|
17
|
-
kind: 'hike',
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
//# sourceMappingURL=hike.js.map
|
package/dist/hike.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hike.js","sourceRoot":"","sources":["../src/hike.ts"],"names":[],"mappings":"AAAA;;GAEG;AAiDH,MAAM,UAAU,IAAI,CAClB,QAA6D,EAC7D,SAA0B;IAE1B,MAAM,QAAQ,GACZ,OAAO,QAAQ,KAAK,QAAQ;QAC1B,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE;QACnC,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAE1C,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,MAAM,IAAI,SAAS,CAAC,+CAA+C,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC;IAC3D,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,GAAG,IAAI;QACP,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC;QACpC,EAAE,EAAE,QAAQ,CAAC,EAAE;QACf,cAAc,EAAE,KAAK,EAAE,KAAQ,EAAE,GAAiB,EAAE,EAAE,CACpD,MAAM,cAAc,CAAC,KAAK,EAAE,GAAG,CAAC;QAClC,IAAI,EAAE,MAAe;KACtB,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
import { Result } from '../result';
|
|
6
|
-
import { hike } from '../hike';
|
|
7
|
-
import type { TrailContext } from '../types';
|
|
8
|
-
|
|
9
|
-
const stubCtx: TrailContext = {
|
|
10
|
-
requestId: 'test-123',
|
|
11
|
-
signal: AbortSignal.timeout(5000),
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
describe('hike()', () => {
|
|
15
|
-
const inputSchema = z.object({ userId: z.string() });
|
|
16
|
-
const outputSchema = z.object({ profile: z.string() });
|
|
17
|
-
|
|
18
|
-
const fetchProfile = hike('fetch-profile', {
|
|
19
|
-
description: 'Fetch a user profile',
|
|
20
|
-
follows: ['authenticate', 'validate-session'],
|
|
21
|
-
implementation: (input) =>
|
|
22
|
-
Result.ok({ profile: `Profile for ${input.userId}` }),
|
|
23
|
-
input: inputSchema,
|
|
24
|
-
output: outputSchema,
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('basics', () => {
|
|
28
|
-
test("returns kind 'hike'", () => {
|
|
29
|
-
expect(fetchProfile.kind).toBe('hike');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('returns correct id', () => {
|
|
33
|
-
expect(fetchProfile.id).toBe('fetch-profile');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test('preserves follows array', () => {
|
|
37
|
-
expect(fetchProfile.follows).toEqual([
|
|
38
|
-
'authenticate',
|
|
39
|
-
'validate-session',
|
|
40
|
-
]);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('follows array is frozen', () => {
|
|
44
|
-
expect(Object.isFrozen(fetchProfile.follows)).toBe(true);
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
describe('trail compatibility', () => {
|
|
49
|
-
test('extends Trail — has input schema', () => {
|
|
50
|
-
const parsed = fetchProfile.input.safeParse({ userId: 'u-1' });
|
|
51
|
-
expect(parsed.success).toBe(true);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('extends Trail — has output schema', () => {
|
|
55
|
-
expect(fetchProfile.output).toBeDefined();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('extends Trail — implementation is callable', async () => {
|
|
59
|
-
const result = await fetchProfile.implementation(
|
|
60
|
-
{ userId: 'u-1' },
|
|
61
|
-
stubCtx
|
|
62
|
-
);
|
|
63
|
-
expect(result.isOk()).toBe(true);
|
|
64
|
-
expect(result.unwrap()).toEqual({ profile: 'Profile for u-1' });
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('preserves description', () => {
|
|
68
|
-
expect(fetchProfile.description).toBe('Fetch a user profile');
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('result object is frozen', () => {
|
|
72
|
-
expect(Object.isFrozen(fetchProfile)).toBe(true);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('markers are preserved', () => {
|
|
77
|
-
const withMarkers = hike('tagged-hike', {
|
|
78
|
-
follows: ['setup'],
|
|
79
|
-
implementation: () => Result.ok(),
|
|
80
|
-
input: z.object({}),
|
|
81
|
-
markers: { domain: 'auth' },
|
|
82
|
-
});
|
|
83
|
-
expect(withMarkers.markers).toEqual({ domain: 'auth' });
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
describe('single-object overload', () => {
|
|
87
|
-
test('accepts spec with id property', () => {
|
|
88
|
-
const r = hike({
|
|
89
|
-
follows: ['entity.add', 'entity.relate'],
|
|
90
|
-
id: 'entity.onboard',
|
|
91
|
-
implementation: () => Result.ok(),
|
|
92
|
-
input: z.object({}),
|
|
93
|
-
});
|
|
94
|
-
expect(r.id).toBe('entity.onboard');
|
|
95
|
-
expect(r.kind).toBe('hike');
|
|
96
|
-
expect(r.follows).toEqual(['entity.add', 'entity.relate']);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('sync implementations are normalized to an awaitable runtime function', async () => {
|
|
100
|
-
const r = hike({
|
|
101
|
-
follows: ['entity.add'],
|
|
102
|
-
id: 'entity.check',
|
|
103
|
-
implementation: (input: { userId: string }) =>
|
|
104
|
-
Result.ok({ profile: input.userId }),
|
|
105
|
-
input: inputSchema,
|
|
106
|
-
output: outputSchema,
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
const promise = r.implementation({ userId: 'u-2' }, stubCtx);
|
|
110
|
-
expect(promise).toBeInstanceOf(Promise);
|
|
111
|
-
|
|
112
|
-
const result = await promise;
|
|
113
|
-
expect(result.isOk()).toBe(true);
|
|
114
|
-
expect(result.unwrap()).toEqual({ profile: 'u-2' });
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
});
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
import { jobOutputSchema } from '../job';
|
|
6
|
-
|
|
7
|
-
describe('jobOutputSchema', () => {
|
|
8
|
-
test('parses a valid completed job output', () => {
|
|
9
|
-
const input = {
|
|
10
|
-
completedAt: '2026-03-25T00:01:00Z',
|
|
11
|
-
current: 10,
|
|
12
|
-
jobId: 'job-123',
|
|
13
|
-
percentage: 100,
|
|
14
|
-
result: { rows: 42 },
|
|
15
|
-
startedAt: '2026-03-25T00:00:00Z',
|
|
16
|
-
status: 'completed',
|
|
17
|
-
total: 10,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const parsed = jobOutputSchema.parse(input);
|
|
21
|
-
|
|
22
|
-
expect(parsed.jobId).toBe('job-123');
|
|
23
|
-
expect(parsed.status).toBe('completed');
|
|
24
|
-
expect(parsed.current).toBe(10);
|
|
25
|
-
expect(parsed.total).toBe(10);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('parses a minimal pending job output', () => {
|
|
29
|
-
const input = {
|
|
30
|
-
current: 0,
|
|
31
|
-
jobId: 'job-456',
|
|
32
|
-
status: 'pending',
|
|
33
|
-
total: 100,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const parsed = jobOutputSchema.parse(input);
|
|
37
|
-
|
|
38
|
-
expect(parsed.jobId).toBe('job-456');
|
|
39
|
-
expect(parsed.status).toBe('pending');
|
|
40
|
-
expect(parsed.result).toBeUndefined();
|
|
41
|
-
expect(parsed.error).toBeUndefined();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('parses a failed job with error', () => {
|
|
45
|
-
const input = {
|
|
46
|
-
current: 3,
|
|
47
|
-
error: 'connection reset',
|
|
48
|
-
jobId: 'job-789',
|
|
49
|
-
status: 'failed',
|
|
50
|
-
total: 10,
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const parsed = jobOutputSchema.parse(input);
|
|
54
|
-
|
|
55
|
-
expect(parsed.status).toBe('failed');
|
|
56
|
-
expect(parsed.error).toBe('connection reset');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('rejects when jobId is missing', () => {
|
|
60
|
-
const input = {
|
|
61
|
-
current: 1,
|
|
62
|
-
status: 'running',
|
|
63
|
-
total: 5,
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
expect(() => jobOutputSchema.parse(input)).toThrow();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('rejects an invalid status value', () => {
|
|
70
|
-
const input = {
|
|
71
|
-
current: 0,
|
|
72
|
-
jobId: 'job-bad',
|
|
73
|
-
status: 'unknown',
|
|
74
|
-
total: 0,
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
expect(() => jobOutputSchema.parse(input)).toThrow();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('composes statusFields and progressFields correctly', () => {
|
|
81
|
-
const { shape } = jobOutputSchema;
|
|
82
|
-
|
|
83
|
-
// Status field from statusFields()
|
|
84
|
-
expect(shape.status).toBeInstanceOf(z.ZodEnum);
|
|
85
|
-
|
|
86
|
-
// Progress fields from progressFields()
|
|
87
|
-
expect(shape.current).toBeInstanceOf(z.ZodNumber);
|
|
88
|
-
expect(shape.total).toBeInstanceOf(z.ZodNumber);
|
|
89
|
-
expect(shape.percentage).toBeDefined();
|
|
90
|
-
|
|
91
|
-
// Job-specific fields
|
|
92
|
-
expect(shape.jobId).toBeInstanceOf(z.ZodString);
|
|
93
|
-
expect(shape.error).toBeDefined();
|
|
94
|
-
expect(shape.result).toBeDefined();
|
|
95
|
-
expect(shape.startedAt).toBeDefined();
|
|
96
|
-
expect(shape.completedAt).toBeDefined();
|
|
97
|
-
});
|
|
98
|
-
});
|
package/src/adapters.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import type { Result } from './result.js';
|
|
2
|
-
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
// Shared option types
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
|
|
7
|
-
/** Options for search queries */
|
|
8
|
-
export interface SearchOptions {
|
|
9
|
-
readonly limit?: number | undefined;
|
|
10
|
-
readonly offset?: number | undefined;
|
|
11
|
-
readonly filters?: Readonly<Record<string, unknown>> | undefined;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** A single search hit */
|
|
15
|
-
export interface SearchResult {
|
|
16
|
-
readonly id: string;
|
|
17
|
-
readonly score: number;
|
|
18
|
-
readonly document: Readonly<Record<string, unknown>>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Options for storage writes */
|
|
22
|
-
export interface StorageOptions {
|
|
23
|
-
/** Time-to-live in milliseconds */
|
|
24
|
-
readonly ttl?: number | undefined;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
// Adapter port interfaces
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
|
|
31
|
-
/** Full-text / vector index adapter */
|
|
32
|
-
export interface IndexAdapter {
|
|
33
|
-
index(
|
|
34
|
-
id: string,
|
|
35
|
-
document: Readonly<Record<string, unknown>>
|
|
36
|
-
): Promise<Result<void, Error>>;
|
|
37
|
-
|
|
38
|
-
search(
|
|
39
|
-
query: string,
|
|
40
|
-
options?: SearchOptions
|
|
41
|
-
): Promise<Result<readonly SearchResult[], Error>>;
|
|
42
|
-
|
|
43
|
-
remove(id: string): Promise<Result<void, Error>>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Key-value storage adapter */
|
|
47
|
-
export interface StorageAdapter {
|
|
48
|
-
get(key: string): Promise<Result<unknown, Error>>;
|
|
49
|
-
set(
|
|
50
|
-
key: string,
|
|
51
|
-
value: unknown,
|
|
52
|
-
options?: StorageOptions
|
|
53
|
-
): Promise<Result<void, Error>>;
|
|
54
|
-
delete(key: string): Promise<Result<void, Error>>;
|
|
55
|
-
has(key: string): Promise<Result<boolean, Error>>;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Cache adapter with typed get/set and bulk clear */
|
|
59
|
-
export interface CacheAdapter {
|
|
60
|
-
get<T>(key: string): Promise<Result<T | undefined, Error>>;
|
|
61
|
-
set<T>(
|
|
62
|
-
key: string,
|
|
63
|
-
value: T,
|
|
64
|
-
options?: StorageOptions
|
|
65
|
-
): Promise<Result<void, Error>>;
|
|
66
|
-
delete(key: string): Promise<Result<void, Error>>;
|
|
67
|
-
clear(): Promise<Result<void, Error>>;
|
|
68
|
-
}
|
package/src/health.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Health check types
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
|
|
5
|
-
/** Aggregate health status */
|
|
6
|
-
export type HealthStatus = 'healthy' | 'degraded' | 'unhealthy';
|
|
7
|
-
|
|
8
|
-
/** Individual component check result */
|
|
9
|
-
export interface HealthCheck {
|
|
10
|
-
readonly status: HealthStatus;
|
|
11
|
-
readonly message?: string | undefined;
|
|
12
|
-
/** Latency in milliseconds */
|
|
13
|
-
readonly latency?: number | undefined;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** Full health report returned by a health endpoint */
|
|
17
|
-
export interface HealthResult {
|
|
18
|
-
readonly status: HealthStatus;
|
|
19
|
-
readonly checks: Readonly<Record<string, HealthCheck>>;
|
|
20
|
-
readonly version?: string | undefined;
|
|
21
|
-
/** Uptime in seconds */
|
|
22
|
-
readonly uptime?: number | undefined;
|
|
23
|
-
}
|
package/src/hike.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hike — a composition that follows trails.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { Trail, TrailSpec } from './trail.js';
|
|
6
|
-
import type { TrailContext } from './types.js';
|
|
7
|
-
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
// Spec (input to the factory)
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
|
|
12
|
-
export interface HikeSpec<I, O> extends TrailSpec<I, O> {
|
|
13
|
-
readonly follows: readonly string[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Shape (output of the factory)
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
export interface Hike<I, O> extends Omit<Trail<I, O>, 'kind'> {
|
|
21
|
-
readonly kind: 'hike';
|
|
22
|
-
readonly follows: readonly string[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// Factory
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Create a hike definition.
|
|
31
|
-
*
|
|
32
|
-
* A hike is a composition that declares which trails it follows.
|
|
33
|
-
* Returns a frozen object with `kind: "hike"` and all spec fields.
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* ```typescript
|
|
37
|
-
* // ID as first argument
|
|
38
|
-
* const onboard = hike("entity.onboard", {
|
|
39
|
-
* follows: ["entity.add", "entity.relate"],
|
|
40
|
-
* input: z.object({ name: z.string() }),
|
|
41
|
-
* implementation: (input, ctx) => Result.ok(...),
|
|
42
|
-
* });
|
|
43
|
-
*
|
|
44
|
-
* // Full spec object (programmatic)
|
|
45
|
-
* const onboard = hike({ id: "entity.onboard", follows: [...], ... });
|
|
46
|
-
* ```
|
|
47
|
-
*/
|
|
48
|
-
export function hike<I, O>(id: string, spec: HikeSpec<I, O>): Hike<I, O>;
|
|
49
|
-
export function hike<I, O>(
|
|
50
|
-
spec: HikeSpec<I, O> & { readonly id: string }
|
|
51
|
-
): Hike<I, O>;
|
|
52
|
-
export function hike<I, O>(
|
|
53
|
-
idOrSpec: string | (HikeSpec<I, O> & { readonly id: string }),
|
|
54
|
-
maybeSpec?: HikeSpec<I, O>
|
|
55
|
-
): Hike<I, O> {
|
|
56
|
-
const resolved =
|
|
57
|
-
typeof idOrSpec === 'string'
|
|
58
|
-
? { id: idOrSpec, spec: maybeSpec }
|
|
59
|
-
: { id: idOrSpec.id, spec: idOrSpec };
|
|
60
|
-
|
|
61
|
-
if (!resolved.spec) {
|
|
62
|
-
throw new TypeError('hike() requires a spec when an id is provided');
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const { follows, implementation, ...rest } = resolved.spec;
|
|
66
|
-
return Object.freeze({
|
|
67
|
-
...rest,
|
|
68
|
-
follows: Object.freeze([...follows]),
|
|
69
|
-
id: resolved.id,
|
|
70
|
-
implementation: async (input: I, ctx: TrailContext) =>
|
|
71
|
-
await implementation(input, ctx),
|
|
72
|
-
kind: 'hike' as const,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// oxlint-disable-next-line no-explicit-any -- existential type; see AnyTrail
|
|
77
|
-
export type AnyHike = Hike<any, any>;
|
package/src/job.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
import { progressFields } from './patterns/progress.js';
|
|
4
|
-
import { statusFields } from './patterns/status.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Proof: job-style output schema composes from existing pattern helpers.
|
|
8
|
-
* No dedicated `kind: "job"` needed — regular trails with status+progress output work.
|
|
9
|
-
*/
|
|
10
|
-
export const jobOutputSchema = z.object({
|
|
11
|
-
...progressFields().shape,
|
|
12
|
-
...statusFields().shape,
|
|
13
|
-
completedAt: z.string().optional(),
|
|
14
|
-
error: z.string().optional(),
|
|
15
|
-
jobId: z.string(),
|
|
16
|
-
result: z.unknown().optional(),
|
|
17
|
-
startedAt: z.string().optional(),
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
export type JobOutput = z.infer<typeof jobOutputSchema>;
|