@ontrails/core 1.0.0-beta.15 → 1.0.0-beta.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/README.md +61 -16
- package/package.json +9 -8
- 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/context.ts +4 -0
- package/src/{internal/cross-batch.ts → cross-batch.ts} +3 -11
- package/src/detours.ts +8 -0
- package/src/draft.ts +4 -0
- package/src/error-projection.ts +51 -0
- package/src/errors.ts +192 -39
- package/src/execute.ts +443 -44
- package/src/fire.ts +965 -91
- package/src/index.ts +285 -38
- package/src/layer-projection.ts +192 -0
- package/src/layer.ts +32 -1
- package/src/observe.ts +361 -0
- package/src/permits.ts +12 -0
- package/src/redaction/patterns.ts +6 -3
- package/src/resilience.ts +8 -8
- package/src/resource-config.ts +477 -44
- package/src/resource.ts +21 -1
- package/src/result.ts +18 -0
- package/src/run.ts +2 -2
- package/src/schedule-runtime.ts +689 -0
- package/src/schedule.ts +324 -0
- package/src/serialization.ts +90 -57
- package/src/signal-diagnostics.ts +633 -0
- package/src/{internal/signal-ref.ts → signal-ref.ts} +1 -1
- package/src/signal.ts +37 -0
- package/src/store/accessor-protocol.ts +5 -5
- package/src/structured-examples.ts +245 -0
- package/src/surface-derivation.ts +91 -0
- package/src/surface-filter.ts +7 -13
- package/src/topo.ts +386 -6
- package/src/tracing.ts +345 -0
- package/src/trail.ts +168 -22
- package/src/trails/derive-trail.ts +1 -1
- package/src/trails/ingest.ts +7 -15
- package/src/{internal/trails-db.ts → trails-db.ts} +42 -21
- package/src/transport-error-map.ts +146 -28
- package/src/types.ts +82 -28
- package/src/validate-established-topo.ts +6 -0
- package/src/validate-topo.ts +212 -5
- package/src/validation.ts +58 -1
- package/src/webhook.ts +276 -0
- package/src/{internal/zod-wrappers.ts → zod-wrappers.ts} +0 -6
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +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 -22
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -37
- package/dist/context.js.map +0 -1
- package/dist/contour.d.ts +0 -81
- package/dist/contour.d.ts.map +0 -1
- package/dist/contour.js +0 -176
- package/dist/contour.js.map +0 -1
- package/dist/cross-schema.d.ts +0 -18
- package/dist/cross-schema.d.ts.map +0 -1
- package/dist/cross-schema.js +0 -29
- package/dist/cross-schema.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/draft.d.ts +0 -28
- package/dist/draft.d.ts.map +0 -1
- package/dist/draft.js +0 -157
- package/dist/draft.js.map +0 -1
- package/dist/errors.d.ts +0 -141
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -185
- package/dist/errors.js.map +0 -1
- package/dist/execute.d.ts +0 -52
- package/dist/execute.d.ts.map +0 -1
- package/dist/execute.js +0 -511
- 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/fire.d.ts +0 -43
- package/dist/fire.d.ts.map +0 -1
- package/dist/fire.js +0 -185
- package/dist/fire.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/index.d.ts +0 -59
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -61
- package/dist/index.js.map +0 -1
- package/dist/internal/cross-batch.d.ts +0 -44
- package/dist/internal/cross-batch.d.ts.map +0 -1
- package/dist/internal/cross-batch.js +0 -58
- package/dist/internal/cross-batch.js.map +0 -1
- package/dist/internal/fork-ctx.d.ts +0 -41
- package/dist/internal/fork-ctx.d.ts.map +0 -1
- package/dist/internal/fork-ctx.js +0 -47
- package/dist/internal/fork-ctx.js.map +0 -1
- package/dist/internal/signal-ref.d.ts +0 -15
- package/dist/internal/signal-ref.d.ts.map +0 -1
- package/dist/internal/signal-ref.js +0 -41
- package/dist/internal/signal-ref.js.map +0 -1
- package/dist/internal/topo-saves.d.ts +0 -53
- package/dist/internal/topo-saves.d.ts.map +0 -1
- package/dist/internal/topo-saves.js +0 -449
- package/dist/internal/topo-saves.js.map +0 -1
- package/dist/internal/topo-snapshots.d.ts +0 -57
- package/dist/internal/topo-snapshots.d.ts.map +0 -1
- package/dist/internal/topo-snapshots.js +0 -372
- package/dist/internal/topo-snapshots.js.map +0 -1
- package/dist/internal/topo-store-read.d.ts +0 -70
- package/dist/internal/topo-store-read.d.ts.map +0 -1
- package/dist/internal/topo-store-read.js +0 -221
- package/dist/internal/topo-store-read.js.map +0 -1
- package/dist/internal/topo-store.d.ts +0 -26
- package/dist/internal/topo-store.d.ts.map +0 -1
- package/dist/internal/topo-store.js +0 -621
- package/dist/internal/topo-store.js.map +0 -1
- package/dist/internal/tracing.d.ts +0 -98
- package/dist/internal/tracing.d.ts.map +0 -1
- package/dist/internal/tracing.js +0 -102
- package/dist/internal/tracing.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/internal/zod-wrappers.d.ts +0 -40
- package/dist/internal/zod-wrappers.d.ts.map +0 -1
- package/dist/internal/zod-wrappers.js +0 -59
- package/dist/internal/zod-wrappers.js.map +0 -1
- package/dist/layer.d.ts +0 -23
- package/dist/layer.d.ts.map +0 -1
- package/dist/layer.js +0 -21
- 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/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/resource-config.d.ts +0 -22
- package/dist/resource-config.d.ts.map +0 -1
- package/dist/resource-config.js +0 -210
- package/dist/resource-config.js.map +0 -1
- package/dist/resource.d.ts +0 -74
- package/dist/resource.d.ts.map +0 -1
- package/dist/resource.js +0 -61
- package/dist/resource.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 -28
- package/dist/run.d.ts.map +0 -1
- package/dist/run.js +0 -35
- 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 -154
- package/dist/serialization.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/store/accessor-protocol.d.ts +0 -51
- package/dist/store/accessor-protocol.d.ts.map +0 -1
- package/dist/store/accessor-protocol.js +0 -2
- package/dist/store/accessor-protocol.js.map +0 -1
- package/dist/store/index.d.ts +0 -2
- package/dist/store/index.d.ts.map +0 -1
- package/dist/store/index.js +0 -2
- package/dist/store/index.js.map +0 -1
- package/dist/surface-filter.d.ts +0 -13
- package/dist/surface-filter.d.ts.map +0 -1
- package/dist/surface-filter.js +0 -76
- package/dist/surface-filter.js.map +0 -1
- package/dist/topo-store.d.ts +0 -62
- package/dist/topo-store.d.ts.map +0 -1
- package/dist/topo-store.js +0 -319
- package/dist/topo-store.js.map +0 -1
- package/dist/topo.d.ts +0 -39
- package/dist/topo.d.ts.map +0 -1
- package/dist/topo.js +0 -241
- package/dist/topo.js.map +0 -1
- package/dist/trail.d.ts +0 -163
- package/dist/trail.d.ts.map +0 -1
- package/dist/trail.js +0 -75
- package/dist/trail.js.map +0 -1
- package/dist/trails/derive-trail.d.ts +0 -66
- package/dist/trails/derive-trail.d.ts.map +0 -1
- package/dist/trails/derive-trail.js +0 -378
- package/dist/trails/derive-trail.js.map +0 -1
- package/dist/trails/index.d.ts +0 -5
- package/dist/trails/index.d.ts.map +0 -1
- package/dist/trails/index.js +0 -3
- package/dist/trails/index.js.map +0 -1
- package/dist/trails/ingest.d.ts +0 -24
- package/dist/trails/ingest.d.ts.map +0 -1
- package/dist/trails/ingest.js +0 -63
- package/dist/trails/ingest.js.map +0 -1
- package/dist/transport-error-map.d.ts +0 -106
- package/dist/transport-error-map.d.ts.map +0 -1
- package/dist/transport-error-map.js +0 -24
- package/dist/transport-error-map.js.map +0 -1
- package/dist/type-checks.test-d.d.ts +0 -71
- package/dist/type-checks.test-d.d.ts.map +0 -1
- package/dist/type-checks.test-d.js +0 -12
- package/dist/type-checks.test-d.js.map +0 -1
- package/dist/type-utils.d.ts +0 -33
- 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 -177
- 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 -193
- 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__/contour.test.ts +0 -263
- package/src/__tests__/derive-trail.test.ts +0 -668
- package/src/__tests__/derive.test.ts +0 -260
- package/src/__tests__/errors.test.ts +0 -427
- package/src/__tests__/execute.test.ts +0 -1563
- package/src/__tests__/fetch.test.ts +0 -217
- package/src/__tests__/fire.test.ts +0 -1056
- package/src/__tests__/fork-ctx.test.ts +0 -83
- package/src/__tests__/guards.test.ts +0 -102
- package/src/__tests__/ingest.test.ts +0 -191
- package/src/__tests__/layer.test.ts +0 -125
- 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__/resource.test.ts +0 -204
- 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 -224
- package/src/__tests__/signal.test.ts +0 -82
- package/src/__tests__/surface-filter.test.ts +0 -196
- package/src/__tests__/topo-store-read.test.ts +0 -256
- package/src/__tests__/topo-store.test.ts +0 -869
- package/src/__tests__/topo.test.ts +0 -573
- package/src/__tests__/trail-permit.test.ts +0 -60
- package/src/__tests__/trail.test.ts +0 -426
- package/src/__tests__/trails-db.test.ts +0 -198
- package/src/__tests__/transport-error-map.test.ts +0 -82
- package/src/__tests__/type-utils.test.ts +0 -118
- package/src/__tests__/validate-topo.test.ts +0 -611
- package/src/__tests__/validation.test.ts +0 -336
- package/src/__tests__/workspace.test.ts +0 -183
- package/src/__tests__/zod-wrappers.test.ts +0 -62
- package/src/internal/topo-snapshots.ts +0 -517
- package/src/internal/topo-store-read.ts +0 -481
- package/src/internal/topo-store.ts +0 -1192
- package/src/internal/tracing.ts +0 -187
- package/src/topo-store.ts +0 -510
- package/src/type-checks.test-d.ts +0 -106
- package/tsconfig.json +0 -9
- package/tsconfig.tests.json +0 -10
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { forkCtx } from '../internal/fork-ctx';
|
|
4
|
-
import type { CrossFn, FireFn, TrailContext } from '../types';
|
|
5
|
-
|
|
6
|
-
// Sentinel values — the helper never invokes these, so their shapes are
|
|
7
|
-
// irrelevant. What matters is identity: a forked ctx must either clear
|
|
8
|
-
// them or preserve the exact reference.
|
|
9
|
-
const noopCross = {} as unknown as CrossFn;
|
|
10
|
-
const noopFire = {} as unknown as FireFn;
|
|
11
|
-
const noopResource = {} as unknown as TrailContext['resource'];
|
|
12
|
-
|
|
13
|
-
describe('forkCtx', () => {
|
|
14
|
-
test('clears cross, fire, and resource by default', () => {
|
|
15
|
-
const parent = {
|
|
16
|
-
cross: noopCross,
|
|
17
|
-
env: { NODE_ENV: 'test' },
|
|
18
|
-
extensions: { trailhead: 'cli' },
|
|
19
|
-
fire: noopFire,
|
|
20
|
-
logger: undefined,
|
|
21
|
-
resource: noopResource,
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const forked = forkCtx(parent, {});
|
|
25
|
-
|
|
26
|
-
expect(forked.cross).toBeUndefined();
|
|
27
|
-
expect(forked.fire).toBeUndefined();
|
|
28
|
-
expect(forked.resource).toBeUndefined();
|
|
29
|
-
// Non-reset keys are preserved.
|
|
30
|
-
expect(forked.env).toEqual({ NODE_ENV: 'test' });
|
|
31
|
-
expect(forked.extensions).toEqual({ trailhead: 'cli' });
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('applies overrides after reset so overrides win', () => {
|
|
35
|
-
const parent = {
|
|
36
|
-
cross: noopCross,
|
|
37
|
-
env: { NODE_ENV: 'test' },
|
|
38
|
-
extensions: { a: 1 },
|
|
39
|
-
fire: noopFire,
|
|
40
|
-
resource: noopResource,
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const forked = forkCtx(parent, {
|
|
44
|
-
env: { NODE_ENV: 'prod' },
|
|
45
|
-
extensions: { b: 2 },
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
expect(forked.env).toEqual({ NODE_ENV: 'prod' });
|
|
49
|
-
expect(forked.extensions).toEqual({ b: 2 });
|
|
50
|
-
expect(forked.cross).toBeUndefined();
|
|
51
|
-
expect(forked.fire).toBeUndefined();
|
|
52
|
-
expect(forked.resource).toBeUndefined();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test('does not mutate the parent context', () => {
|
|
56
|
-
const parent = {
|
|
57
|
-
cross: noopCross,
|
|
58
|
-
fire: noopFire,
|
|
59
|
-
resource: noopResource,
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
forkCtx(parent, {});
|
|
63
|
-
|
|
64
|
-
expect(parent.cross).toBe(noopCross);
|
|
65
|
-
expect(parent.fire).toBe(noopFire);
|
|
66
|
-
expect(parent.resource).toBe(noopResource);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('honours a caller-supplied reset list', () => {
|
|
70
|
-
const parent = {
|
|
71
|
-
cross: noopCross,
|
|
72
|
-
fire: noopFire,
|
|
73
|
-
resource: noopResource,
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const forked = forkCtx(parent, {}, ['fire']);
|
|
77
|
-
|
|
78
|
-
expect(forked.fire).toBeUndefined();
|
|
79
|
-
// Only `fire` is reset when caller overrides the list.
|
|
80
|
-
expect(forked.cross).toBe(noopCross);
|
|
81
|
-
expect(forked.resource).toBe(noopResource);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
isDefined,
|
|
5
|
-
isNonEmptyString,
|
|
6
|
-
isPlainObject,
|
|
7
|
-
hasProperty,
|
|
8
|
-
assertNever,
|
|
9
|
-
} from '../guards';
|
|
10
|
-
|
|
11
|
-
describe('guards', () => {
|
|
12
|
-
describe('isDefined()', () => {
|
|
13
|
-
test('returns true for non-nullish values', () => {
|
|
14
|
-
expect(isDefined(0)).toBe(true);
|
|
15
|
-
expect(isDefined('')).toBe(true);
|
|
16
|
-
expect(isDefined(false)).toBe(true);
|
|
17
|
-
expect(isDefined([])).toBe(true);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test('returns false for null', () => {
|
|
21
|
-
expect(isDefined(null)).toBe(false);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('returns false for undefined', () => {
|
|
25
|
-
expect(isDefined()).toBe(false);
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe('isNonEmptyString()', () => {
|
|
30
|
-
test('returns true for non-empty strings', () => {
|
|
31
|
-
expect(isNonEmptyString('hello')).toBe(true);
|
|
32
|
-
expect(isNonEmptyString(' ')).toBe(true);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('returns false for empty string', () => {
|
|
36
|
-
expect(isNonEmptyString('')).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('returns false for non-string types', () => {
|
|
40
|
-
expect(isNonEmptyString(42)).toBe(false);
|
|
41
|
-
expect(isNonEmptyString(null)).toBe(false);
|
|
42
|
-
expect(isNonEmptyString()).toBe(false);
|
|
43
|
-
expect(isNonEmptyString([])).toBe(false);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe('isPlainObject()', () => {
|
|
48
|
-
test('returns true for plain objects', () => {
|
|
49
|
-
expect(isPlainObject({})).toBe(true);
|
|
50
|
-
expect(isPlainObject({ a: 1 })).toBe(true);
|
|
51
|
-
expect(isPlainObject(Object.create(null))).toBe(true);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('returns false for arrays', () => {
|
|
55
|
-
expect(isPlainObject([])).toBe(false);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('returns false for class instances', () => {
|
|
59
|
-
expect(isPlainObject(new Date())).toBe(false);
|
|
60
|
-
expect(isPlainObject(new Map())).toBe(false);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('returns false for null', () => {
|
|
64
|
-
expect(isPlainObject(null)).toBe(false);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('returns false for primitives', () => {
|
|
68
|
-
expect(isPlainObject('string')).toBe(false);
|
|
69
|
-
expect(isPlainObject(42)).toBe(false);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('hasProperty()', () => {
|
|
74
|
-
test('returns true when key exists', () => {
|
|
75
|
-
expect(hasProperty({ name: 'test' }, 'name')).toBe(true);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('returns true for keys with undefined values', () => {
|
|
79
|
-
expect(hasProperty({ x: undefined }, 'x')).toBe(true);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('returns false when key is missing', () => {
|
|
83
|
-
expect(hasProperty({}, 'missing')).toBe(false);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test('returns false for null', () => {
|
|
87
|
-
expect(hasProperty(null, 'key')).toBe(false);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('returns false for primitives', () => {
|
|
91
|
-
expect(hasProperty(42, 'key')).toBe(false);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe('assertNever()', () => {
|
|
96
|
-
test('throws with a descriptive message', () => {
|
|
97
|
-
expect(() => assertNever('unexpected' as never)).toThrow(
|
|
98
|
-
'Unexpected value: unexpected'
|
|
99
|
-
);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
});
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
/* oxlint-disable require-await -- layer wrappers satisfy async interfaces without awaiting */
|
|
2
|
-
import { describe, expect, test } from 'bun:test';
|
|
3
|
-
|
|
4
|
-
import { z } from 'zod';
|
|
5
|
-
|
|
6
|
-
import { ValidationError } from '../errors.js';
|
|
7
|
-
import type { Layer } from '../layer.js';
|
|
8
|
-
import { Result } from '../result.js';
|
|
9
|
-
import { run } from '../run.js';
|
|
10
|
-
import { signal } from '../signal.js';
|
|
11
|
-
import { topo } from '../topo.js';
|
|
12
|
-
import { trail } from '../trail.js';
|
|
13
|
-
import { ingest } from '../trails/index.js';
|
|
14
|
-
|
|
15
|
-
const withExamples = <TSchema extends z.ZodType>(
|
|
16
|
-
schema: TSchema,
|
|
17
|
-
examples: readonly z.input<TSchema>[]
|
|
18
|
-
): TSchema & { readonly examples: readonly z.input<TSchema>[] } =>
|
|
19
|
-
Object.assign(schema, { examples });
|
|
20
|
-
|
|
21
|
-
const paymentCompleted = signal('payment.completed', {
|
|
22
|
-
payload: z.object({
|
|
23
|
-
amount: z.number(),
|
|
24
|
-
paymentId: z.string(),
|
|
25
|
-
}),
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const rawPaymentEvent = z.object({
|
|
29
|
-
amount: z.number(),
|
|
30
|
-
paymentId: z.string(),
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const stripeEvent = z.object({
|
|
34
|
-
data: z.object({
|
|
35
|
-
object: z.object({
|
|
36
|
-
amount: z.number(),
|
|
37
|
-
id: z.string(),
|
|
38
|
-
}),
|
|
39
|
-
}),
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const createConsumer = (
|
|
43
|
-
id: string,
|
|
44
|
-
capture: { payloads: unknown[] },
|
|
45
|
-
source = paymentCompleted
|
|
46
|
-
) =>
|
|
47
|
-
trail(id, {
|
|
48
|
-
blaze: (input) => {
|
|
49
|
-
capture.payloads.push(input);
|
|
50
|
-
return Result.ok({ ok: true });
|
|
51
|
-
},
|
|
52
|
-
input: source.payload,
|
|
53
|
-
on: [source],
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe('ingest()', () => {
|
|
57
|
-
test('derives id, intent, fires, output schema, and examples from schema examples', () => {
|
|
58
|
-
const schema = withExamples(rawPaymentEvent, [
|
|
59
|
-
{ amount: 42, paymentId: 'pay_1' },
|
|
60
|
-
{ amount: 7, paymentId: 'pay_2' },
|
|
61
|
-
]);
|
|
62
|
-
|
|
63
|
-
const paymentIngest = ingest({
|
|
64
|
-
schema,
|
|
65
|
-
signal: paymentCompleted,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
expect(paymentIngest.id).toBe('payment.completed.ingest');
|
|
69
|
-
expect(paymentIngest.intent).toBe('write');
|
|
70
|
-
expect(paymentIngest.pattern).toBe('ingest');
|
|
71
|
-
expect(paymentIngest.fires).toEqual(['payment.completed']);
|
|
72
|
-
expect(paymentIngest.output?.safeParse().success).toBe(true);
|
|
73
|
-
expect(paymentIngest.examples).toEqual([
|
|
74
|
-
{
|
|
75
|
-
input: { amount: 42, paymentId: 'pay_1' },
|
|
76
|
-
name: 'Ingest payment.completed 1',
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
input: { amount: 7, paymentId: 'pay_2' },
|
|
80
|
-
name: 'Ingest payment.completed 2',
|
|
81
|
-
},
|
|
82
|
-
]);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test('validates input before attempting to emit the signal', async () => {
|
|
86
|
-
const capture = { payloads: [] as unknown[] };
|
|
87
|
-
const paymentIngest = ingest({
|
|
88
|
-
schema: rawPaymentEvent,
|
|
89
|
-
signal: paymentCompleted,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const app = topo('ingest-validation', {
|
|
93
|
-
consumer: createConsumer('payments.capture', capture),
|
|
94
|
-
paymentCompleted,
|
|
95
|
-
paymentIngest,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const result = await run(app, paymentIngest.id, {
|
|
99
|
-
amount: '42',
|
|
100
|
-
paymentId: 'pay_1',
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
expect(result.isErr()).toBe(true);
|
|
104
|
-
expect(result.error).toBeInstanceOf(ValidationError);
|
|
105
|
-
expect(capture.payloads).toEqual([]);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test('applies the verification layer when provided', async () => {
|
|
109
|
-
const wrapped: string[] = [];
|
|
110
|
-
const verify: Layer = {
|
|
111
|
-
name: 'verify',
|
|
112
|
-
wrap: (trailDef, implementation) => async (input, ctx) => {
|
|
113
|
-
wrapped.push(trailDef.id);
|
|
114
|
-
return await implementation(input, ctx);
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
const capture = { payloads: [] as unknown[] };
|
|
118
|
-
const paymentIngest = ingest({
|
|
119
|
-
schema: rawPaymentEvent,
|
|
120
|
-
signal: paymentCompleted,
|
|
121
|
-
verify,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const app = topo('ingest-verify', {
|
|
125
|
-
consumer: createConsumer('payments.capture', capture),
|
|
126
|
-
paymentCompleted,
|
|
127
|
-
paymentIngest,
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const result = await run(app, paymentIngest.id, {
|
|
131
|
-
amount: 42,
|
|
132
|
-
paymentId: 'pay_1',
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
expect(result.isOk()).toBe(true);
|
|
136
|
-
expect(wrapped).toEqual(['payment.completed.ingest']);
|
|
137
|
-
expect(capture.payloads).toEqual([{ amount: 42, paymentId: 'pay_1' }]);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test('emits the declared signal after successful ingestion', async () => {
|
|
141
|
-
const capture = { payloads: [] as unknown[] };
|
|
142
|
-
const paymentIngest = ingest({
|
|
143
|
-
schema: rawPaymentEvent,
|
|
144
|
-
signal: paymentCompleted,
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
const app = topo('ingest-fire', {
|
|
148
|
-
consumer: createConsumer('payments.capture', capture),
|
|
149
|
-
paymentCompleted,
|
|
150
|
-
paymentIngest,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
const result = await run(app, paymentIngest.id, {
|
|
154
|
-
amount: 42,
|
|
155
|
-
paymentId: 'pay_1',
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
expect(result.isOk()).toBe(true);
|
|
159
|
-
expect(capture.payloads).toEqual([{ amount: 42, paymentId: 'pay_1' }]);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test('applies the transform before emitting the signal payload', async () => {
|
|
163
|
-
const capture = { payloads: [] as unknown[] };
|
|
164
|
-
const paymentIngest = ingest({
|
|
165
|
-
schema: stripeEvent,
|
|
166
|
-
signal: paymentCompleted,
|
|
167
|
-
transform: (payload) => ({
|
|
168
|
-
amount: payload.data.object.amount,
|
|
169
|
-
paymentId: payload.data.object.id,
|
|
170
|
-
}),
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
const app = topo('ingest-transform', {
|
|
174
|
-
consumer: createConsumer('payments.capture', capture),
|
|
175
|
-
paymentCompleted,
|
|
176
|
-
paymentIngest,
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
const result = await run(app, paymentIngest.id, {
|
|
180
|
-
data: {
|
|
181
|
-
object: {
|
|
182
|
-
amount: 99,
|
|
183
|
-
id: 'pay_99',
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
expect(result.isOk()).toBe(true);
|
|
189
|
-
expect(capture.payloads).toEqual([{ amount: 99, paymentId: 'pay_99' }]);
|
|
190
|
-
});
|
|
191
|
-
});
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/* oxlint-disable require-await -- layer wrappers satisfy async interfaces without awaiting */
|
|
2
|
-
import { describe, test, expect } from 'bun:test';
|
|
3
|
-
|
|
4
|
-
import { z } from 'zod';
|
|
5
|
-
|
|
6
|
-
import { createTrailContext } from '../context';
|
|
7
|
-
import { composeLayers } from '../layer';
|
|
8
|
-
import type { Layer } from '../layer';
|
|
9
|
-
import { Result } from '../result';
|
|
10
|
-
import { trail } from '../trail';
|
|
11
|
-
import type { TrailContext } from '../types';
|
|
12
|
-
|
|
13
|
-
const stubCtx: TrailContext = createTrailContext({
|
|
14
|
-
abortSignal: AbortSignal.timeout(5000),
|
|
15
|
-
requestId: 'test-layer',
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
const echoTrail = trail('echo', {
|
|
19
|
-
blaze: (input) => Result.ok({ value: input.value }),
|
|
20
|
-
input: z.object({ value: z.string() }),
|
|
21
|
-
meta: { domain: 'test' },
|
|
22
|
-
output: z.object({ value: z.string() }),
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// Layer tests
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
describe('Layer', () => {
|
|
30
|
-
test('single layer wraps implementation', async () => {
|
|
31
|
-
const prefixGate: Layer = {
|
|
32
|
-
name: 'prefix',
|
|
33
|
-
wrap(_trail, impl) {
|
|
34
|
-
return async (input, ctx) => {
|
|
35
|
-
const result = await impl(input, ctx);
|
|
36
|
-
return result.map((out) => ({
|
|
37
|
-
...out,
|
|
38
|
-
value: `prefixed:${(out as { value: string }).value}`,
|
|
39
|
-
}));
|
|
40
|
-
};
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const wrapped = composeLayers([prefixGate], echoTrail, echoTrail.blaze);
|
|
45
|
-
const result = await wrapped({ value: 'hello' }, stubCtx);
|
|
46
|
-
|
|
47
|
-
expect(result.isOk()).toBe(true);
|
|
48
|
-
expect(result.unwrap()).toEqual({ value: 'prefixed:hello' });
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('multiple layers compose in outermost-first order', async () => {
|
|
52
|
-
const log: string[] = [];
|
|
53
|
-
|
|
54
|
-
const outer: Layer = {
|
|
55
|
-
name: 'outer',
|
|
56
|
-
wrap(_trail, impl) {
|
|
57
|
-
return async (input, ctx) => {
|
|
58
|
-
log.push('outer:before');
|
|
59
|
-
const r = await impl(input, ctx);
|
|
60
|
-
log.push('outer:after');
|
|
61
|
-
return r;
|
|
62
|
-
};
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const inner: Layer = {
|
|
67
|
-
name: 'inner',
|
|
68
|
-
wrap(_trail, impl) {
|
|
69
|
-
return async (input, ctx) => {
|
|
70
|
-
log.push('inner:before');
|
|
71
|
-
const r = await impl(input, ctx);
|
|
72
|
-
log.push('inner:after');
|
|
73
|
-
return r;
|
|
74
|
-
};
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const wrapped = composeLayers([outer, inner], echoTrail, echoTrail.blaze);
|
|
79
|
-
await wrapped({ value: 'x' }, stubCtx);
|
|
80
|
-
|
|
81
|
-
expect(log).toEqual([
|
|
82
|
-
'outer:before',
|
|
83
|
-
'inner:before',
|
|
84
|
-
'inner:after',
|
|
85
|
-
'outer:after',
|
|
86
|
-
]);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('layer can short-circuit without calling inner implementation', async () => {
|
|
90
|
-
const shortCircuit: Layer = {
|
|
91
|
-
name: 'short-circuit',
|
|
92
|
-
wrap() {
|
|
93
|
-
return async () => Result.err(new Error('blocked'));
|
|
94
|
-
},
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const wrapped = composeLayers([shortCircuit], echoTrail, echoTrail.blaze);
|
|
98
|
-
const result = await wrapped({ value: 'hello' }, stubCtx);
|
|
99
|
-
|
|
100
|
-
expect(result.isErr()).toBe(true);
|
|
101
|
-
const err = result as unknown as { error: Error };
|
|
102
|
-
expect(err.error.message).toBe('blocked');
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test('layer can inspect trail meta', () => {
|
|
106
|
-
let capturedDomain: unknown;
|
|
107
|
-
|
|
108
|
-
const inspectGate: Layer = {
|
|
109
|
-
name: 'inspect',
|
|
110
|
-
wrap(t, impl) {
|
|
111
|
-
capturedDomain = t.meta?.['domain'];
|
|
112
|
-
return impl;
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
composeLayers([inspectGate], echoTrail, echoTrail.blaze);
|
|
117
|
-
|
|
118
|
-
expect(capturedDomain).toBe('test');
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test('empty layers array returns implementation unchanged', () => {
|
|
122
|
-
const wrapped = composeLayers([], echoTrail, echoTrail.blaze);
|
|
123
|
-
expect(wrapped).toBe(echoTrail.blaze);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
import { resolve, join } from 'node:path';
|
|
3
|
-
|
|
4
|
-
import { PermissionError } from '../errors.js';
|
|
5
|
-
import { securePath, isPathSafe, deriveSafePath } from '../path-security.js';
|
|
6
|
-
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
// securePath
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
describe('securePath', () => {
|
|
12
|
-
const base = '/project/workspace';
|
|
13
|
-
|
|
14
|
-
test('resolves a simple relative path', () => {
|
|
15
|
-
const result = securePath(base, 'src/index.ts');
|
|
16
|
-
expect(result.isOk()).toBe(true);
|
|
17
|
-
expect(result.unwrap()).toBe(resolve(base, 'src/index.ts'));
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test('resolves nested paths', () => {
|
|
21
|
-
const result = securePath(base, 'src/../lib/utils.ts');
|
|
22
|
-
expect(result.isOk()).toBe(true);
|
|
23
|
-
expect(result.unwrap()).toBe(resolve(base, 'lib/utils.ts'));
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('accepts the base directory itself', () => {
|
|
27
|
-
const result = securePath(base, '.');
|
|
28
|
-
expect(result.isOk()).toBe(true);
|
|
29
|
-
expect(result.unwrap()).toBe(resolve(base));
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('rejects path traversal with ..', () => {
|
|
33
|
-
const result = securePath(base, '../../etc/passwd');
|
|
34
|
-
expect(result.isErr()).toBe(true);
|
|
35
|
-
const err = result as unknown as { error: PermissionError };
|
|
36
|
-
expect(err.error).toBeInstanceOf(PermissionError);
|
|
37
|
-
expect(err.error.message).toContain('Path traversal detected');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('rejects absolute paths outside base', () => {
|
|
41
|
-
const result = securePath(base, '/etc/passwd');
|
|
42
|
-
expect(result.isErr()).toBe(true);
|
|
43
|
-
const err = result as unknown as { error: PermissionError };
|
|
44
|
-
expect(err.error).toBeInstanceOf(PermissionError);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('rejects sneaky traversal (subdir/../../..)', () => {
|
|
48
|
-
const result = securePath(base, 'src/../../..');
|
|
49
|
-
expect(result.isErr()).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('allows paths that mention .. but stay inside', () => {
|
|
53
|
-
const result = securePath(base, 'src/../src/file.ts');
|
|
54
|
-
expect(result.isOk()).toBe(true);
|
|
55
|
-
expect(result.unwrap()).toBe(resolve(base, 'src/file.ts'));
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
// isPathSafe
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
|
|
63
|
-
describe('isPathSafe', () => {
|
|
64
|
-
const base = '/project/workspace';
|
|
65
|
-
|
|
66
|
-
test('returns true for child paths', () => {
|
|
67
|
-
expect(isPathSafe(base, 'src/index.ts')).toBe(true);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('returns true for the base directory', () => {
|
|
71
|
-
expect(isPathSafe(base, '.')).toBe(true);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test('returns false for traversal', () => {
|
|
75
|
-
expect(isPathSafe(base, '../secret')).toBe(false);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('returns false for absolute paths outside base', () => {
|
|
79
|
-
expect(isPathSafe(base, '/tmp/evil')).toBe(false);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
|
-
// deriveSafePath
|
|
85
|
-
// ---------------------------------------------------------------------------
|
|
86
|
-
|
|
87
|
-
describe('deriveSafePath', () => {
|
|
88
|
-
const base = '/project/workspace';
|
|
89
|
-
|
|
90
|
-
test('joins multiple segments safely', () => {
|
|
91
|
-
const result = deriveSafePath(base, 'src', 'lib', 'utils.ts');
|
|
92
|
-
expect(result.isOk()).toBe(true);
|
|
93
|
-
expect(result.unwrap()).toBe(join(resolve(base), 'src', 'lib', 'utils.ts'));
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('rejects when segments escape the base', () => {
|
|
97
|
-
const result = deriveSafePath(base, 'src', '../../../etc');
|
|
98
|
-
expect(result.isErr()).toBe(true);
|
|
99
|
-
const err = result as unknown as { error: PermissionError };
|
|
100
|
-
expect(err.error).toBeInstanceOf(PermissionError);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test('handles single segment', () => {
|
|
104
|
-
const result = deriveSafePath(base, 'file.txt');
|
|
105
|
-
expect(result.isOk()).toBe(true);
|
|
106
|
-
expect(result.unwrap()).toBe(resolve(base, 'file.txt'));
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test('handles empty segments', () => {
|
|
110
|
-
const result = deriveSafePath(base);
|
|
111
|
-
expect(result.isOk()).toBe(true);
|
|
112
|
-
expect(result.unwrap()).toBe(resolve(base));
|
|
113
|
-
});
|
|
114
|
-
});
|