better-effect 0.2.0 → 0.4.0
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/README.md +156 -220
- package/dist/adapters/iti.d.mts +1 -1
- package/dist/adapters/iti.d.mts.map +1 -1
- package/dist/adapters/iti.mjs +1 -2
- package/dist/adapters/iti.mjs.map +1 -1
- package/dist/{errors-CnvKqBpb.mjs → errors-DlHCwICc.mjs} +5 -1
- package/dist/{errors-CnvKqBpb.mjs.map → errors-DlHCwICc.mjs.map} +1 -1
- package/dist/index-BYQKfyeJ.d.mts +235 -0
- package/dist/index-BYQKfyeJ.d.mts.map +1 -0
- package/dist/index.d.mts +106 -63
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +473 -32
- package/dist/index.mjs.map +1 -1
- package/dist/testing.d.mts +1 -2
- package/dist/testing.d.mts.map +1 -1
- package/dist/testing.mjs +2 -18
- package/dist/testing.mjs.map +1 -1
- package/package.json +6 -9
- package/dist/backend-CNAls62W.d.mts +0 -39
- package/dist/backend-CNAls62W.d.mts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { a as LayerRegistrationError, i as LayerGeneratorYieldError, n as DuplicateServiceError, o as ServiceNotFoundError, r as LayerDisposeError, s as ServiceRuntimeNotConfiguredError, t as BuiltLayerDisposedError } from "./errors-
|
|
1
|
+
import { a as LayerRegistrationError, i as LayerGeneratorYieldError, n as DuplicateServiceError, o as ServiceNotFoundError, r as LayerDisposeError, s as ServiceRuntimeNotConfiguredError, t as BuiltLayerDisposedError } from "./errors-DlHCwICc.mjs";
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
3
|
import { Result, TaggedError } from "better-result";
|
|
4
4
|
//#region src/service/runtime.ts
|
|
5
|
-
const storage = new AsyncLocalStorage();
|
|
5
|
+
const storage$1 = new AsyncLocalStorage();
|
|
6
6
|
var ServiceRuntime = class ServiceRuntime {
|
|
7
7
|
static run(resolver, program) {
|
|
8
|
-
return storage.run(resolver, program);
|
|
8
|
+
return storage$1.run(resolver, program);
|
|
9
9
|
}
|
|
10
10
|
static current() {
|
|
11
|
-
const resolver = storage.getStore();
|
|
11
|
+
const resolver = storage$1.getStore();
|
|
12
12
|
if (!resolver) throw new ServiceRuntimeNotConfiguredError();
|
|
13
13
|
return resolver;
|
|
14
14
|
}
|
|
@@ -53,6 +53,12 @@ var Layer = class Layer {
|
|
|
53
53
|
static succeed(service, instance) {
|
|
54
54
|
return Layer.make(service, () => instance);
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Define a dependency-free provider with Runtime-root cleanup.
|
|
58
|
+
*
|
|
59
|
+
* The release callback intentionally keeps its compatibility-friendly
|
|
60
|
+
* one-argument shape. Use `scopedGen` when cleanup needs `ScopeOutcome`.
|
|
61
|
+
*/
|
|
56
62
|
static scoped(service, acquire, release) {
|
|
57
63
|
return new Layer([{
|
|
58
64
|
service,
|
|
@@ -60,6 +66,14 @@ var Layer = class Layer {
|
|
|
60
66
|
release: (instance) => release(instance)
|
|
61
67
|
}]);
|
|
62
68
|
}
|
|
69
|
+
/** Define a contextual provider with Runtime-root, outcome-aware cleanup. */
|
|
70
|
+
static scopedGen(service, factory, release) {
|
|
71
|
+
return new Layer([{
|
|
72
|
+
service,
|
|
73
|
+
acquire: () => runLayerGenerator(service, factory),
|
|
74
|
+
release: (instance, outcome) => release(instance, outcome)
|
|
75
|
+
}]);
|
|
76
|
+
}
|
|
63
77
|
static gen(service, factory) {
|
|
64
78
|
return Layer.make(service, () => runLayerGenerator(service, factory));
|
|
65
79
|
}
|
|
@@ -80,64 +94,448 @@ var Layer = class Layer {
|
|
|
80
94
|
}
|
|
81
95
|
};
|
|
82
96
|
//#endregion
|
|
97
|
+
//#region src/scope/errors.ts
|
|
98
|
+
var ScopeRuntimeNotConfiguredError = class extends Error {
|
|
99
|
+
constructor() {
|
|
100
|
+
super("No Scope is available in the current execution context");
|
|
101
|
+
this.name = "ScopeRuntimeNotConfiguredError";
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
var ScopeClosedError = class extends Error {
|
|
105
|
+
constructor() {
|
|
106
|
+
super("Cannot add resources or finalizers to a closed Scope");
|
|
107
|
+
this.name = "ScopeClosedError";
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
var ScopeCloseError = class extends Error {
|
|
111
|
+
causes;
|
|
112
|
+
constructor(causes) {
|
|
113
|
+
super(`Failed to close Scope (${causes.length} finalizer${causes.length === 1 ? "" : "s"} failed)`);
|
|
114
|
+
this.causes = causes;
|
|
115
|
+
this.name = "ScopeCloseError";
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var ResourceNotDisposableError = class extends Error {
|
|
119
|
+
constructor() {
|
|
120
|
+
super("Resource does not implement Symbol.dispose or Symbol.asyncDispose");
|
|
121
|
+
this.name = "ResourceNotDisposableError";
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/scope/disposable.ts
|
|
126
|
+
const SCOPE_SUCCESS$2 = { status: "success" };
|
|
127
|
+
const getDisposeFinalizer = (resource) => {
|
|
128
|
+
const candidate = Object(resource);
|
|
129
|
+
const asyncDispose = candidate[Symbol.asyncDispose];
|
|
130
|
+
if (typeof asyncDispose === "function") return () => asyncDispose.call(resource);
|
|
131
|
+
const dispose = candidate[Symbol.dispose];
|
|
132
|
+
if (typeof dispose === "function") return () => dispose.call(resource);
|
|
133
|
+
};
|
|
134
|
+
const disposeResource = (resource) => {
|
|
135
|
+
return getDisposeFinalizer(resource)?.(SCOPE_SUCCESS$2);
|
|
136
|
+
};
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/scope/runtime.ts
|
|
139
|
+
const storage = new AsyncLocalStorage();
|
|
140
|
+
var ScopeRuntime = class {
|
|
141
|
+
static run(scope, program) {
|
|
142
|
+
return storage.run(scope, program);
|
|
143
|
+
}
|
|
144
|
+
static current() {
|
|
145
|
+
const scope = storage.getStore();
|
|
146
|
+
if (!scope) throw new ScopeRuntimeNotConfiguredError();
|
|
147
|
+
return scope;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/scope/internal.ts
|
|
152
|
+
const notifyCleanupFailure = async (observer, diagnostic) => {
|
|
153
|
+
if (!observer) return;
|
|
154
|
+
try {
|
|
155
|
+
await observer(diagnostic);
|
|
156
|
+
} catch {}
|
|
157
|
+
};
|
|
158
|
+
const runScoped = async (scope, program, options) => {
|
|
159
|
+
let value;
|
|
160
|
+
let programFailed = false;
|
|
161
|
+
let programFailure;
|
|
162
|
+
try {
|
|
163
|
+
value = await ScopeRuntime.run(scope, program);
|
|
164
|
+
} catch (cause) {
|
|
165
|
+
programFailed = true;
|
|
166
|
+
programFailure = cause;
|
|
167
|
+
}
|
|
168
|
+
const outcome = programFailed ? {
|
|
169
|
+
status: "failure",
|
|
170
|
+
cause: programFailure
|
|
171
|
+
} : options.classify(value);
|
|
172
|
+
let cleanupFailed = false;
|
|
173
|
+
let cleanupFailure;
|
|
174
|
+
try {
|
|
175
|
+
await scope.close(outcome);
|
|
176
|
+
} catch (cause) {
|
|
177
|
+
cleanupFailed = true;
|
|
178
|
+
cleanupFailure = cause;
|
|
179
|
+
}
|
|
180
|
+
if (cleanupFailed) {
|
|
181
|
+
const error = cleanupFailure instanceof ScopeCloseError ? cleanupFailure : new ScopeCloseError([cleanupFailure]);
|
|
182
|
+
await notifyCleanupFailure(options.onCleanupFailure, {
|
|
183
|
+
outcome,
|
|
184
|
+
error
|
|
185
|
+
});
|
|
186
|
+
cleanupFailure = error;
|
|
187
|
+
}
|
|
188
|
+
if (programFailed) throw programFailure;
|
|
189
|
+
if (outcome.status === "failure") return value;
|
|
190
|
+
if (cleanupFailed) throw cleanupFailure;
|
|
191
|
+
return value;
|
|
192
|
+
};
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region src/scope/scope.ts
|
|
195
|
+
const SCOPE_SUCCESS$1 = Object.freeze({ status: "success" });
|
|
196
|
+
var ScopeImpl = class ScopeImpl {
|
|
197
|
+
parent;
|
|
198
|
+
children = /* @__PURE__ */ new Set();
|
|
199
|
+
finalizers = [];
|
|
200
|
+
closePromise;
|
|
201
|
+
closeOutcome;
|
|
202
|
+
constructor(parent) {
|
|
203
|
+
this.parent = parent;
|
|
204
|
+
}
|
|
205
|
+
fork() {
|
|
206
|
+
this.assertOpen();
|
|
207
|
+
const child = new ScopeImpl(this);
|
|
208
|
+
this.children.add(child);
|
|
209
|
+
return child;
|
|
210
|
+
}
|
|
211
|
+
addFinalizer(finalizer) {
|
|
212
|
+
this.assertOpen();
|
|
213
|
+
this.finalizers.push(finalizer);
|
|
214
|
+
}
|
|
215
|
+
async acquire(acquire, release) {
|
|
216
|
+
this.assertOpen();
|
|
217
|
+
const resource = await acquire();
|
|
218
|
+
try {
|
|
219
|
+
this.addFinalizer((outcome) => release(resource, outcome));
|
|
220
|
+
return resource;
|
|
221
|
+
} catch (scopeFailure) {
|
|
222
|
+
try {
|
|
223
|
+
await release(resource, this.closeOutcome ?? SCOPE_SUCCESS$1);
|
|
224
|
+
} catch (releaseFailure) {
|
|
225
|
+
throw new AggregateError([scopeFailure, releaseFailure], "Scope closed while acquiring a resource and immediate cleanup also failed");
|
|
226
|
+
}
|
|
227
|
+
throw scopeFailure;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async add(resource) {
|
|
231
|
+
const finalizer = getDisposeFinalizer(resource);
|
|
232
|
+
if (!finalizer) throw new ResourceNotDisposableError();
|
|
233
|
+
try {
|
|
234
|
+
this.addFinalizer(finalizer);
|
|
235
|
+
return resource;
|
|
236
|
+
} catch (scopeFailure) {
|
|
237
|
+
try {
|
|
238
|
+
await finalizer(this.closeOutcome ?? SCOPE_SUCCESS$1);
|
|
239
|
+
} catch (releaseFailure) {
|
|
240
|
+
throw new AggregateError([scopeFailure, releaseFailure], "Scope closed while adding a disposable resource and cleanup also failed");
|
|
241
|
+
}
|
|
242
|
+
throw scopeFailure;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
close(outcome = SCOPE_SUCCESS$1) {
|
|
246
|
+
if (this.closePromise) return this.closePromise;
|
|
247
|
+
this.closeOutcome = outcome;
|
|
248
|
+
this.closePromise = ScopeRuntime.run(this, () => this.closeInternal(outcome));
|
|
249
|
+
return this.closePromise;
|
|
250
|
+
}
|
|
251
|
+
async closeInternal(outcome) {
|
|
252
|
+
const failures = [];
|
|
253
|
+
const children = [...this.children];
|
|
254
|
+
this.children.clear();
|
|
255
|
+
for (let index = children.length - 1; index >= 0; index--) {
|
|
256
|
+
const child = children[index];
|
|
257
|
+
if (!child) continue;
|
|
258
|
+
try {
|
|
259
|
+
await child.close(outcome);
|
|
260
|
+
} catch (cause) {
|
|
261
|
+
if (cause instanceof ScopeCloseError) failures.push(...cause.causes);
|
|
262
|
+
else failures.push(cause);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
for (let index = this.finalizers.length - 1; index >= 0; index--) {
|
|
266
|
+
const finalizer = this.finalizers[index];
|
|
267
|
+
if (!finalizer) continue;
|
|
268
|
+
try {
|
|
269
|
+
await finalizer(outcome);
|
|
270
|
+
} catch (cause) {
|
|
271
|
+
failures.push(cause);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
this.finalizers.length = 0;
|
|
275
|
+
this.detach();
|
|
276
|
+
if (failures.length > 0) throw new ScopeCloseError(failures);
|
|
277
|
+
}
|
|
278
|
+
detach() {
|
|
279
|
+
const parent = this.parent;
|
|
280
|
+
if (!parent) return;
|
|
281
|
+
parent.children.delete(this);
|
|
282
|
+
this.parent = void 0;
|
|
283
|
+
}
|
|
284
|
+
assertOpen() {
|
|
285
|
+
if (this.closePromise) throw new ScopeClosedError();
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
const Scope = {
|
|
289
|
+
make() {
|
|
290
|
+
return new ScopeImpl();
|
|
291
|
+
},
|
|
292
|
+
current() {
|
|
293
|
+
return ScopeRuntime.current();
|
|
294
|
+
},
|
|
295
|
+
provide(scope, program) {
|
|
296
|
+
return ScopeRuntime.run(scope, program);
|
|
297
|
+
},
|
|
298
|
+
*[Symbol.iterator]() {
|
|
299
|
+
return ScopeRuntime.current();
|
|
300
|
+
},
|
|
301
|
+
/**
|
|
302
|
+
* Run a program in a newly owned Scope.
|
|
303
|
+
*
|
|
304
|
+
* Scope is independent from `better-result`, so returned values—including
|
|
305
|
+
* `Result.err`—close this Scope with a successful outcome. Result-aware
|
|
306
|
+
* outcome classification belongs to `Runtime.run`.
|
|
307
|
+
*/
|
|
308
|
+
run(program) {
|
|
309
|
+
const scope = new ScopeImpl();
|
|
310
|
+
return runScoped(scope, () => program(scope), { classify: () => SCOPE_SUCCESS$1 });
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region src/runtime/outcome.ts
|
|
315
|
+
const isResultLike = (value) => typeof value === "object" && value !== null && "status" in value && (value.status === "ok" || value.status === "error");
|
|
316
|
+
const classifyRuntimeOutcome = (value) => {
|
|
317
|
+
if (isResultLike(value) && Result.isError(value)) return {
|
|
318
|
+
status: "failure",
|
|
319
|
+
cause: value.error
|
|
320
|
+
};
|
|
321
|
+
return { status: "success" };
|
|
322
|
+
};
|
|
323
|
+
//#endregion
|
|
83
324
|
//#region src/layer/runtime.ts
|
|
325
|
+
const SCOPE_SUCCESS = Object.freeze({ status: "success" });
|
|
84
326
|
const normalizeDisposeCauses = (cause) => {
|
|
85
327
|
if (cause instanceof AggregateError) return [...cause.errors];
|
|
86
328
|
return [cause];
|
|
87
329
|
};
|
|
330
|
+
const notifyShutdownFailure = async (observer, diagnostic) => {
|
|
331
|
+
if (!observer) return;
|
|
332
|
+
try {
|
|
333
|
+
await observer(diagnostic);
|
|
334
|
+
} catch {}
|
|
335
|
+
};
|
|
336
|
+
const bindProviderToScope = (provider, rootScope) => ({
|
|
337
|
+
service: provider.service,
|
|
338
|
+
acquire: () => ScopeRuntime.run(rootScope, async () => {
|
|
339
|
+
if (!provider.release) return await provider.acquire();
|
|
340
|
+
return await rootScope.acquire(() => provider.acquire(), (resource, outcome) => provider.release(resource, outcome));
|
|
341
|
+
})
|
|
342
|
+
});
|
|
88
343
|
var BuiltLayerImpl = class {
|
|
89
344
|
backend;
|
|
345
|
+
rootScope;
|
|
346
|
+
onCleanupFailure;
|
|
90
347
|
disposePromise;
|
|
91
|
-
|
|
92
|
-
|
|
348
|
+
executions = /* @__PURE__ */ new Set();
|
|
349
|
+
state = "active";
|
|
350
|
+
constructor(backend, rootScope, onCleanupFailure) {
|
|
93
351
|
this.backend = backend;
|
|
352
|
+
this.rootScope = rootScope;
|
|
353
|
+
this.onCleanupFailure = onCleanupFailure;
|
|
94
354
|
}
|
|
95
355
|
run(program) {
|
|
96
|
-
|
|
97
|
-
|
|
356
|
+
this.assertActive();
|
|
357
|
+
const executionScope = this.rootScope.fork();
|
|
358
|
+
let resolveExecution;
|
|
359
|
+
let rejectExecution;
|
|
360
|
+
const execution = new Promise((resolve, reject) => {
|
|
361
|
+
resolveExecution = resolve;
|
|
362
|
+
rejectExecution = reject;
|
|
363
|
+
});
|
|
364
|
+
this.executions.add(execution);
|
|
365
|
+
execution.then(() => {
|
|
366
|
+
this.executions.delete(execution);
|
|
367
|
+
}, () => {
|
|
368
|
+
this.executions.delete(execution);
|
|
369
|
+
});
|
|
370
|
+
try {
|
|
371
|
+
this.runExecution(executionScope, program).then((value) => {
|
|
372
|
+
resolveExecution(value);
|
|
373
|
+
}, (cause) => {
|
|
374
|
+
rejectExecution(cause);
|
|
375
|
+
});
|
|
376
|
+
} catch (cause) {
|
|
377
|
+
rejectExecution(cause);
|
|
378
|
+
}
|
|
379
|
+
return execution;
|
|
98
380
|
}
|
|
99
|
-
|
|
381
|
+
runExecution(executionScope, program) {
|
|
382
|
+
const options = this.onCleanupFailure ? {
|
|
383
|
+
classify: classifyRuntimeOutcome,
|
|
384
|
+
onCleanupFailure: this.onCleanupFailure
|
|
385
|
+
} : { classify: classifyRuntimeOutcome };
|
|
386
|
+
return runScoped(executionScope, () => ServiceRuntime.run(this.backend, program), options);
|
|
387
|
+
}
|
|
388
|
+
dispose(outcome = SCOPE_SUCCESS) {
|
|
100
389
|
if (this.disposePromise) return this.disposePromise;
|
|
101
|
-
this.
|
|
390
|
+
this.state = "disposing";
|
|
391
|
+
const executions = [...this.executions];
|
|
392
|
+
this.disposePromise = this.performDispose(executions, outcome);
|
|
102
393
|
return this.disposePromise;
|
|
103
394
|
}
|
|
104
|
-
async performDispose() {
|
|
395
|
+
async performDispose(executions, outcome) {
|
|
396
|
+
const failures = [];
|
|
397
|
+
await Promise.allSettled(executions);
|
|
398
|
+
try {
|
|
399
|
+
await ServiceRuntime.run(this.backend, () => this.rootScope.close(outcome));
|
|
400
|
+
} catch (cause) {
|
|
401
|
+
failures.push(cause);
|
|
402
|
+
}
|
|
105
403
|
try {
|
|
106
404
|
await this.backend.disposeAll();
|
|
107
405
|
} catch (cause) {
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
406
|
+
failures.push(cause);
|
|
407
|
+
}
|
|
408
|
+
this.state = "disposed";
|
|
409
|
+
if (failures.length > 0) {
|
|
410
|
+
const error = new LayerDisposeError(failures.flatMap(normalizeDisposeCauses));
|
|
411
|
+
await notifyShutdownFailure(this.onCleanupFailure, {
|
|
412
|
+
outcome,
|
|
413
|
+
error
|
|
414
|
+
});
|
|
415
|
+
throw error;
|
|
111
416
|
}
|
|
112
417
|
}
|
|
418
|
+
assertActive() {
|
|
419
|
+
if (this.state !== "active") throw new BuiltLayerDisposedError();
|
|
420
|
+
}
|
|
113
421
|
};
|
|
114
|
-
|
|
422
|
+
/**
|
|
423
|
+
* Build a low-level Layer handle.
|
|
424
|
+
*
|
|
425
|
+
* @deprecated Prefer `Runtime.make()` for application code.
|
|
426
|
+
*/
|
|
427
|
+
const buildLayer = async (layer, backend, options = {}) => {
|
|
428
|
+
const rootScope = Scope.make();
|
|
115
429
|
let current;
|
|
116
430
|
try {
|
|
117
431
|
for (const provider of layer.providers) {
|
|
118
432
|
current = provider;
|
|
119
|
-
await backend.register(provider);
|
|
433
|
+
await backend.register(bindProviderToScope(provider, rootScope));
|
|
120
434
|
}
|
|
121
435
|
} catch (registrationCause) {
|
|
122
|
-
|
|
436
|
+
const outcome = {
|
|
437
|
+
status: "failure",
|
|
438
|
+
cause: registrationCause
|
|
439
|
+
};
|
|
440
|
+
const cleanupCauses = [];
|
|
441
|
+
try {
|
|
442
|
+
await ServiceRuntime.run(backend, () => rootScope.close(outcome));
|
|
443
|
+
} catch (cause) {
|
|
444
|
+
cleanupCauses.push(cause);
|
|
445
|
+
}
|
|
123
446
|
try {
|
|
124
447
|
await backend.disposeAll();
|
|
125
448
|
} catch (cause) {
|
|
126
|
-
|
|
449
|
+
cleanupCauses.push(cause);
|
|
450
|
+
}
|
|
451
|
+
if (cleanupCauses.length > 0) {
|
|
452
|
+
const shutdownError = new LayerDisposeError(cleanupCauses.flatMap(normalizeDisposeCauses));
|
|
453
|
+
await notifyShutdownFailure(options.onCleanupFailure, {
|
|
454
|
+
outcome,
|
|
455
|
+
error: shutdownError
|
|
456
|
+
});
|
|
127
457
|
}
|
|
458
|
+
let cleanupCause;
|
|
459
|
+
if (cleanupCauses.length === 1) cleanupCause = cleanupCauses[0];
|
|
460
|
+
else if (cleanupCauses.length > 1) cleanupCause = new LayerDisposeError(cleanupCauses.flatMap(normalizeDisposeCauses));
|
|
128
461
|
throw new LayerRegistrationError(current?.service, registrationCause, cleanupCause);
|
|
129
462
|
}
|
|
130
|
-
return new BuiltLayerImpl(backend);
|
|
463
|
+
return new BuiltLayerImpl(backend, rootScope, options.onCleanupFailure);
|
|
131
464
|
};
|
|
132
465
|
//#endregion
|
|
466
|
+
//#region src/effect/combinators.ts
|
|
467
|
+
const isPromiseLike = (value) => {
|
|
468
|
+
if (typeof value !== "object" && typeof value !== "function" || value === null) return false;
|
|
469
|
+
return "then" in value && typeof value.then === "function";
|
|
470
|
+
};
|
|
471
|
+
const mapResult = (result, fn) => Result.map(result, fn);
|
|
472
|
+
const mapErrorResult = (result, fn) => Result.mapError(result, fn);
|
|
473
|
+
const andThenResult = (result, next) => {
|
|
474
|
+
const chained = Result.andThen(result, next);
|
|
475
|
+
if (!isPromiseLike(chained)) return chained;
|
|
476
|
+
return Result.andThenAsync(result, () => Promise.resolve(chained));
|
|
477
|
+
};
|
|
478
|
+
function map(first, second) {
|
|
479
|
+
if (typeof first === "function" && second === void 0) return (effect) => map(effect, first);
|
|
480
|
+
const fn = second;
|
|
481
|
+
if (isPromiseLike(first)) return Promise.resolve(first).then((result) => mapResult(result, fn));
|
|
482
|
+
return mapResult(first, fn);
|
|
483
|
+
}
|
|
484
|
+
function mapError(first, second) {
|
|
485
|
+
if (typeof first === "function" && second === void 0) return (effect) => mapError(effect, first);
|
|
486
|
+
const fn = second;
|
|
487
|
+
if (isPromiseLike(first)) return Promise.resolve(first).then((result) => mapErrorResult(result, fn));
|
|
488
|
+
return mapErrorResult(first, fn);
|
|
489
|
+
}
|
|
490
|
+
function andThen(first, second) {
|
|
491
|
+
if (typeof first === "function" && second === void 0) return (effect) => andThen(effect, first);
|
|
492
|
+
const next = second;
|
|
493
|
+
if (isPromiseLike(first)) return Promise.resolve(first).then((result) => andThenResult(result, next));
|
|
494
|
+
return andThenResult(first, next);
|
|
495
|
+
}
|
|
496
|
+
//#endregion
|
|
497
|
+
//#region src/effect/effect.ts
|
|
498
|
+
function gen(body) {
|
|
499
|
+
return Result.gen(body);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Acquire a resource in the current Scope and register its release callback.
|
|
503
|
+
*
|
|
504
|
+
* Acquisition failures are represented in the Effect Result error channel;
|
|
505
|
+
* release failures remain owned by Scope cleanup.
|
|
506
|
+
*/
|
|
507
|
+
function acquireRelease(acquire, release) {
|
|
508
|
+
const scope = Scope.current();
|
|
509
|
+
return Result.await(Result.tryPromise(() => scope.acquire(acquire, release)));
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Register an already-acquired disposable resource in the current Scope.
|
|
513
|
+
*
|
|
514
|
+
* Registration failures are represented in the Effect Result error channel;
|
|
515
|
+
* disposal failures remain owned by Scope cleanup.
|
|
516
|
+
*/
|
|
517
|
+
function add(resource) {
|
|
518
|
+
const scope = Scope.current();
|
|
519
|
+
return Result.await(Result.tryPromise(() => scope.add(resource)));
|
|
520
|
+
}
|
|
521
|
+
const Effect = {
|
|
522
|
+
gen,
|
|
523
|
+
acquireRelease,
|
|
524
|
+
add,
|
|
525
|
+
map,
|
|
526
|
+
mapError,
|
|
527
|
+
andThen
|
|
528
|
+
};
|
|
529
|
+
//#endregion
|
|
530
|
+
//#region src/function/pipe.ts
|
|
531
|
+
function pipe(value, ...operations) {
|
|
532
|
+
return operations.reduce((current, operation) => operation(current), value);
|
|
533
|
+
}
|
|
534
|
+
//#endregion
|
|
133
535
|
//#region src/resource/errors.ts
|
|
134
536
|
var ResourceReleaseFailure = class extends TaggedError("ResourceReleaseFailure") {};
|
|
135
537
|
//#endregion
|
|
136
538
|
//#region src/resource/internal.ts
|
|
137
|
-
const disposeResource = (resource) => {
|
|
138
|
-
const candidate = Object(resource);
|
|
139
|
-
return (candidate[Symbol.asyncDispose] ?? candidate[Symbol.dispose])?.call(candidate);
|
|
140
|
-
};
|
|
141
539
|
const toReleaseFailure = (resource, cause) => new ResourceReleaseFailure({
|
|
142
540
|
resource,
|
|
143
541
|
cause,
|
|
@@ -177,8 +575,13 @@ const combineUseAndRelease = async (used, released, onReleaseFailure) => {
|
|
|
177
575
|
//#region src/resource/resource.ts
|
|
178
576
|
const acquireUseRelease = ({ name, acquire, use, release = disposeResource, onReleaseFailure }) => Result.gen(async function* () {
|
|
179
577
|
const resource = yield* Result.await(runResult(acquire));
|
|
578
|
+
const scope = Scope.make();
|
|
579
|
+
let released = Result.ok();
|
|
580
|
+
scope.addFinalizer(async () => {
|
|
581
|
+
released = await runRelease(name, resource, release);
|
|
582
|
+
});
|
|
180
583
|
const used = await runResult(() => use(resource));
|
|
181
|
-
|
|
584
|
+
await scope.close();
|
|
182
585
|
return await combineUseAndRelease(used, released, onReleaseFailure);
|
|
183
586
|
});
|
|
184
587
|
const Resource = { acquireUseRelease };
|
|
@@ -189,26 +592,64 @@ var Runtime = class Runtime {
|
|
|
189
592
|
constructor(built) {
|
|
190
593
|
this.built = built;
|
|
191
594
|
}
|
|
192
|
-
|
|
193
|
-
|
|
595
|
+
/** Create a long-lived Runtime that owns its Layer resources. */
|
|
596
|
+
static async make(layer, backend, options = {}) {
|
|
597
|
+
const built = await buildLayer(layer, backend, options);
|
|
194
598
|
return new Runtime(built);
|
|
195
599
|
}
|
|
196
|
-
|
|
197
|
-
|
|
600
|
+
/** Run one program and dispose its Layer resources before resolving. */
|
|
601
|
+
static async run(layer, backend, program, options = {}) {
|
|
602
|
+
const runtime = await Runtime.make(layer, backend, options);
|
|
603
|
+
let value;
|
|
604
|
+
let executionFailed = false;
|
|
605
|
+
let executionFailure;
|
|
606
|
+
let programOutcome;
|
|
198
607
|
try {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
608
|
+
value = await runtime.runUnchecked(async () => {
|
|
609
|
+
try {
|
|
610
|
+
const programValue = await program();
|
|
611
|
+
programOutcome = classifyRuntimeOutcome(programValue);
|
|
612
|
+
return programValue;
|
|
613
|
+
} catch (cause) {
|
|
614
|
+
programOutcome = {
|
|
615
|
+
status: "failure",
|
|
616
|
+
cause
|
|
617
|
+
};
|
|
618
|
+
throw cause;
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
} catch (cause) {
|
|
622
|
+
executionFailed = true;
|
|
623
|
+
executionFailure = cause;
|
|
624
|
+
}
|
|
625
|
+
const outcome = programOutcome ?? {
|
|
626
|
+
status: "failure",
|
|
627
|
+
cause: executionFailure
|
|
628
|
+
};
|
|
629
|
+
try {
|
|
630
|
+
await runtime.disposeWithOutcome(outcome);
|
|
631
|
+
} catch (shutdownFailure) {
|
|
632
|
+
if (!executionFailed && outcome.status === "success") throw shutdownFailure;
|
|
202
633
|
}
|
|
634
|
+
if (executionFailed) throw executionFailure;
|
|
635
|
+
return value;
|
|
203
636
|
}
|
|
637
|
+
/** Run one execution in this Runtime's child Scope. */
|
|
204
638
|
run(program) {
|
|
205
639
|
return this.built.run(program);
|
|
206
640
|
}
|
|
641
|
+
runUnchecked(program) {
|
|
642
|
+
return this.built.run(program);
|
|
643
|
+
}
|
|
644
|
+
/** Stop new executions and release the Runtime's Layer resources. */
|
|
207
645
|
dispose() {
|
|
208
646
|
return this.built.dispose();
|
|
209
647
|
}
|
|
648
|
+
disposeWithOutcome(outcome) {
|
|
649
|
+
return this.built.dispose(outcome);
|
|
650
|
+
}
|
|
210
651
|
};
|
|
211
652
|
//#endregion
|
|
212
|
-
export { BuiltLayerDisposedError, DuplicateServiceError, Layer, LayerDisposeError, LayerGeneratorYieldError, LayerRegistrationError, Resource, ResourceReleaseFailure, Runtime, Service, ServiceNotFoundError, ServiceRuntime, ServiceRuntimeNotConfiguredError,
|
|
653
|
+
export { BuiltLayerDisposedError, DuplicateServiceError, Effect, Layer, LayerDisposeError, LayerGeneratorYieldError, LayerRegistrationError, Resource, ResourceNotDisposableError, ResourceReleaseFailure, Runtime, Scope, ScopeCloseError, ScopeClosedError, ScopeRuntimeNotConfiguredError, Service, ServiceNotFoundError, ServiceRuntime, ServiceRuntimeNotConfiguredError, pipe };
|
|
213
654
|
|
|
214
655
|
//# sourceMappingURL=index.mjs.map
|