@ontrails/core 1.0.0-beta.12 → 1.0.0-beta.13
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 +59 -35
- package/README.md +19 -19
- package/dist/context.d.ts +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +5 -4
- package/dist/context.js.map +1 -1
- package/dist/derive.d.ts +8 -3
- package/dist/derive.d.ts.map +1 -1
- package/dist/derive.js +7 -7
- package/dist/derive.js.map +1 -1
- package/dist/event.d.ts +4 -41
- package/dist/event.d.ts.map +1 -1
- package/dist/event.js +4 -14
- package/dist/event.js.map +1 -1
- package/dist/execute.d.ts +10 -10
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +35 -28
- package/dist/execute.js.map +1 -1
- package/dist/gate.d.ts +17 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +21 -0
- package/dist/gate.js.map +1 -0
- package/dist/index.d.ts +10 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -8
- package/dist/index.js.map +1 -1
- package/dist/layer.d.ts +5 -16
- package/dist/layer.d.ts.map +1 -1
- package/dist/layer.js +3 -20
- package/dist/layer.js.map +1 -1
- package/dist/provision-config.d.ts +22 -0
- package/dist/provision-config.d.ts.map +1 -0
- package/dist/provision-config.js +210 -0
- package/dist/provision-config.js.map +1 -0
- package/dist/provision.d.ts +71 -0
- package/dist/provision.d.ts.map +1 -0
- package/dist/provision.js +56 -0
- package/dist/provision.js.map +1 -0
- package/dist/run.d.ts +27 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +34 -0
- package/dist/run.js.map +1 -0
- package/dist/service-config.d.ts +7 -7
- package/dist/service-config.d.ts.map +1 -1
- package/dist/service-config.js +75 -73
- package/dist/service-config.js.map +1 -1
- package/dist/service.d.ts +32 -36
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +21 -21
- package/dist/service.js.map +1 -1
- package/dist/signal.d.ts +33 -0
- package/dist/signal.d.ts.map +1 -0
- package/dist/signal.js +17 -0
- package/dist/signal.js.map +1 -0
- package/dist/topo.d.ts +10 -10
- package/dist/topo.d.ts.map +1 -1
- package/dist/topo.js +48 -35
- package/dist/topo.js.map +1 -1
- package/dist/trail.d.ts +16 -16
- package/dist/trail.d.ts.map +1 -1
- package/dist/trail.js +5 -4
- package/dist/trail.js.map +1 -1
- package/dist/types.d.ts +10 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -2
- package/dist/types.js.map +1 -1
- package/dist/validate-topo.d.ts +2 -2
- package/dist/validate-topo.js +31 -31
- package/dist/validate-topo.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/context.test.ts +8 -8
- package/src/__tests__/execute.test.ts +124 -122
- package/src/__tests__/{layer.test.ts → gate.test.ts} +26 -26
- package/src/__tests__/{dispatch.test.ts → run.test.ts} +39 -39
- package/src/__tests__/service-config.test.ts +41 -37
- package/src/__tests__/service.test.ts +71 -71
- package/src/__tests__/{event.test.ts → signal.test.ts} +15 -15
- package/src/__tests__/topo.test.ts +54 -54
- package/src/__tests__/trail-permit.test.ts +4 -4
- package/src/__tests__/trail.test.ts +58 -58
- package/src/__tests__/type-utils.test.ts +3 -3
- package/src/__tests__/validate-topo.test.ts +38 -38
- package/src/context.ts +5 -4
- package/src/derive.ts +8 -8
- package/src/event.ts +11 -73
- package/src/execute.ts +56 -40
- package/src/{layer.ts → gate.ts} +13 -13
- package/src/index.ts +24 -22
- package/src/{service-config.ts → provision-config.ts} +124 -105
- package/src/provision.ts +148 -0
- package/src/{dispatch.ts → run.ts} +8 -8
- package/src/signal.ts +65 -0
- package/src/topo.ts +74 -52
- package/src/trail.ts +23 -22
- package/src/types.ts +11 -11
- package/src/validate-topo.ts +33 -33
- package/tsconfig.tsbuildinfo +1 -1
- package/src/service.ts +0 -145
package/src/index.ts
CHANGED
|
@@ -31,33 +31,33 @@ export type {
|
|
|
31
31
|
Implementation,
|
|
32
32
|
TrailContext,
|
|
33
33
|
TrailContextInit,
|
|
34
|
-
|
|
34
|
+
CrossFn,
|
|
35
35
|
BasePermit,
|
|
36
36
|
PermitRequirement,
|
|
37
37
|
ProgressCallback,
|
|
38
38
|
ProgressEvent,
|
|
39
39
|
Logger,
|
|
40
|
-
|
|
40
|
+
ProvisionLookup,
|
|
41
41
|
} from './types.js';
|
|
42
|
-
export {
|
|
42
|
+
export { TRAILHEAD_KEY } from './types.js';
|
|
43
43
|
|
|
44
44
|
// Context factory
|
|
45
45
|
export { createTrailContext } from './context.js';
|
|
46
46
|
|
|
47
|
-
//
|
|
47
|
+
// Provision
|
|
48
48
|
export {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
} from './
|
|
49
|
+
createProvisionLookup,
|
|
50
|
+
findDuplicateProvisionId,
|
|
51
|
+
isProvision,
|
|
52
|
+
provision,
|
|
53
|
+
} from './provision.js';
|
|
54
54
|
export type {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
} from './
|
|
55
|
+
AnyProvision,
|
|
56
|
+
Provision,
|
|
57
|
+
ProvisionContext,
|
|
58
|
+
ProvisionOverrideMap,
|
|
59
|
+
ProvisionSpec,
|
|
60
|
+
} from './provision.js';
|
|
61
61
|
|
|
62
62
|
// Trail
|
|
63
63
|
export { trail } from './trail.js';
|
|
@@ -73,7 +73,9 @@ export type {
|
|
|
73
73
|
export type { TrailInput, TrailOutput, TrailResult } from './type-utils.js';
|
|
74
74
|
export { inputOf, outputOf } from './type-utils.js';
|
|
75
75
|
|
|
76
|
-
//
|
|
76
|
+
// Signal
|
|
77
|
+
export { signal } from './signal.js';
|
|
78
|
+
export type { AnySignal, Signal, SignalSpec } from './signal.js';
|
|
77
79
|
export { event } from './event.js';
|
|
78
80
|
export type { AnyEvent, Event, EventSpec } from './event.js';
|
|
79
81
|
|
|
@@ -85,9 +87,9 @@ export type { Topo } from './topo.js';
|
|
|
85
87
|
export { validateTopo } from './validate-topo.js';
|
|
86
88
|
export type { TopoIssue } from './validate-topo.js';
|
|
87
89
|
|
|
88
|
-
//
|
|
89
|
-
export {
|
|
90
|
-
export type {
|
|
90
|
+
// Gate
|
|
91
|
+
export { composeGates } from './gate.js';
|
|
92
|
+
export type { Gate } from './gate.js';
|
|
91
93
|
|
|
92
94
|
// Derive
|
|
93
95
|
export { deriveFields } from './derive.js';
|
|
@@ -97,9 +99,9 @@ export type { Field, FieldOverride } from './derive.js';
|
|
|
97
99
|
export { executeTrail } from './execute.js';
|
|
98
100
|
export type { ExecuteTrailOptions } from './execute.js';
|
|
99
101
|
|
|
100
|
-
//
|
|
101
|
-
export {
|
|
102
|
-
export type {
|
|
102
|
+
// Run
|
|
103
|
+
export { run } from './run.js';
|
|
104
|
+
export type { RunOptions } from './run.js';
|
|
103
105
|
|
|
104
106
|
// Validation
|
|
105
107
|
export {
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Provision resolution pipeline.
|
|
3
3
|
*
|
|
4
4
|
* Extracted from execute.ts to keep both modules under the 400 LOC ceiling.
|
|
5
5
|
* Handles config validation, singleton caching, concurrent-creation dedup,
|
|
6
|
-
* and the full resolve-or-create flow for declared
|
|
6
|
+
* and the full resolve-or-create flow for declared provisions.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from './
|
|
10
|
+
AnyProvision,
|
|
11
|
+
ProvisionContext,
|
|
12
|
+
ProvisionOverrideMap,
|
|
13
|
+
} from './provision.js';
|
|
14
14
|
import type { AnyTrail } from './trail.js';
|
|
15
15
|
import type { TrailContext } from './types.js';
|
|
16
16
|
|
|
17
17
|
import { InternalError, ValidationError } from './errors.js';
|
|
18
18
|
import { Result } from './result.js';
|
|
19
|
-
import {
|
|
19
|
+
import { createProvisionLookup } from './provision.js';
|
|
20
20
|
|
|
21
21
|
type MutableTrailContext = {
|
|
22
22
|
-readonly [K in keyof TrailContext]: TrailContext[K];
|
|
@@ -28,11 +28,11 @@ type ConfigValues = Readonly<Record<string, Record<string, unknown>>>;
|
|
|
28
28
|
// Singleton caches
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
30
30
|
|
|
31
|
-
const
|
|
31
|
+
const singletonProvisions = new WeakMap<AnyProvision, Map<string, unknown>>();
|
|
32
32
|
|
|
33
|
-
/** In-flight
|
|
33
|
+
/** In-flight provision creation promises, keyed by provision x context. */
|
|
34
34
|
const pendingCreations = new WeakMap<
|
|
35
|
-
|
|
35
|
+
AnyProvision,
|
|
36
36
|
Map<string, Promise<Result<unknown, Error>>>
|
|
37
37
|
>();
|
|
38
38
|
|
|
@@ -40,17 +40,17 @@ const pendingCreations = new WeakMap<
|
|
|
40
40
|
// Context helpers
|
|
41
41
|
// ---------------------------------------------------------------------------
|
|
42
42
|
|
|
43
|
-
const
|
|
43
|
+
const toProvisionContext = (
|
|
44
44
|
ctx: TrailContext,
|
|
45
45
|
config?: unknown
|
|
46
|
-
):
|
|
46
|
+
): ProvisionContext => ({
|
|
47
47
|
config,
|
|
48
48
|
cwd: ctx.cwd,
|
|
49
49
|
env: ctx.env,
|
|
50
50
|
workspaceRoot: ctx.workspaceRoot,
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
const
|
|
53
|
+
const toProvisionContextKey = (ctx: ProvisionContext): string =>
|
|
54
54
|
JSON.stringify({
|
|
55
55
|
config: ctx.config,
|
|
56
56
|
cwd: ctx.cwd,
|
|
@@ -64,27 +64,27 @@ const toServiceContextKey = (ctx: ServiceContext): string =>
|
|
|
64
64
|
// Config validation
|
|
65
65
|
// ---------------------------------------------------------------------------
|
|
66
66
|
|
|
67
|
-
/** Validate and resolve a
|
|
68
|
-
const
|
|
69
|
-
|
|
67
|
+
/** Validate and resolve a provision's config from the provided configValues map. */
|
|
68
|
+
const resolveProvisionConfig = (
|
|
69
|
+
declaredProvision: AnyProvision,
|
|
70
70
|
configValues?: ConfigValues
|
|
71
71
|
): Result<unknown, Error> => {
|
|
72
|
-
if (
|
|
72
|
+
if (declaredProvision.config === undefined) {
|
|
73
73
|
return Result.ok();
|
|
74
74
|
}
|
|
75
|
-
const raw = configValues?.[
|
|
75
|
+
const raw = configValues?.[declaredProvision.id];
|
|
76
76
|
if (raw === undefined) {
|
|
77
77
|
return Result.err(
|
|
78
78
|
new ValidationError(
|
|
79
|
-
`
|
|
79
|
+
`Provision "${declaredProvision.id}" declares a config schema but no config was provided`
|
|
80
80
|
)
|
|
81
81
|
);
|
|
82
82
|
}
|
|
83
|
-
const parsed =
|
|
83
|
+
const parsed = declaredProvision.config.safeParse(raw);
|
|
84
84
|
if (!parsed.success) {
|
|
85
85
|
return Result.err(
|
|
86
86
|
new ValidationError(
|
|
87
|
-
`
|
|
87
|
+
`Provision "${declaredProvision.id}" config validation failed: ${parsed.error.message}`
|
|
88
88
|
)
|
|
89
89
|
);
|
|
90
90
|
}
|
|
@@ -95,22 +95,22 @@ const resolveServiceConfig = (
|
|
|
95
95
|
// Override / cache lookups
|
|
96
96
|
// ---------------------------------------------------------------------------
|
|
97
97
|
|
|
98
|
-
const
|
|
99
|
-
overrides:
|
|
98
|
+
const hasOwnProvisionOverride = (
|
|
99
|
+
overrides: ProvisionOverrideMap | undefined,
|
|
100
100
|
id: string
|
|
101
|
-
): overrides is
|
|
101
|
+
): overrides is ProvisionOverrideMap =>
|
|
102
102
|
overrides !== undefined && Object.hasOwn(overrides, id);
|
|
103
103
|
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
const getCachedSingletonProvision = (
|
|
105
|
+
declaredProvision: AnyProvision,
|
|
106
|
+
provisionContext: ProvisionContext
|
|
107
107
|
): { readonly found: boolean; readonly value: unknown } => {
|
|
108
|
-
const scopedCache =
|
|
108
|
+
const scopedCache = singletonProvisions.get(declaredProvision);
|
|
109
109
|
if (scopedCache === undefined) {
|
|
110
110
|
return { found: false, value: undefined };
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
const key =
|
|
113
|
+
const key = toProvisionContextKey(provisionContext);
|
|
114
114
|
if (!scopedCache.has(key)) {
|
|
115
115
|
return { found: false, value: undefined };
|
|
116
116
|
}
|
|
@@ -121,14 +121,14 @@ const getCachedSingletonService = (
|
|
|
121
121
|
};
|
|
122
122
|
};
|
|
123
123
|
|
|
124
|
-
const
|
|
124
|
+
const getProvidedProvision = (
|
|
125
125
|
ctx: TrailContext,
|
|
126
|
-
overrides:
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
overrides: ProvisionOverrideMap | undefined,
|
|
127
|
+
declaredProvision: AnyProvision,
|
|
128
|
+
provisionContext: ProvisionContext
|
|
129
129
|
): Result<unknown, Error> | undefined => {
|
|
130
|
-
const { id } =
|
|
131
|
-
if (
|
|
130
|
+
const { id } = declaredProvision;
|
|
131
|
+
if (hasOwnProvisionOverride(overrides, id)) {
|
|
132
132
|
return Result.ok(overrides[id]);
|
|
133
133
|
}
|
|
134
134
|
|
|
@@ -136,7 +136,10 @@ const getProvidedService = (
|
|
|
136
136
|
return Result.ok(ctx.extensions?.[id]);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
const cached =
|
|
139
|
+
const cached = getCachedSingletonProvision(
|
|
140
|
+
declaredProvision,
|
|
141
|
+
provisionContext
|
|
142
|
+
);
|
|
140
143
|
if (cached.found) {
|
|
141
144
|
return Result.ok(cached.value);
|
|
142
145
|
}
|
|
@@ -146,200 +149,216 @@ const getProvidedService = (
|
|
|
146
149
|
|
|
147
150
|
const getOverrideOrExtension = (
|
|
148
151
|
ctx: TrailContext,
|
|
149
|
-
overrides:
|
|
150
|
-
|
|
152
|
+
overrides: ProvisionOverrideMap | undefined,
|
|
153
|
+
declaredProvision: AnyProvision
|
|
151
154
|
): Result<unknown, Error> | undefined =>
|
|
152
|
-
|
|
155
|
+
getProvidedProvision(
|
|
156
|
+
ctx,
|
|
157
|
+
overrides,
|
|
158
|
+
declaredProvision,
|
|
159
|
+
toProvisionContext(ctx)
|
|
160
|
+
);
|
|
153
161
|
|
|
154
162
|
type ConfigAwareResolution =
|
|
155
163
|
| Result<{ readonly kind: 'provided'; readonly value: unknown }, Error>
|
|
156
164
|
| Result<
|
|
157
|
-
{ readonly kind: 'context'; readonly
|
|
165
|
+
{ readonly kind: 'context'; readonly provisionContext: ProvisionContext },
|
|
158
166
|
Error
|
|
159
167
|
>;
|
|
160
168
|
|
|
161
|
-
const
|
|
169
|
+
const resolveConfigAwareProvidedProvision = (
|
|
162
170
|
ctx: TrailContext,
|
|
163
|
-
|
|
171
|
+
declaredProvision: AnyProvision,
|
|
164
172
|
configValues: ConfigValues | undefined
|
|
165
173
|
): ConfigAwareResolution => {
|
|
166
|
-
const configResult =
|
|
174
|
+
const configResult = resolveProvisionConfig(declaredProvision, configValues);
|
|
167
175
|
if (configResult.isErr()) {
|
|
168
176
|
return configResult;
|
|
169
177
|
}
|
|
170
178
|
|
|
171
|
-
const
|
|
172
|
-
const provided =
|
|
179
|
+
const provisionContext = toProvisionContext(ctx, configResult.value);
|
|
180
|
+
const provided = getProvidedProvision(
|
|
173
181
|
ctx,
|
|
174
182
|
undefined,
|
|
175
|
-
|
|
176
|
-
|
|
183
|
+
declaredProvision,
|
|
184
|
+
provisionContext
|
|
177
185
|
);
|
|
178
186
|
|
|
179
187
|
return provided
|
|
180
188
|
? Result.ok({ kind: 'provided', value: provided.unwrap() })
|
|
181
|
-
: Result.ok({ kind: 'context',
|
|
189
|
+
: Result.ok({ kind: 'context', provisionContext });
|
|
182
190
|
};
|
|
183
191
|
|
|
184
192
|
// ---------------------------------------------------------------------------
|
|
185
193
|
// Instance creation
|
|
186
194
|
// ---------------------------------------------------------------------------
|
|
187
195
|
|
|
188
|
-
const
|
|
196
|
+
const toInternalProvisionError = (
|
|
197
|
+
id: string,
|
|
198
|
+
error: unknown
|
|
199
|
+
): InternalError => {
|
|
189
200
|
const cause = error instanceof Error ? error : undefined;
|
|
190
201
|
const message = cause?.message ?? String(error);
|
|
191
|
-
return new InternalError(`
|
|
202
|
+
return new InternalError(`Provision "${id}" failed to resolve: ${message}`, {
|
|
192
203
|
...(cause ? { cause } : {}),
|
|
193
|
-
context: {
|
|
204
|
+
context: { provisionId: id },
|
|
194
205
|
});
|
|
195
206
|
};
|
|
196
207
|
|
|
197
|
-
const
|
|
198
|
-
|
|
208
|
+
const getSingletonProvisionCache = (
|
|
209
|
+
declaredProvision: AnyProvision
|
|
199
210
|
): Map<string, unknown> => {
|
|
200
|
-
const existing =
|
|
211
|
+
const existing = singletonProvisions.get(declaredProvision);
|
|
201
212
|
if (existing !== undefined) {
|
|
202
213
|
return existing;
|
|
203
214
|
}
|
|
204
215
|
|
|
205
216
|
const created = new Map<string, unknown>();
|
|
206
|
-
|
|
217
|
+
singletonProvisions.set(declaredProvision, created);
|
|
207
218
|
return created;
|
|
208
219
|
};
|
|
209
220
|
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
221
|
+
const doCreateProvisionInstance = async (
|
|
222
|
+
declaredProvision: AnyProvision,
|
|
223
|
+
provisionContext: ProvisionContext
|
|
213
224
|
): Promise<Result<unknown, Error>> => {
|
|
214
225
|
try {
|
|
215
|
-
const created = await
|
|
226
|
+
const created = await declaredProvision.create(provisionContext);
|
|
216
227
|
if (created.isErr()) {
|
|
217
228
|
return Result.err(created.error);
|
|
218
229
|
}
|
|
219
230
|
|
|
220
231
|
const instance = created.unwrap();
|
|
221
|
-
|
|
222
|
-
|
|
232
|
+
getSingletonProvisionCache(declaredProvision).set(
|
|
233
|
+
toProvisionContextKey(provisionContext),
|
|
223
234
|
instance
|
|
224
235
|
);
|
|
225
236
|
return Result.ok(instance);
|
|
226
237
|
} catch (error: unknown) {
|
|
227
|
-
return Result.err(
|
|
238
|
+
return Result.err(toInternalProvisionError(declaredProvision.id, error));
|
|
228
239
|
}
|
|
229
240
|
};
|
|
230
241
|
|
|
231
242
|
const trackPendingCreation = (
|
|
232
|
-
|
|
243
|
+
declaredProvision: AnyProvision,
|
|
233
244
|
key: string,
|
|
234
245
|
promise: Promise<Result<unknown, Error>>
|
|
235
246
|
): void => {
|
|
236
|
-
const pending = pendingCreations.get(
|
|
247
|
+
const pending = pendingCreations.get(declaredProvision);
|
|
237
248
|
if (pending) {
|
|
238
249
|
pending.set(key, promise);
|
|
239
250
|
} else {
|
|
240
|
-
pendingCreations.set(
|
|
251
|
+
pendingCreations.set(declaredProvision, new Map([[key, promise]]));
|
|
241
252
|
}
|
|
242
253
|
};
|
|
243
254
|
|
|
244
255
|
/**
|
|
245
|
-
* Deduplicates concurrent creation of the same
|
|
246
|
-
* If a creation is already in flight for this
|
|
256
|
+
* Deduplicates concurrent creation of the same provision singleton.
|
|
257
|
+
* If a creation is already in flight for this provision x context key,
|
|
247
258
|
* returns the existing promise instead of spawning a second factory call.
|
|
248
259
|
*/
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
260
|
+
const createProvisionInstance = async (
|
|
261
|
+
declaredProvision: AnyProvision,
|
|
262
|
+
provisionContext: ProvisionContext
|
|
252
263
|
): Promise<Result<unknown, Error>> => {
|
|
253
|
-
const key =
|
|
254
|
-
const inflight = pendingCreations.get(
|
|
264
|
+
const key = toProvisionContextKey(provisionContext);
|
|
265
|
+
const inflight = pendingCreations.get(declaredProvision)?.get(key);
|
|
255
266
|
if (inflight) {
|
|
256
267
|
return inflight;
|
|
257
268
|
}
|
|
258
269
|
|
|
259
|
-
const promise =
|
|
260
|
-
|
|
270
|
+
const promise = doCreateProvisionInstance(
|
|
271
|
+
declaredProvision,
|
|
272
|
+
provisionContext
|
|
273
|
+
);
|
|
274
|
+
trackPendingCreation(declaredProvision, key, promise);
|
|
261
275
|
|
|
262
276
|
try {
|
|
263
277
|
return await promise;
|
|
264
278
|
} finally {
|
|
265
|
-
pendingCreations.get(
|
|
279
|
+
pendingCreations.get(declaredProvision)?.delete(key);
|
|
266
280
|
}
|
|
267
281
|
};
|
|
268
282
|
|
|
269
|
-
/** Validate config and resolve a single declared
|
|
270
|
-
const
|
|
271
|
-
|
|
283
|
+
/** Validate config and resolve a single declared provision. */
|
|
284
|
+
const resolveDeclaredProvision = async (
|
|
285
|
+
declaredProvision: AnyProvision,
|
|
272
286
|
ctx: TrailContext,
|
|
273
|
-
overrides:
|
|
287
|
+
overrides: ProvisionOverrideMap | undefined,
|
|
274
288
|
configValues: ConfigValues | undefined
|
|
275
289
|
): Promise<Result<unknown, Error>> => {
|
|
276
290
|
// Check overrides/extensions first — skip config validation entirely when
|
|
277
|
-
// a
|
|
291
|
+
// a provision instance is already provided.
|
|
278
292
|
const overrideOrExtension = getOverrideOrExtension(
|
|
279
293
|
ctx,
|
|
280
294
|
overrides,
|
|
281
|
-
|
|
295
|
+
declaredProvision
|
|
282
296
|
);
|
|
283
297
|
if (overrideOrExtension !== undefined) {
|
|
284
298
|
return overrideOrExtension;
|
|
285
299
|
}
|
|
286
300
|
|
|
287
301
|
// Resolve config before consulting the singleton cache so config-aware
|
|
288
|
-
//
|
|
289
|
-
const
|
|
302
|
+
// provisions use the same canonical context for cache reads and writes.
|
|
303
|
+
const configAwareProvision = resolveConfigAwareProvidedProvision(
|
|
290
304
|
ctx,
|
|
291
|
-
|
|
305
|
+
declaredProvision,
|
|
292
306
|
configValues
|
|
293
307
|
);
|
|
294
|
-
if (
|
|
295
|
-
return
|
|
308
|
+
if (configAwareProvision.isErr()) {
|
|
309
|
+
return configAwareProvision;
|
|
296
310
|
}
|
|
297
311
|
|
|
298
312
|
// No provided instance — create via factory.
|
|
299
|
-
const resolved =
|
|
313
|
+
const resolved = configAwareProvision.unwrap();
|
|
300
314
|
if (resolved.kind === 'provided') {
|
|
301
315
|
return Result.ok(resolved.value);
|
|
302
316
|
}
|
|
303
317
|
|
|
304
|
-
return await
|
|
318
|
+
return await createProvisionInstance(
|
|
319
|
+
declaredProvision,
|
|
320
|
+
resolved.provisionContext
|
|
321
|
+
);
|
|
305
322
|
};
|
|
306
323
|
|
|
307
324
|
// ---------------------------------------------------------------------------
|
|
308
|
-
// Full trail
|
|
325
|
+
// Full trail provision resolution
|
|
309
326
|
// ---------------------------------------------------------------------------
|
|
310
327
|
|
|
311
|
-
const
|
|
328
|
+
const withResolvedProvisions = (
|
|
312
329
|
ctx: TrailContext,
|
|
313
|
-
|
|
330
|
+
resolvedProvisions: Record<string, unknown>
|
|
314
331
|
): TrailContext => {
|
|
315
|
-
const extensions = { ...ctx.extensions, ...
|
|
332
|
+
const extensions = { ...ctx.extensions, ...resolvedProvisions };
|
|
316
333
|
const resolvedCtx = { ...ctx, extensions } as MutableTrailContext;
|
|
317
|
-
|
|
334
|
+
const lookup = createProvisionLookup(() => resolvedCtx);
|
|
335
|
+
resolvedCtx.provision = lookup;
|
|
318
336
|
return resolvedCtx;
|
|
319
337
|
};
|
|
320
338
|
|
|
321
339
|
/**
|
|
322
|
-
* Resolve all declared
|
|
340
|
+
* Resolve all declared provisions for a trail.
|
|
323
341
|
*
|
|
324
|
-
* Validates per-
|
|
325
|
-
* new instances as needed. Returns an enriched context with all
|
|
342
|
+
* Validates per-provision config, checks overrides and caches, and creates
|
|
343
|
+
* new instances as needed. Returns an enriched context with all provision
|
|
326
344
|
* instances injected into extensions.
|
|
327
345
|
*/
|
|
328
|
-
export const
|
|
346
|
+
export const resolveProvisions = async (
|
|
329
347
|
trail: AnyTrail,
|
|
330
348
|
ctx: TrailContext,
|
|
331
|
-
overrides?:
|
|
349
|
+
overrides?: ProvisionOverrideMap,
|
|
332
350
|
configValues?: ConfigValues
|
|
333
351
|
): Promise<Result<TrailContext, Error>> => {
|
|
334
|
-
|
|
352
|
+
const { provisions } = trail;
|
|
353
|
+
if (provisions.length === 0) {
|
|
335
354
|
return Result.ok(ctx);
|
|
336
355
|
}
|
|
337
356
|
|
|
338
|
-
const
|
|
357
|
+
const resolvedProvisions: Record<string, unknown> = {};
|
|
339
358
|
|
|
340
|
-
for (const
|
|
341
|
-
const resolved = await
|
|
342
|
-
|
|
359
|
+
for (const declaredProvision of provisions) {
|
|
360
|
+
const resolved = await resolveDeclaredProvision(
|
|
361
|
+
declaredProvision,
|
|
343
362
|
ctx,
|
|
344
363
|
overrides,
|
|
345
364
|
configValues
|
|
@@ -347,8 +366,8 @@ export const resolveServices = async (
|
|
|
347
366
|
if (resolved.isErr()) {
|
|
348
367
|
return resolved;
|
|
349
368
|
}
|
|
350
|
-
|
|
369
|
+
resolvedProvisions[declaredProvision.id] = resolved.unwrap();
|
|
351
370
|
}
|
|
352
371
|
|
|
353
|
-
return Result.ok(
|
|
372
|
+
return Result.ok(withResolvedProvisions(ctx, resolvedProvisions));
|
|
354
373
|
};
|