@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.
- package/CHANGELOG.md +74 -0
- package/README.md +73 -24
- package/package.json +11 -4
- package/src/activation-provenance.ts +111 -0
- package/src/activation-source-compatibility.ts +430 -0
- package/src/activation-source-projection.ts +221 -0
- package/src/activation-source.ts +91 -0
- package/src/blob-ref.ts +51 -0
- package/src/branded.ts +1 -1
- package/src/context.ts +28 -4
- package/src/contour.ts +344 -0
- package/src/cross-batch.ts +69 -0
- package/src/cross-schema.ts +36 -0
- package/src/detours.ts +8 -0
- package/src/draft.ts +60 -52
- package/src/error-projection.ts +51 -0
- package/src/errors.ts +245 -48
- package/src/execute.ts +1250 -40
- package/src/fire.ts +1169 -0
- package/src/index.ts +383 -49
- package/src/internal/fork-ctx.ts +69 -0
- package/src/layer-projection.ts +192 -0
- package/src/layer.ts +81 -0
- package/src/observe.ts +361 -0
- package/src/path-security.ts +1 -1
- package/src/permits.ts +12 -0
- package/src/redaction/patterns.ts +6 -3
- package/src/resilience.ts +11 -11
- package/src/resource-config.ts +792 -0
- package/src/resource.ts +175 -0
- package/src/result.ts +18 -0
- package/src/run.ts +7 -6
- package/src/schedule-runtime.ts +689 -0
- package/src/schedule.ts +324 -0
- package/src/serialization.ts +90 -55
- package/src/signal-diagnostics.ts +633 -0
- package/src/signal-ref.ts +87 -0
- package/src/signal.ts +37 -0
- package/src/store/accessor-protocol.ts +56 -0
- package/src/store/index.ts +4 -0
- package/src/structured-examples.ts +245 -0
- package/src/surface-derivation.ts +91 -0
- package/src/surface-filter.ts +176 -0
- package/src/topo.ts +710 -59
- package/src/tracing.ts +345 -0
- package/src/trail.ts +347 -33
- package/src/trails/derive-trail.ts +842 -0
- package/src/trails/index.ts +9 -0
- package/src/trails/ingest.ts +138 -0
- package/src/{internal/trails-db.ts → trails-db.ts} +50 -31
- package/src/transport-error-map.ts +174 -0
- package/src/type-utils.ts +17 -2
- package/src/types.ts +208 -17
- package/src/validate-established-topo.ts +8 -2
- package/src/validate-topo.ts +244 -13
- package/src/validation.ts +58 -1
- package/src/webhook.ts +276 -0
- package/src/workspace.ts +1 -1
- package/src/zod-wrappers.ts +72 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/adapters.d.ts +0 -39
- package/dist/adapters.d.ts.map +0 -1
- package/dist/adapters.js +0 -2
- package/dist/adapters.js.map +0 -1
- package/dist/blob-ref.d.ts +0 -20
- package/dist/blob-ref.d.ts.map +0 -1
- package/dist/blob-ref.js +0 -22
- package/dist/blob-ref.js.map +0 -1
- package/dist/branded.d.ts +0 -36
- package/dist/branded.d.ts.map +0 -1
- package/dist/branded.js +0 -89
- package/dist/branded.js.map +0 -1
- package/dist/collections.d.ts +0 -31
- package/dist/collections.d.ts.map +0 -1
- package/dist/collections.js +0 -60
- package/dist/collections.js.map +0 -1
- package/dist/context.d.ts +0 -10
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -21
- package/dist/context.js.map +0 -1
- package/dist/derive.d.ts +0 -44
- package/dist/derive.d.ts.map +0 -1
- package/dist/derive.js +0 -149
- package/dist/derive.js.map +0 -1
- package/dist/dispatch.d.ts +0 -27
- package/dist/dispatch.d.ts.map +0 -1
- package/dist/dispatch.js +0 -34
- package/dist/dispatch.js.map +0 -1
- package/dist/draft.d.ts +0 -28
- package/dist/draft.d.ts.map +0 -1
- package/dist/draft.js +0 -156
- package/dist/draft.js.map +0 -1
- package/dist/errors.d.ts +0 -83
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -142
- package/dist/errors.js.map +0 -1
- package/dist/event.d.ts +0 -8
- package/dist/event.d.ts.map +0 -1
- package/dist/event.js +0 -7
- package/dist/event.js.map +0 -1
- package/dist/execute.d.ts +0 -35
- package/dist/execute.d.ts.map +0 -1
- package/dist/execute.js +0 -90
- package/dist/execute.js.map +0 -1
- package/dist/fetch.d.ts +0 -15
- package/dist/fetch.d.ts.map +0 -1
- package/dist/fetch.js +0 -102
- package/dist/fetch.js.map +0 -1
- package/dist/gate.d.ts +0 -17
- package/dist/gate.d.ts.map +0 -1
- package/dist/gate.js +0 -21
- package/dist/gate.js.map +0 -1
- package/dist/guards.d.ts +0 -17
- package/dist/guards.d.ts.map +0 -1
- package/dist/guards.js +0 -25
- package/dist/guards.js.map +0 -1
- package/dist/health.d.ts +0 -18
- package/dist/health.d.ts.map +0 -1
- package/dist/health.js +0 -5
- package/dist/health.js.map +0 -1
- package/dist/index.d.ts +0 -48
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -51
- package/dist/index.js.map +0 -1
- package/dist/internal/topo-saves.d.ts +0 -47
- package/dist/internal/topo-saves.d.ts.map +0 -1
- package/dist/internal/topo-saves.js +0 -310
- package/dist/internal/topo-saves.js.map +0 -1
- package/dist/internal/topo-store-read.d.ts +0 -67
- package/dist/internal/topo-store-read.d.ts.map +0 -1
- package/dist/internal/topo-store-read.js +0 -222
- package/dist/internal/topo-store-read.js.map +0 -1
- package/dist/internal/topo-store.d.ts +0 -12
- package/dist/internal/topo-store.d.ts.map +0 -1
- package/dist/internal/topo-store.js +0 -571
- package/dist/internal/topo-store.js.map +0 -1
- package/dist/internal/trails-db.d.ts +0 -16
- package/dist/internal/trails-db.d.ts.map +0 -1
- package/dist/internal/trails-db.js +0 -118
- package/dist/internal/trails-db.js.map +0 -1
- package/dist/job.d.ts +0 -24
- package/dist/job.d.ts.map +0 -1
- package/dist/job.js +0 -17
- package/dist/job.js.map +0 -1
- package/dist/layer.d.ts +0 -6
- package/dist/layer.d.ts.map +0 -1
- package/dist/layer.js +0 -4
- package/dist/layer.js.map +0 -1
- package/dist/path-security.d.ts +0 -28
- package/dist/path-security.d.ts.map +0 -1
- package/dist/path-security.js +0 -63
- package/dist/path-security.js.map +0 -1
- package/dist/patterns/bulk.d.ts +0 -15
- package/dist/patterns/bulk.d.ts.map +0 -1
- package/dist/patterns/bulk.js +0 -14
- package/dist/patterns/bulk.js.map +0 -1
- package/dist/patterns/change.d.ts +0 -10
- package/dist/patterns/change.d.ts.map +0 -1
- package/dist/patterns/change.js +0 -10
- package/dist/patterns/change.js.map +0 -1
- package/dist/patterns/date-range.d.ts +0 -10
- package/dist/patterns/date-range.d.ts.map +0 -1
- package/dist/patterns/date-range.js +0 -10
- package/dist/patterns/date-range.js.map +0 -1
- package/dist/patterns/index.d.ts +0 -9
- package/dist/patterns/index.d.ts.map +0 -1
- package/dist/patterns/index.js +0 -9
- package/dist/patterns/index.js.map +0 -1
- package/dist/patterns/pagination.d.ts +0 -18
- package/dist/patterns/pagination.d.ts.map +0 -1
- package/dist/patterns/pagination.js +0 -18
- package/dist/patterns/pagination.js.map +0 -1
- package/dist/patterns/progress.d.ts +0 -11
- package/dist/patterns/progress.d.ts.map +0 -1
- package/dist/patterns/progress.js +0 -11
- package/dist/patterns/progress.js.map +0 -1
- package/dist/patterns/sorting.d.ts +0 -13
- package/dist/patterns/sorting.d.ts.map +0 -1
- package/dist/patterns/sorting.js +0 -10
- package/dist/patterns/sorting.js.map +0 -1
- package/dist/patterns/status.d.ts +0 -15
- package/dist/patterns/status.d.ts.map +0 -1
- package/dist/patterns/status.js +0 -9
- package/dist/patterns/status.js.map +0 -1
- package/dist/patterns/timestamps.d.ts +0 -10
- package/dist/patterns/timestamps.d.ts.map +0 -1
- package/dist/patterns/timestamps.js +0 -10
- package/dist/patterns/timestamps.js.map +0 -1
- package/dist/provision-config.d.ts +0 -22
- package/dist/provision-config.d.ts.map +0 -1
- package/dist/provision-config.js +0 -210
- package/dist/provision-config.js.map +0 -1
- package/dist/provision.d.ts +0 -71
- package/dist/provision.d.ts.map +0 -1
- package/dist/provision.js +0 -56
- package/dist/provision.js.map +0 -1
- package/dist/redaction/index.d.ts +0 -4
- package/dist/redaction/index.d.ts.map +0 -1
- package/dist/redaction/index.js +0 -3
- package/dist/redaction/index.js.map +0 -1
- package/dist/redaction/patterns.d.ts +0 -9
- package/dist/redaction/patterns.d.ts.map +0 -1
- package/dist/redaction/patterns.js +0 -39
- package/dist/redaction/patterns.js.map +0 -1
- package/dist/redaction/redactor.d.ts +0 -27
- package/dist/redaction/redactor.d.ts.map +0 -1
- package/dist/redaction/redactor.js +0 -89
- package/dist/redaction/redactor.js.map +0 -1
- package/dist/resilience.d.ts +0 -34
- package/dist/resilience.d.ts.map +0 -1
- package/dist/resilience.js +0 -164
- package/dist/resilience.js.map +0 -1
- package/dist/result.d.ts +0 -57
- package/dist/result.d.ts.map +0 -1
- package/dist/result.js +0 -156
- package/dist/result.js.map +0 -1
- package/dist/run.d.ts +0 -27
- package/dist/run.d.ts.map +0 -1
- package/dist/run.js +0 -34
- package/dist/run.js.map +0 -1
- package/dist/serialization.d.ts +0 -27
- package/dist/serialization.d.ts.map +0 -1
- package/dist/serialization.js +0 -153
- package/dist/serialization.js.map +0 -1
- package/dist/service-config.d.ts +0 -22
- package/dist/service-config.d.ts.map +0 -1
- package/dist/service-config.js +0 -210
- package/dist/service-config.js.map +0 -1
- package/dist/service.d.ts +0 -71
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js +0 -56
- package/dist/service.js.map +0 -1
- package/dist/signal.d.ts +0 -33
- package/dist/signal.d.ts.map +0 -1
- package/dist/signal.js +0 -17
- package/dist/signal.js.map +0 -1
- package/dist/topo-store.d.ts +0 -48
- package/dist/topo-store.d.ts.map +0 -1
- package/dist/topo-store.js +0 -175
- package/dist/topo-store.js.map +0 -1
- package/dist/topo.d.ts +0 -25
- package/dist/topo.d.ts.map +0 -1
- package/dist/topo.js +0 -112
- package/dist/topo.js.map +0 -1
- package/dist/trail.d.ts +0 -96
- package/dist/trail.d.ts.map +0 -1
- package/dist/trail.js +0 -20
- package/dist/trail.js.map +0 -1
- package/dist/type-utils.d.ts +0 -24
- package/dist/type-utils.d.ts.map +0 -1
- package/dist/type-utils.js +0 -12
- package/dist/type-utils.js.map +0 -1
- package/dist/types.d.ts +0 -71
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/validate-established-topo.d.ts +0 -76
- package/dist/validate-established-topo.d.ts.map +0 -1
- package/dist/validate-established-topo.js +0 -43
- package/dist/validate-established-topo.js.map +0 -1
- package/dist/validate-topo.d.ts +0 -24
- package/dist/validate-topo.d.ts.map +0 -1
- package/dist/validate-topo.js +0 -176
- package/dist/validate-topo.js.map +0 -1
- package/dist/validation.d.ts +0 -27
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js +0 -165
- package/dist/validation.js.map +0 -1
- package/dist/workspace.d.ts +0 -25
- package/dist/workspace.d.ts.map +0 -1
- package/dist/workspace.js +0 -57
- package/dist/workspace.js.map +0 -1
- package/src/__tests__/blob-ref.test.ts +0 -103
- package/src/__tests__/branded.test.ts +0 -148
- package/src/__tests__/collections.test.ts +0 -126
- package/src/__tests__/context.test.ts +0 -77
- package/src/__tests__/derive.test.ts +0 -260
- package/src/__tests__/errors.test.ts +0 -309
- package/src/__tests__/execute.test.ts +0 -525
- package/src/__tests__/fetch.test.ts +0 -217
- package/src/__tests__/gate.test.ts +0 -125
- package/src/__tests__/guards.test.ts +0 -102
- package/src/__tests__/path-security.test.ts +0 -114
- package/src/__tests__/patterns.test.ts +0 -273
- package/src/__tests__/redaction.test.ts +0 -244
- package/src/__tests__/resilience.test.ts +0 -246
- package/src/__tests__/result.test.ts +0 -155
- package/src/__tests__/run.test.ts +0 -181
- package/src/__tests__/serialization.test.ts +0 -401
- package/src/__tests__/service-config.test.ts +0 -228
- package/src/__tests__/service.test.ts +0 -197
- package/src/__tests__/signal.test.ts +0 -82
- package/src/__tests__/topo-store-read.test.ts +0 -251
- package/src/__tests__/topo-store.test.ts +0 -469
- package/src/__tests__/topo.test.ts +0 -277
- package/src/__tests__/trail-permit.test.ts +0 -60
- package/src/__tests__/trail.test.ts +0 -261
- package/src/__tests__/trails-db.test.ts +0 -191
- package/src/__tests__/type-utils.test.ts +0 -90
- package/src/__tests__/validate-topo.test.ts +0 -489
- package/src/__tests__/validation.test.ts +0 -336
- package/src/__tests__/workspace.test.ts +0 -183
- package/src/event.ts +0 -15
- package/src/gate.ts +0 -44
- package/src/internal/topo-saves.ts +0 -429
- package/src/internal/topo-store-read.ts +0 -473
- package/src/internal/topo-store.ts +0 -1087
- package/src/provision-config.ts +0 -373
- package/src/provision.ts +0 -148
- package/src/topo-store.ts +0 -301
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { createBlobRef, isBlobRef } from '../blob-ref';
|
|
4
|
-
|
|
5
|
-
describe('BlobRef', () => {
|
|
6
|
-
const sampleData = new Uint8Array([137, 80, 78, 71]);
|
|
7
|
-
|
|
8
|
-
describe('createBlobRef', () => {
|
|
9
|
-
test('returns a frozen object with the provided fields', () => {
|
|
10
|
-
const ref = createBlobRef({
|
|
11
|
-
data: sampleData,
|
|
12
|
-
mimeType: 'image/png',
|
|
13
|
-
name: 'image.png',
|
|
14
|
-
size: 4,
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
expect(ref.name).toBe('image.png');
|
|
18
|
-
expect(ref.mimeType).toBe('image/png');
|
|
19
|
-
expect(ref.size).toBe(4);
|
|
20
|
-
expect(ref.data).toBe(sampleData);
|
|
21
|
-
expect(Object.isFrozen(ref)).toBe(true);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('works with ReadableStream data', () => {
|
|
25
|
-
const stream = new ReadableStream<Uint8Array>({
|
|
26
|
-
start(controller) {
|
|
27
|
-
controller.enqueue(sampleData);
|
|
28
|
-
controller.close();
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const ref = createBlobRef({
|
|
33
|
-
data: stream,
|
|
34
|
-
mimeType: 'video/mp4',
|
|
35
|
-
name: 'video.mp4',
|
|
36
|
-
size: 1024,
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
expect(ref.data).toBeInstanceOf(ReadableStream);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe('isBlobRef', () => {
|
|
44
|
-
test('returns true for a valid BlobRef with Uint8Array data', () => {
|
|
45
|
-
const ref = createBlobRef({
|
|
46
|
-
data: sampleData,
|
|
47
|
-
mimeType: 'application/octet-stream',
|
|
48
|
-
name: 'file.bin',
|
|
49
|
-
size: 4,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
expect(isBlobRef(ref)).toBe(true);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test('returns true for a valid BlobRef with ReadableStream data', () => {
|
|
56
|
-
const stream = new ReadableStream<Uint8Array>();
|
|
57
|
-
const ref = createBlobRef({
|
|
58
|
-
data: stream,
|
|
59
|
-
mimeType: 'application/octet-stream',
|
|
60
|
-
name: 'file.bin',
|
|
61
|
-
size: 0,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
expect(isBlobRef(ref)).toBe(true);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('returns true for a plain object matching the shape', () => {
|
|
68
|
-
expect(
|
|
69
|
-
isBlobRef({
|
|
70
|
-
data: new Uint8Array(),
|
|
71
|
-
mimeType: 'application/pdf',
|
|
72
|
-
name: 'doc.pdf',
|
|
73
|
-
size: 999,
|
|
74
|
-
})
|
|
75
|
-
).toBe(true);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('returns false for null', () => {
|
|
79
|
-
expect(isBlobRef(null)).toBe(false);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('returns false for a string', () => {
|
|
83
|
-
expect(isBlobRef('not a blob')).toBe(false);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test('returns false when name is missing', () => {
|
|
87
|
-
expect(
|
|
88
|
-
isBlobRef({ data: new Uint8Array(), mimeType: 'text/plain', size: 0 })
|
|
89
|
-
).toBe(false);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('returns false when data is a plain object', () => {
|
|
93
|
-
expect(
|
|
94
|
-
isBlobRef({
|
|
95
|
-
data: {},
|
|
96
|
-
mimeType: 'text/plain',
|
|
97
|
-
name: 'x',
|
|
98
|
-
size: 0,
|
|
99
|
-
})
|
|
100
|
-
).toBe(false);
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
});
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
brand,
|
|
5
|
-
unbrand,
|
|
6
|
-
uuid,
|
|
7
|
-
email,
|
|
8
|
-
nonEmptyString,
|
|
9
|
-
positiveInt,
|
|
10
|
-
shortId,
|
|
11
|
-
hashId,
|
|
12
|
-
} from '../branded';
|
|
13
|
-
|
|
14
|
-
describe('branded', () => {
|
|
15
|
-
describe('brand / unbrand', () => {
|
|
16
|
-
test('brand wraps and unbrand unwraps', () => {
|
|
17
|
-
const branded = brand('Tag', 'hello');
|
|
18
|
-
expect(unbrand(branded)).toBe('hello');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test('branded value is still usable as base type at runtime', () => {
|
|
22
|
-
const branded = brand('PositiveInt', 42);
|
|
23
|
-
expect(branded + 1).toBe(43);
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('uuid()', () => {
|
|
28
|
-
test('accepts a valid v4 UUID', () => {
|
|
29
|
-
const result = uuid('550e8400-e29b-41d4-a716-446655440000');
|
|
30
|
-
expect(result.isOk()).toBe(true);
|
|
31
|
-
const value: string = unbrand(result.unwrap());
|
|
32
|
-
expect(value).toBe('550e8400-e29b-41d4-a716-446655440000');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('rejects an empty string', () => {
|
|
36
|
-
const result = uuid('');
|
|
37
|
-
expect(result.isErr()).toBe(true);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('rejects a malformed UUID', () => {
|
|
41
|
-
const result = uuid('not-a-uuid');
|
|
42
|
-
expect(result.isErr()).toBe(true);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('rejects UUID missing hyphens', () => {
|
|
46
|
-
const result = uuid('550e8400e29b41d4a716446655440000');
|
|
47
|
-
expect(result.isErr()).toBe(true);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe('email()', () => {
|
|
52
|
-
test('accepts a simple email', () => {
|
|
53
|
-
const result = email('test@example.com');
|
|
54
|
-
expect(result.isOk()).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('rejects missing @', () => {
|
|
58
|
-
const result = email('testexample.com');
|
|
59
|
-
expect(result.isErr()).toBe(true);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('rejects missing domain', () => {
|
|
63
|
-
const result = email('test@');
|
|
64
|
-
expect(result.isErr()).toBe(true);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('rejects empty string', () => {
|
|
68
|
-
const result = email('');
|
|
69
|
-
expect(result.isErr()).toBe(true);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('nonEmptyString()', () => {
|
|
74
|
-
test('accepts a non-empty string', () => {
|
|
75
|
-
const result = nonEmptyString('hello');
|
|
76
|
-
expect(result.isOk()).toBe(true);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test('rejects an empty string', () => {
|
|
80
|
-
const result = nonEmptyString('');
|
|
81
|
-
expect(result.isErr()).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
describe('positiveInt()', () => {
|
|
86
|
-
test('accepts 1', () => {
|
|
87
|
-
const result = positiveInt(1);
|
|
88
|
-
expect(result.isOk()).toBe(true);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('accepts a large integer', () => {
|
|
92
|
-
const result = positiveInt(1_000_000);
|
|
93
|
-
expect(result.isOk()).toBe(true);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('rejects 0', () => {
|
|
97
|
-
const result = positiveInt(0);
|
|
98
|
-
expect(result.isErr()).toBe(true);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('rejects negative numbers', () => {
|
|
102
|
-
const result = positiveInt(-5);
|
|
103
|
-
expect(result.isErr()).toBe(true);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test('rejects non-integers', () => {
|
|
107
|
-
const result = positiveInt(1.5);
|
|
108
|
-
expect(result.isErr()).toBe(true);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe('shortId()', () => {
|
|
113
|
-
test('returns a string of default length 8', () => {
|
|
114
|
-
const id = shortId();
|
|
115
|
-
expect(id).toHaveLength(8);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test('respects custom length', () => {
|
|
119
|
-
const id = shortId(16);
|
|
120
|
-
expect(id).toHaveLength(16);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('contains only alphanumeric characters', () => {
|
|
124
|
-
const id = shortId(100);
|
|
125
|
-
expect(id).toMatch(/^[A-Za-z0-9]+$/);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test('produces unique values', () => {
|
|
129
|
-
const ids = new Set(Array.from({ length: 50 }, () => shortId()));
|
|
130
|
-
expect(ids.size).toBe(50);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
describe('hashId()', () => {
|
|
135
|
-
test('returns a hex string', () => {
|
|
136
|
-
const id = hashId('test');
|
|
137
|
-
expect(id).toMatch(/^[0-9a-f]{8}$/);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test('is deterministic', () => {
|
|
141
|
-
expect(hashId('hello')).toBe(hashId('hello'));
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test('different inputs produce different outputs', () => {
|
|
145
|
-
expect(hashId('a')).not.toBe(hashId('b'));
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
});
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
chunk,
|
|
5
|
-
dedupe,
|
|
6
|
-
groupBy,
|
|
7
|
-
sortBy,
|
|
8
|
-
isNonEmptyArray,
|
|
9
|
-
} from '../collections';
|
|
10
|
-
|
|
11
|
-
describe('collections', () => {
|
|
12
|
-
describe('chunk()', () => {
|
|
13
|
-
test('splits into even chunks', () => {
|
|
14
|
-
expect(chunk([1, 2, 3, 4], 2)).toEqual([
|
|
15
|
-
[1, 2],
|
|
16
|
-
[3, 4],
|
|
17
|
-
]);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test('handles remainder', () => {
|
|
21
|
-
expect(chunk([1, 2, 3, 4, 5], 2)).toEqual([[1, 2], [3, 4], [5]]);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('returns empty array for empty input', () => {
|
|
25
|
-
expect(chunk([], 3)).toEqual([]);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('single-element chunks', () => {
|
|
29
|
-
expect(chunk([1, 2, 3], 1)).toEqual([[1], [2], [3]]);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('chunk size larger than array', () => {
|
|
33
|
-
expect(chunk([1, 2], 10)).toEqual([[1, 2]]);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test('throws on size < 1', () => {
|
|
37
|
-
expect(() => chunk([1], 0)).toThrow(RangeError);
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe('dedupe()', () => {
|
|
42
|
-
test('removes primitive duplicates', () => {
|
|
43
|
-
expect(dedupe([1, 2, 2, 3, 1])).toEqual([1, 2, 3]);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('dedupes by key function', () => {
|
|
47
|
-
const items = [
|
|
48
|
-
{ id: 1, name: 'a' },
|
|
49
|
-
{ id: 2, name: 'b' },
|
|
50
|
-
{ id: 1, name: 'c' },
|
|
51
|
-
];
|
|
52
|
-
const result = dedupe(items, (i) => i.id);
|
|
53
|
-
expect(result).toEqual([
|
|
54
|
-
{ id: 1, name: 'a' },
|
|
55
|
-
{ id: 2, name: 'b' },
|
|
56
|
-
]);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('preserves order', () => {
|
|
60
|
-
expect(dedupe([3, 1, 2, 1, 3])).toEqual([3, 1, 2]);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('handles empty array', () => {
|
|
64
|
-
expect(dedupe([])).toEqual([]);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe('groupBy()', () => {
|
|
69
|
-
test('groups by key', () => {
|
|
70
|
-
const items = [
|
|
71
|
-
{ type: 'a', value: 1 },
|
|
72
|
-
{ type: 'b', value: 2 },
|
|
73
|
-
{ type: 'a', value: 3 },
|
|
74
|
-
];
|
|
75
|
-
const groups = groupBy(items, (i) => i.type);
|
|
76
|
-
expect(groups).toEqual({
|
|
77
|
-
a: [
|
|
78
|
-
{ type: 'a', value: 1 },
|
|
79
|
-
{ type: 'a', value: 3 },
|
|
80
|
-
],
|
|
81
|
-
b: [{ type: 'b', value: 2 }],
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test('returns empty object for empty array', () => {
|
|
86
|
-
expect(groupBy([], () => 'key')).toEqual({});
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe('sortBy()', () => {
|
|
91
|
-
test('sorts by numeric key ascending', () => {
|
|
92
|
-
const items = [{ n: 3 }, { n: 1 }, { n: 2 }];
|
|
93
|
-
expect(sortBy(items, (i) => i.n)).toEqual([{ n: 1 }, { n: 2 }, { n: 3 }]);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('sorts by string key alphabetically', () => {
|
|
97
|
-
const items = [{ name: 'charlie' }, { name: 'alice' }, { name: 'bob' }];
|
|
98
|
-
expect(sortBy(items, (i) => i.name)).toEqual([
|
|
99
|
-
{ name: 'alice' },
|
|
100
|
-
{ name: 'bob' },
|
|
101
|
-
{ name: 'charlie' },
|
|
102
|
-
]);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test('does not mutate the original array', () => {
|
|
106
|
-
const original = [{ n: 3 }, { n: 1 }];
|
|
107
|
-
sortBy(original, (i) => i.n);
|
|
108
|
-
expect(original[0]?.n).toBe(3);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test('returns empty array for empty input', () => {
|
|
112
|
-
expect(sortBy([], () => 0)).toEqual([]);
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
describe('isNonEmptyArray()', () => {
|
|
117
|
-
test('returns true for arrays with elements', () => {
|
|
118
|
-
expect(isNonEmptyArray([1])).toBe(true);
|
|
119
|
-
expect(isNonEmptyArray([1, 2, 3])).toBe(true);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test('returns false for empty array', () => {
|
|
123
|
-
expect(isNonEmptyArray([])).toBe(false);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
});
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { createTrailContext } from '../context.js';
|
|
4
|
-
import type { TrailContext } from '../types.js';
|
|
5
|
-
|
|
6
|
-
const UUID_RE =
|
|
7
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
8
|
-
|
|
9
|
-
describe('createTrailContext', () => {
|
|
10
|
-
test('generates a requestId in UUID format', () => {
|
|
11
|
-
const ctx = createTrailContext();
|
|
12
|
-
expect(ctx.requestId).toMatch(UUID_RE);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test('defaults cwd to process.cwd()', () => {
|
|
16
|
-
const ctx = createTrailContext();
|
|
17
|
-
expect(ctx.cwd).toBe(process.cwd());
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test('defaults env to process.env', () => {
|
|
21
|
-
const ctx = createTrailContext();
|
|
22
|
-
expect(ctx.env).toBeTruthy();
|
|
23
|
-
expect(ctx.env).toBe(process.env);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('provides a non-aborted abortSignal by default', () => {
|
|
27
|
-
const ctx = createTrailContext();
|
|
28
|
-
expect(ctx.abortSignal).toBeInstanceOf(AbortSignal);
|
|
29
|
-
expect(ctx.abortSignal.aborted).toBe(false);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('override values take precedence', () => {
|
|
33
|
-
const ac = new AbortController();
|
|
34
|
-
ac.abort();
|
|
35
|
-
|
|
36
|
-
const ctx = createTrailContext({
|
|
37
|
-
abortSignal: ac.signal,
|
|
38
|
-
cwd: '/custom/dir',
|
|
39
|
-
env: { CUSTOM: 'yes' },
|
|
40
|
-
requestId: 'custom-id',
|
|
41
|
-
workspaceRoot: '/tmp',
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
expect(ctx.cwd).toBe('/custom/dir');
|
|
45
|
-
expect(ctx.env).toEqual({ CUSTOM: 'yes' });
|
|
46
|
-
expect(ctx.requestId).toBe('custom-id');
|
|
47
|
-
expect(ctx.abortSignal.aborted).toBe(true);
|
|
48
|
-
expect(ctx.workspaceRoot).toBe('/tmp');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('TrailContext supports extensions for custom fields', () => {
|
|
52
|
-
const ctx: TrailContext = createTrailContext({
|
|
53
|
-
extensions: { customField: 42, nested: { key: 'value' } },
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
expect(ctx.extensions?.['customField']).toBe(42);
|
|
57
|
-
expect(ctx.extensions?.['nested']).toEqual({ key: 'value' });
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('provides a provision accessor backed by extensions', () => {
|
|
61
|
-
const widget = { id: 'widget-1' };
|
|
62
|
-
const ctx = createTrailContext({
|
|
63
|
-
extensions: { 'widget.main': widget },
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
expect(ctx.provision('widget.main')).toBe(widget);
|
|
67
|
-
expect(ctx.provision<{ id: string }>({ id: 'widget.main' }).id).toBe(
|
|
68
|
-
'widget-1'
|
|
69
|
-
);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('each call generates a unique requestId', () => {
|
|
73
|
-
const a = createTrailContext();
|
|
74
|
-
const b = createTrailContext();
|
|
75
|
-
expect(a.requestId).not.toBe(b.requestId);
|
|
76
|
-
});
|
|
77
|
-
});
|
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
import { deriveCliPath, deriveFields } from '../derive.js';
|
|
6
|
-
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
// Tests
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
describe('derive', () => {
|
|
12
|
-
describe('cli paths', () => {
|
|
13
|
-
test('top-level trail IDs derive to a one-segment CLI path', () => {
|
|
14
|
-
expect(deriveCliPath('search')).toEqual(['search']);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test('multi-dot trail IDs derive to the full ordered CLI path', () => {
|
|
18
|
-
expect(deriveCliPath('topo.pin.remove')).toEqual([
|
|
19
|
-
'topo',
|
|
20
|
-
'pin',
|
|
21
|
-
'remove',
|
|
22
|
-
]);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('trail IDs with consecutive dots throw a ValidationError', () => {
|
|
26
|
-
expect(() => deriveCliPath('topo..pin')).toThrow(
|
|
27
|
-
'Trail ID "topo..pin" contains an empty segment'
|
|
28
|
-
);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('trail IDs with a leading dot throw a ValidationError', () => {
|
|
32
|
-
expect(() => deriveCliPath('.pin')).toThrow(
|
|
33
|
-
'Trail ID ".pin" contains an empty segment'
|
|
34
|
-
);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('trail IDs with a trailing dot throw a ValidationError', () => {
|
|
38
|
-
expect(() => deriveCliPath('pin.')).toThrow(
|
|
39
|
-
'Trail ID "pin." contains an empty segment'
|
|
40
|
-
);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('primitive types', () => {
|
|
45
|
-
test('z.string() derives string field', () => {
|
|
46
|
-
const schema = z.object({ name: z.string() });
|
|
47
|
-
const fields = deriveFields(schema);
|
|
48
|
-
expect(fields).toHaveLength(1);
|
|
49
|
-
expect(fields[0]).toMatchObject({
|
|
50
|
-
label: 'Name',
|
|
51
|
-
name: 'name',
|
|
52
|
-
required: true,
|
|
53
|
-
type: 'string',
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('z.number() derives number field', () => {
|
|
58
|
-
const schema = z.object({ count: z.number() });
|
|
59
|
-
const fields = deriveFields(schema);
|
|
60
|
-
expect(fields[0]).toMatchObject({
|
|
61
|
-
name: 'count',
|
|
62
|
-
required: true,
|
|
63
|
-
type: 'number',
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('z.boolean() derives boolean field', () => {
|
|
68
|
-
const schema = z.object({ verbose: z.boolean() });
|
|
69
|
-
const fields = deriveFields(schema);
|
|
70
|
-
expect(fields[0]).toMatchObject({
|
|
71
|
-
name: 'verbose',
|
|
72
|
-
required: true,
|
|
73
|
-
type: 'boolean',
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
describe('enum and multiselect', () => {
|
|
79
|
-
test('z.enum() derives enum field with options', () => {
|
|
80
|
-
const schema = z.object({ color: z.enum(['red', 'green', 'blue']) });
|
|
81
|
-
const fields = deriveFields(schema);
|
|
82
|
-
expect(fields[0]).toMatchObject({
|
|
83
|
-
name: 'color',
|
|
84
|
-
required: true,
|
|
85
|
-
type: 'enum',
|
|
86
|
-
});
|
|
87
|
-
expect(fields[0]?.options).toEqual([
|
|
88
|
-
{ value: 'red' },
|
|
89
|
-
{ value: 'green' },
|
|
90
|
-
{ value: 'blue' },
|
|
91
|
-
]);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test('z.array(z.enum()) derives multiselect field', () => {
|
|
95
|
-
const schema = z.object({
|
|
96
|
-
tags: z.array(z.enum(['a', 'b', 'c'])),
|
|
97
|
-
});
|
|
98
|
-
const fields = deriveFields(schema);
|
|
99
|
-
expect(fields[0]).toMatchObject({
|
|
100
|
-
name: 'tags',
|
|
101
|
-
required: true,
|
|
102
|
-
type: 'multiselect',
|
|
103
|
-
});
|
|
104
|
-
expect(fields[0]?.options).toEqual([
|
|
105
|
-
{ value: 'a' },
|
|
106
|
-
{ value: 'b' },
|
|
107
|
-
{ value: 'c' },
|
|
108
|
-
]);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe('array types', () => {
|
|
113
|
-
test('z.array(z.string()) derives string[] field', () => {
|
|
114
|
-
const schema = z.object({ names: z.array(z.string()) });
|
|
115
|
-
const fields = deriveFields(schema);
|
|
116
|
-
expect(fields[0]).toMatchObject({
|
|
117
|
-
name: 'names',
|
|
118
|
-
options: undefined,
|
|
119
|
-
required: true,
|
|
120
|
-
type: 'string[]',
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test('z.array(z.number()) derives number[] field', () => {
|
|
125
|
-
const schema = z.object({ scores: z.array(z.number()) });
|
|
126
|
-
const fields = deriveFields(schema);
|
|
127
|
-
expect(fields[0]).toMatchObject({
|
|
128
|
-
name: 'scores',
|
|
129
|
-
options: undefined,
|
|
130
|
-
required: true,
|
|
131
|
-
type: 'number[]',
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test('optional z.array(z.string()) marks not required', () => {
|
|
136
|
-
const schema = z.object({ tags: z.array(z.string()).optional() });
|
|
137
|
-
const fields = deriveFields(schema);
|
|
138
|
-
expect(fields[0]).toMatchObject({
|
|
139
|
-
name: 'tags',
|
|
140
|
-
required: false,
|
|
141
|
-
type: 'string[]',
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test('z.array(z.string()) with default sets default value', () => {
|
|
146
|
-
const schema = z.object({
|
|
147
|
-
items: z.array(z.string()).default(['a', 'b']),
|
|
148
|
-
});
|
|
149
|
-
const fields = deriveFields(schema);
|
|
150
|
-
expect(fields[0]?.required).toBe(false);
|
|
151
|
-
expect(fields[0]?.type).toBe('string[]');
|
|
152
|
-
expect(fields[0]?.default).toEqual(['a', 'b']);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test('z.array(z.object(...)) is omitted when it cannot be represented faithfully', () => {
|
|
156
|
-
const schema = z.object({
|
|
157
|
-
files: z.array(
|
|
158
|
-
z.object({
|
|
159
|
-
content: z.string(),
|
|
160
|
-
filename: z.string(),
|
|
161
|
-
})
|
|
162
|
-
),
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
expect(deriveFields(schema)).toEqual([]);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
describe('unsupported shapes', () => {
|
|
170
|
-
test('nested objects are omitted when they cannot be represented faithfully', () => {
|
|
171
|
-
const schema = z.object({
|
|
172
|
-
filter: z.object({
|
|
173
|
-
query: z.string(),
|
|
174
|
-
}),
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
expect(deriveFields(schema)).toEqual([]);
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
describe('modifiers', () => {
|
|
182
|
-
test('.describe() sets label', () => {
|
|
183
|
-
const schema = z.object({
|
|
184
|
-
name: z.string().describe('Your full name'),
|
|
185
|
-
});
|
|
186
|
-
const fields = deriveFields(schema);
|
|
187
|
-
expect(fields[0]?.label).toBe('Your full name');
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test('.default() sets default and marks not required', () => {
|
|
191
|
-
const schema = z.object({
|
|
192
|
-
port: z.number().default(3000),
|
|
193
|
-
});
|
|
194
|
-
const fields = deriveFields(schema);
|
|
195
|
-
expect(fields[0]?.required).toBe(false);
|
|
196
|
-
expect(fields[0]?.default).toBe(3000);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test('.optional() marks not required', () => {
|
|
200
|
-
const schema = z.object({
|
|
201
|
-
nickname: z.string().optional(),
|
|
202
|
-
});
|
|
203
|
-
const fields = deriveFields(schema);
|
|
204
|
-
expect(fields[0]?.required).toBe(false);
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
describe('overrides', () => {
|
|
209
|
-
test('override label replaces derived label', () => {
|
|
210
|
-
const schema = z.object({
|
|
211
|
-
name: z.string().describe('Derived label'),
|
|
212
|
-
});
|
|
213
|
-
const fields = deriveFields(schema, {
|
|
214
|
-
name: { label: 'Override label' },
|
|
215
|
-
});
|
|
216
|
-
expect(fields[0]?.label).toBe('Override label');
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test('override options enrich enum values with labels and hints', () => {
|
|
220
|
-
const schema = z.object({
|
|
221
|
-
color: z.enum(['red', 'green', 'blue']),
|
|
222
|
-
});
|
|
223
|
-
const fields = deriveFields(schema, {
|
|
224
|
-
color: {
|
|
225
|
-
options: [
|
|
226
|
-
{ hint: 'Hot color', label: 'Red', value: 'red' },
|
|
227
|
-
{ label: 'Green', value: 'green' },
|
|
228
|
-
],
|
|
229
|
-
},
|
|
230
|
-
});
|
|
231
|
-
expect(fields[0]?.options).toEqual([
|
|
232
|
-
{ hint: 'Hot color', label: 'Red', value: 'red' },
|
|
233
|
-
{ label: 'Green', value: 'green' },
|
|
234
|
-
{ value: 'blue' },
|
|
235
|
-
]);
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
describe('sorting and edge cases', () => {
|
|
240
|
-
test('returns fields sorted by name', () => {
|
|
241
|
-
const schema = z.object({
|
|
242
|
-
alpha: z.string(),
|
|
243
|
-
middle: z.string(),
|
|
244
|
-
zebra: z.string(),
|
|
245
|
-
});
|
|
246
|
-
const fields = deriveFields(schema);
|
|
247
|
-
expect(fields.map((f) => f.name)).toEqual(['alpha', 'middle', 'zebra']);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
test('returns empty array for non-object schema', () => {
|
|
251
|
-
expect(deriveFields(z.string())).toEqual([]);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
test('humanizes camelCase field names', () => {
|
|
255
|
-
const schema = z.object({ firstName: z.string() });
|
|
256
|
-
const fields = deriveFields(schema);
|
|
257
|
-
expect(fields[0]?.label).toBe('First Name');
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
});
|