better-effect 0.1.0 → 0.3.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 +301 -19
- package/dist/adapters/iti.d.mts +15 -2
- package/dist/adapters/iti.d.mts.map +1 -0
- package/dist/adapters/iti.mjs +7 -2
- package/dist/adapters/iti.mjs.map +1 -1
- package/dist/errors-CnvKqBpb.mjs +63 -0
- package/dist/errors-CnvKqBpb.mjs.map +1 -0
- package/dist/index-Bg6ofRE1.d.mts +232 -0
- package/dist/index-Bg6ofRE1.d.mts.map +1 -0
- package/dist/index.d.mts +68 -47
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +502 -48
- package/dist/index.mjs.map +1 -1
- package/dist/testing.d.mts +13 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +36 -0
- package/dist/testing.mjs.map +1 -0
- package/package.json +2 -1
- package/dist/iti-xXAb9-Qs.d.mts +0 -45
- package/dist/iti-xXAb9-Qs.d.mts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import {
|
|
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-CnvKqBpb.mjs";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
3
|
import { Result, TaggedError } from "better-result";
|
|
3
4
|
//#region src/service/runtime.ts
|
|
4
|
-
|
|
5
|
-
var ServiceRuntime = class {
|
|
6
|
-
static
|
|
7
|
-
|
|
5
|
+
const storage$1 = new AsyncLocalStorage();
|
|
6
|
+
var ServiceRuntime = class ServiceRuntime {
|
|
7
|
+
static run(resolver, program) {
|
|
8
|
+
return storage$1.run(resolver, program);
|
|
8
9
|
}
|
|
9
|
-
static
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
static current() {
|
|
11
|
+
const resolver = storage$1.getStore();
|
|
12
|
+
if (!resolver) throw new ServiceRuntimeNotConfiguredError();
|
|
13
|
+
return resolver;
|
|
12
14
|
}
|
|
13
|
-
static
|
|
14
|
-
|
|
15
|
+
static async resolve(token) {
|
|
16
|
+
return await ServiceRuntime.current().resolve(token);
|
|
15
17
|
}
|
|
16
18
|
};
|
|
17
19
|
//#endregion
|
|
@@ -24,11 +26,23 @@ function Service() {
|
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
//#endregion
|
|
29
|
+
//#region src/layer/internal.ts
|
|
30
|
+
const runLayerGenerator = async (service, factory) => {
|
|
31
|
+
const iterator = factory();
|
|
32
|
+
const state = await iterator.next();
|
|
33
|
+
if (!state.done) try {
|
|
34
|
+
await iterator.return(void 0);
|
|
35
|
+
} finally {
|
|
36
|
+
throw new LayerGeneratorYieldError(service);
|
|
37
|
+
}
|
|
38
|
+
return state.value;
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
27
41
|
//#region src/layer/layer.ts
|
|
28
42
|
var Layer = class Layer {
|
|
29
43
|
providers;
|
|
30
44
|
constructor(providers) {
|
|
31
|
-
this.providers = providers;
|
|
45
|
+
this.providers = Object.freeze([...providers]);
|
|
32
46
|
}
|
|
33
47
|
static make(service, acquire) {
|
|
34
48
|
return new Layer([{
|
|
@@ -46,11 +60,21 @@ var Layer = class Layer {
|
|
|
46
60
|
release: (instance) => release(instance)
|
|
47
61
|
}]);
|
|
48
62
|
}
|
|
63
|
+
static scopedGen(service, factory, release) {
|
|
64
|
+
return new Layer([{
|
|
65
|
+
service,
|
|
66
|
+
acquire: () => runLayerGenerator(service, factory),
|
|
67
|
+
release: (instance, outcome) => release(instance, outcome)
|
|
68
|
+
}]);
|
|
69
|
+
}
|
|
70
|
+
static gen(service, factory) {
|
|
71
|
+
return Layer.make(service, () => runLayerGenerator(service, factory));
|
|
72
|
+
}
|
|
49
73
|
static merge(...layers) {
|
|
50
74
|
const providers = /* @__PURE__ */ new Map();
|
|
51
75
|
for (const layer of layers) for (const provider of layer.providers) {
|
|
52
76
|
const service = provider.service;
|
|
53
|
-
if (providers.has(service)) throw new
|
|
77
|
+
if (providers.has(service)) throw new DuplicateServiceError(service);
|
|
54
78
|
providers.set(service, provider);
|
|
55
79
|
}
|
|
56
80
|
return new Layer([...providers.values()]);
|
|
@@ -63,39 +87,402 @@ var Layer = class Layer {
|
|
|
63
87
|
}
|
|
64
88
|
};
|
|
65
89
|
//#endregion
|
|
90
|
+
//#region src/scope/errors.ts
|
|
91
|
+
var ScopeRuntimeNotConfiguredError = class extends Error {
|
|
92
|
+
constructor() {
|
|
93
|
+
super("No Scope is available in the current execution context");
|
|
94
|
+
this.name = "ScopeRuntimeNotConfiguredError";
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var ScopeClosedError = class extends Error {
|
|
98
|
+
constructor() {
|
|
99
|
+
super("Cannot add resources or finalizers to a closed Scope");
|
|
100
|
+
this.name = "ScopeClosedError";
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var ScopeCloseError = class extends Error {
|
|
104
|
+
causes;
|
|
105
|
+
constructor(causes) {
|
|
106
|
+
super(`Failed to close Scope (${causes.length} finalizer${causes.length === 1 ? "" : "s"} failed)`);
|
|
107
|
+
this.causes = causes;
|
|
108
|
+
this.name = "ScopeCloseError";
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
var ResourceNotDisposableError = class extends Error {
|
|
112
|
+
constructor() {
|
|
113
|
+
super("Resource does not implement Symbol.dispose or Symbol.asyncDispose");
|
|
114
|
+
this.name = "ResourceNotDisposableError";
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/scope/disposable.ts
|
|
119
|
+
const SCOPE_SUCCESS$2 = { status: "success" };
|
|
120
|
+
const getDisposeFinalizer = (resource) => {
|
|
121
|
+
const candidate = Object(resource);
|
|
122
|
+
const asyncDispose = candidate[Symbol.asyncDispose];
|
|
123
|
+
if (typeof asyncDispose === "function") return () => asyncDispose.call(resource);
|
|
124
|
+
const dispose = candidate[Symbol.dispose];
|
|
125
|
+
if (typeof dispose === "function") return () => dispose.call(resource);
|
|
126
|
+
};
|
|
127
|
+
const disposeResource = (resource) => {
|
|
128
|
+
return getDisposeFinalizer(resource)?.(SCOPE_SUCCESS$2);
|
|
129
|
+
};
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/scope/runtime.ts
|
|
132
|
+
const storage = new AsyncLocalStorage();
|
|
133
|
+
var ScopeRuntime = class {
|
|
134
|
+
static run(scope, program) {
|
|
135
|
+
return storage.run(scope, program);
|
|
136
|
+
}
|
|
137
|
+
static current() {
|
|
138
|
+
const scope = storage.getStore();
|
|
139
|
+
if (!scope) throw new ScopeRuntimeNotConfiguredError();
|
|
140
|
+
return scope;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/scope/internal.ts
|
|
145
|
+
const notifyCleanupFailure = async (observer, diagnostic) => {
|
|
146
|
+
if (!observer) return;
|
|
147
|
+
try {
|
|
148
|
+
await observer(diagnostic);
|
|
149
|
+
} catch {}
|
|
150
|
+
};
|
|
151
|
+
const runScoped = async (scope, program, options) => {
|
|
152
|
+
let value;
|
|
153
|
+
let programFailed = false;
|
|
154
|
+
let programFailure;
|
|
155
|
+
try {
|
|
156
|
+
value = await ScopeRuntime.run(scope, program);
|
|
157
|
+
} catch (cause) {
|
|
158
|
+
programFailed = true;
|
|
159
|
+
programFailure = cause;
|
|
160
|
+
}
|
|
161
|
+
const outcome = programFailed ? {
|
|
162
|
+
status: "failure",
|
|
163
|
+
cause: programFailure
|
|
164
|
+
} : options.classify(value);
|
|
165
|
+
let cleanupFailed = false;
|
|
166
|
+
let cleanupFailure;
|
|
167
|
+
try {
|
|
168
|
+
await scope.close(outcome);
|
|
169
|
+
} catch (cause) {
|
|
170
|
+
cleanupFailed = true;
|
|
171
|
+
cleanupFailure = cause;
|
|
172
|
+
}
|
|
173
|
+
if (cleanupFailed) {
|
|
174
|
+
const error = cleanupFailure instanceof ScopeCloseError ? cleanupFailure : new ScopeCloseError([cleanupFailure]);
|
|
175
|
+
await notifyCleanupFailure(options.onCleanupFailure, {
|
|
176
|
+
outcome,
|
|
177
|
+
error
|
|
178
|
+
});
|
|
179
|
+
cleanupFailure = error;
|
|
180
|
+
}
|
|
181
|
+
if (programFailed) throw programFailure;
|
|
182
|
+
if (outcome.status === "failure") return value;
|
|
183
|
+
if (cleanupFailed) throw cleanupFailure;
|
|
184
|
+
return value;
|
|
185
|
+
};
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/scope/scope.ts
|
|
188
|
+
const SCOPE_SUCCESS$1 = Object.freeze({ status: "success" });
|
|
189
|
+
var ScopeImpl = class ScopeImpl {
|
|
190
|
+
parent;
|
|
191
|
+
children = /* @__PURE__ */ new Set();
|
|
192
|
+
finalizers = [];
|
|
193
|
+
closePromise;
|
|
194
|
+
closeOutcome;
|
|
195
|
+
constructor(parent) {
|
|
196
|
+
this.parent = parent;
|
|
197
|
+
}
|
|
198
|
+
fork() {
|
|
199
|
+
this.assertOpen();
|
|
200
|
+
const child = new ScopeImpl(this);
|
|
201
|
+
this.children.add(child);
|
|
202
|
+
return child;
|
|
203
|
+
}
|
|
204
|
+
addFinalizer(finalizer) {
|
|
205
|
+
this.assertOpen();
|
|
206
|
+
this.finalizers.push(finalizer);
|
|
207
|
+
}
|
|
208
|
+
async acquire(acquire, release) {
|
|
209
|
+
this.assertOpen();
|
|
210
|
+
const resource = await acquire();
|
|
211
|
+
try {
|
|
212
|
+
this.addFinalizer((outcome) => release(resource, outcome));
|
|
213
|
+
return resource;
|
|
214
|
+
} catch (scopeFailure) {
|
|
215
|
+
try {
|
|
216
|
+
await release(resource, this.closeOutcome ?? SCOPE_SUCCESS$1);
|
|
217
|
+
} catch (releaseFailure) {
|
|
218
|
+
throw new AggregateError([scopeFailure, releaseFailure], "Scope closed while acquiring a resource and immediate cleanup also failed");
|
|
219
|
+
}
|
|
220
|
+
throw scopeFailure;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async add(resource) {
|
|
224
|
+
const finalizer = getDisposeFinalizer(resource);
|
|
225
|
+
if (!finalizer) throw new ResourceNotDisposableError();
|
|
226
|
+
try {
|
|
227
|
+
this.addFinalizer(finalizer);
|
|
228
|
+
return resource;
|
|
229
|
+
} catch (scopeFailure) {
|
|
230
|
+
try {
|
|
231
|
+
await finalizer(this.closeOutcome ?? SCOPE_SUCCESS$1);
|
|
232
|
+
} catch (releaseFailure) {
|
|
233
|
+
throw new AggregateError([scopeFailure, releaseFailure], "Scope closed while adding a disposable resource and cleanup also failed");
|
|
234
|
+
}
|
|
235
|
+
throw scopeFailure;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
close(outcome = SCOPE_SUCCESS$1) {
|
|
239
|
+
if (this.closePromise) return this.closePromise;
|
|
240
|
+
this.closeOutcome = outcome;
|
|
241
|
+
this.closePromise = ScopeRuntime.run(this, () => this.closeInternal(outcome));
|
|
242
|
+
return this.closePromise;
|
|
243
|
+
}
|
|
244
|
+
async closeInternal(outcome) {
|
|
245
|
+
const failures = [];
|
|
246
|
+
const children = [...this.children];
|
|
247
|
+
this.children.clear();
|
|
248
|
+
for (let index = children.length - 1; index >= 0; index--) {
|
|
249
|
+
const child = children[index];
|
|
250
|
+
if (!child) continue;
|
|
251
|
+
try {
|
|
252
|
+
await child.close(outcome);
|
|
253
|
+
} catch (cause) {
|
|
254
|
+
if (cause instanceof ScopeCloseError) failures.push(...cause.causes);
|
|
255
|
+
else failures.push(cause);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
for (let index = this.finalizers.length - 1; index >= 0; index--) {
|
|
259
|
+
const finalizer = this.finalizers[index];
|
|
260
|
+
if (!finalizer) continue;
|
|
261
|
+
try {
|
|
262
|
+
await finalizer(outcome);
|
|
263
|
+
} catch (cause) {
|
|
264
|
+
failures.push(cause);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
this.finalizers.length = 0;
|
|
268
|
+
this.detach();
|
|
269
|
+
if (failures.length > 0) throw new ScopeCloseError(failures);
|
|
270
|
+
}
|
|
271
|
+
detach() {
|
|
272
|
+
const parent = this.parent;
|
|
273
|
+
if (!parent) return;
|
|
274
|
+
parent.children.delete(this);
|
|
275
|
+
this.parent = void 0;
|
|
276
|
+
}
|
|
277
|
+
assertOpen() {
|
|
278
|
+
if (this.closePromise) throw new ScopeClosedError();
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
const Scope = {
|
|
282
|
+
make() {
|
|
283
|
+
return new ScopeImpl();
|
|
284
|
+
},
|
|
285
|
+
current() {
|
|
286
|
+
return ScopeRuntime.current();
|
|
287
|
+
},
|
|
288
|
+
provide(scope, program) {
|
|
289
|
+
return ScopeRuntime.run(scope, program);
|
|
290
|
+
},
|
|
291
|
+
*[Symbol.iterator]() {
|
|
292
|
+
return ScopeRuntime.current();
|
|
293
|
+
},
|
|
294
|
+
run(program) {
|
|
295
|
+
const scope = new ScopeImpl();
|
|
296
|
+
return runScoped(scope, () => program(scope), { classify: () => SCOPE_SUCCESS$1 });
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/runtime/outcome.ts
|
|
301
|
+
const isResultLike = (value) => typeof value === "object" && value !== null && "status" in value && (value.status === "ok" || value.status === "error");
|
|
302
|
+
const classifyRuntimeOutcome = (value) => {
|
|
303
|
+
if (isResultLike(value) && Result.isError(value)) return {
|
|
304
|
+
status: "failure",
|
|
305
|
+
cause: value.error
|
|
306
|
+
};
|
|
307
|
+
return { status: "success" };
|
|
308
|
+
};
|
|
309
|
+
//#endregion
|
|
66
310
|
//#region src/layer/runtime.ts
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return
|
|
311
|
+
const SCOPE_SUCCESS = Object.freeze({ status: "success" });
|
|
312
|
+
const normalizeDisposeCauses = (cause) => {
|
|
313
|
+
if (cause instanceof AggregateError) return [...cause.errors];
|
|
314
|
+
return [cause];
|
|
315
|
+
};
|
|
316
|
+
const notifyShutdownFailure = async (observer, diagnostic) => {
|
|
317
|
+
if (!observer) return;
|
|
318
|
+
try {
|
|
319
|
+
await observer(diagnostic);
|
|
320
|
+
} catch {}
|
|
321
|
+
};
|
|
322
|
+
const bindProviderToScope = (provider, rootScope) => ({
|
|
323
|
+
service: provider.service,
|
|
324
|
+
acquire: () => ScopeRuntime.run(rootScope, async () => {
|
|
325
|
+
if (!provider.release) return await provider.acquire();
|
|
326
|
+
return await rootScope.acquire(() => provider.acquire(), (resource, outcome) => provider.release(resource, outcome));
|
|
327
|
+
})
|
|
328
|
+
});
|
|
329
|
+
var BuiltLayerImpl = class {
|
|
330
|
+
backend;
|
|
331
|
+
rootScope;
|
|
332
|
+
onCleanupFailure;
|
|
333
|
+
disposePromise;
|
|
334
|
+
executions = /* @__PURE__ */ new Set();
|
|
335
|
+
state = "active";
|
|
336
|
+
constructor(backend, rootScope, onCleanupFailure) {
|
|
337
|
+
this.backend = backend;
|
|
338
|
+
this.rootScope = rootScope;
|
|
339
|
+
this.onCleanupFailure = onCleanupFailure;
|
|
340
|
+
}
|
|
341
|
+
run(program) {
|
|
342
|
+
this.assertActive();
|
|
343
|
+
const executionScope = this.rootScope.fork();
|
|
344
|
+
let resolveExecution;
|
|
345
|
+
let rejectExecution;
|
|
346
|
+
const execution = new Promise((resolve, reject) => {
|
|
347
|
+
resolveExecution = resolve;
|
|
348
|
+
rejectExecution = reject;
|
|
349
|
+
});
|
|
350
|
+
this.executions.add(execution);
|
|
351
|
+
execution.then(() => {
|
|
352
|
+
this.executions.delete(execution);
|
|
353
|
+
}, () => {
|
|
354
|
+
this.executions.delete(execution);
|
|
355
|
+
});
|
|
356
|
+
try {
|
|
357
|
+
this.runExecution(executionScope, program).then((value) => {
|
|
358
|
+
resolveExecution(value);
|
|
359
|
+
}, (cause) => {
|
|
360
|
+
rejectExecution(cause);
|
|
361
|
+
});
|
|
362
|
+
} catch (cause) {
|
|
363
|
+
rejectExecution(cause);
|
|
364
|
+
}
|
|
365
|
+
return execution;
|
|
366
|
+
}
|
|
367
|
+
runExecution(executionScope, program) {
|
|
368
|
+
const options = this.onCleanupFailure ? {
|
|
369
|
+
classify: classifyRuntimeOutcome,
|
|
370
|
+
onCleanupFailure: this.onCleanupFailure
|
|
371
|
+
} : { classify: classifyRuntimeOutcome };
|
|
372
|
+
return runScoped(executionScope, () => ServiceRuntime.run(this.backend, program), options);
|
|
373
|
+
}
|
|
374
|
+
dispose(outcome = SCOPE_SUCCESS) {
|
|
375
|
+
if (this.disposePromise) return this.disposePromise;
|
|
376
|
+
this.state = "disposing";
|
|
377
|
+
const executions = [...this.executions];
|
|
378
|
+
this.disposePromise = this.performDispose(executions, outcome);
|
|
379
|
+
return this.disposePromise;
|
|
380
|
+
}
|
|
381
|
+
async performDispose(executions, outcome) {
|
|
382
|
+
const failures = [];
|
|
383
|
+
await Promise.allSettled(executions);
|
|
384
|
+
try {
|
|
385
|
+
await ServiceRuntime.run(this.backend, () => this.rootScope.close(outcome));
|
|
386
|
+
} catch (cause) {
|
|
387
|
+
failures.push(cause);
|
|
388
|
+
}
|
|
389
|
+
try {
|
|
390
|
+
await this.backend.disposeAll();
|
|
391
|
+
} catch (cause) {
|
|
392
|
+
failures.push(cause);
|
|
393
|
+
}
|
|
394
|
+
this.state = "disposed";
|
|
395
|
+
if (failures.length > 0) {
|
|
396
|
+
const error = new LayerDisposeError(failures.flatMap(normalizeDisposeCauses));
|
|
397
|
+
await notifyShutdownFailure(this.onCleanupFailure, {
|
|
398
|
+
outcome,
|
|
399
|
+
error
|
|
400
|
+
});
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
assertActive() {
|
|
405
|
+
if (this.state !== "active") throw new BuiltLayerDisposedError();
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
const buildLayer = async (layer, backend, options = {}) => {
|
|
409
|
+
const rootScope = Scope.make();
|
|
410
|
+
let current;
|
|
411
|
+
try {
|
|
412
|
+
for (const provider of layer.providers) {
|
|
413
|
+
current = provider;
|
|
414
|
+
await backend.register(bindProviderToScope(provider, rootScope));
|
|
415
|
+
}
|
|
416
|
+
} catch (registrationCause) {
|
|
417
|
+
const outcome = {
|
|
418
|
+
status: "failure",
|
|
419
|
+
cause: registrationCause
|
|
420
|
+
};
|
|
421
|
+
const cleanupCauses = [];
|
|
422
|
+
try {
|
|
423
|
+
await ServiceRuntime.run(backend, () => rootScope.close(outcome));
|
|
424
|
+
} catch (cause) {
|
|
425
|
+
cleanupCauses.push(cause);
|
|
426
|
+
}
|
|
71
427
|
try {
|
|
72
428
|
await backend.disposeAll();
|
|
73
|
-
}
|
|
74
|
-
|
|
429
|
+
} catch (cause) {
|
|
430
|
+
cleanupCauses.push(cause);
|
|
431
|
+
}
|
|
432
|
+
if (cleanupCauses.length > 0) {
|
|
433
|
+
const shutdownError = new LayerDisposeError(cleanupCauses.flatMap(normalizeDisposeCauses));
|
|
434
|
+
await notifyShutdownFailure(options.onCleanupFailure, {
|
|
435
|
+
outcome,
|
|
436
|
+
error: shutdownError
|
|
437
|
+
});
|
|
75
438
|
}
|
|
76
|
-
|
|
439
|
+
let cleanupCause;
|
|
440
|
+
if (cleanupCauses.length === 1) cleanupCause = cleanupCauses[0];
|
|
441
|
+
else if (cleanupCauses.length > 1) cleanupCause = new LayerDisposeError(cleanupCauses.flatMap(normalizeDisposeCauses));
|
|
442
|
+
throw new LayerRegistrationError(current?.service, registrationCause, cleanupCause);
|
|
443
|
+
}
|
|
444
|
+
return new BuiltLayerImpl(backend, rootScope, options.onCleanupFailure);
|
|
77
445
|
};
|
|
78
|
-
|
|
79
|
-
|
|
446
|
+
//#endregion
|
|
447
|
+
//#region src/effect/effect.ts
|
|
448
|
+
function gen(body) {
|
|
449
|
+
return Result.gen(body);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Acquire a resource in the current Scope and register its release callback.
|
|
453
|
+
*
|
|
454
|
+
* Acquisition failures are represented in the Effect Result error channel;
|
|
455
|
+
* release failures remain owned by Scope cleanup.
|
|
456
|
+
*/
|
|
457
|
+
function acquireRelease(acquire, release) {
|
|
458
|
+
const scope = Scope.current();
|
|
459
|
+
return Result.await(Result.tryPromise(() => scope.acquire(acquire, release)));
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Register an already-acquired disposable resource in the current Scope.
|
|
463
|
+
*
|
|
464
|
+
* Registration failures are represented in the Effect Result error channel;
|
|
465
|
+
* disposal failures remain owned by Scope cleanup.
|
|
466
|
+
*/
|
|
467
|
+
function add(resource) {
|
|
468
|
+
const scope = Scope.current();
|
|
469
|
+
return Result.await(Result.tryPromise(() => scope.add(resource)));
|
|
470
|
+
}
|
|
471
|
+
const Effect = {
|
|
472
|
+
gen,
|
|
473
|
+
acquireRelease,
|
|
474
|
+
add
|
|
80
475
|
};
|
|
81
476
|
//#endregion
|
|
82
477
|
//#region src/resource/errors.ts
|
|
83
478
|
var ResourceReleaseFailure = class extends TaggedError("ResourceReleaseFailure") {};
|
|
84
479
|
//#endregion
|
|
85
480
|
//#region src/resource/internal.ts
|
|
86
|
-
const disposeResource = (resource) => {
|
|
87
|
-
const candidate = Object(resource);
|
|
88
|
-
return (candidate[Symbol.asyncDispose] ?? candidate[Symbol.dispose])?.call(candidate);
|
|
89
|
-
};
|
|
90
481
|
const toReleaseFailure = (resource, cause) => new ResourceReleaseFailure({
|
|
91
482
|
resource,
|
|
92
483
|
cause,
|
|
93
484
|
message: `Failed to release resource: ${resource}`
|
|
94
485
|
});
|
|
95
|
-
/**
|
|
96
|
-
* Executa uma operação que retorna Result e converte
|
|
97
|
-
* exceptions/rejections inesperadas para UnhandledException.
|
|
98
|
-
*/
|
|
99
486
|
const runResult = async (operation) => {
|
|
100
487
|
return (await Result.tryPromise(() => Promise.resolve(operation()))).andThen((result) => result);
|
|
101
488
|
};
|
|
@@ -109,31 +496,98 @@ const runRelease = async (name, resource, release) => {
|
|
|
109
496
|
catch: (cause) => toReleaseFailure(name, cause)
|
|
110
497
|
})).andThen((outcome) => normalizeReleaseOutcome(name, outcome));
|
|
111
498
|
};
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
499
|
+
const notifyReleaseFailure = async (observer, failure) => {
|
|
500
|
+
if (!observer) return;
|
|
501
|
+
try {
|
|
502
|
+
await observer(failure);
|
|
503
|
+
} catch {}
|
|
504
|
+
};
|
|
505
|
+
const combineUseAndRelease = async (used, released, onReleaseFailure) => {
|
|
506
|
+
if (Result.isError(used)) {
|
|
507
|
+
if (Result.isError(released)) await notifyReleaseFailure(onReleaseFailure, released.error);
|
|
508
|
+
return Result.err(used.error);
|
|
509
|
+
}
|
|
510
|
+
if (Result.isError(released)) {
|
|
511
|
+
await notifyReleaseFailure(onReleaseFailure, released.error);
|
|
512
|
+
return Result.err(released.error);
|
|
513
|
+
}
|
|
514
|
+
return Result.ok(used.value);
|
|
515
|
+
};
|
|
120
516
|
//#endregion
|
|
121
517
|
//#region src/resource/resource.ts
|
|
122
|
-
const acquireUseRelease = ({ name, acquire, use, release = disposeResource }) => Result.gen(async function* () {
|
|
518
|
+
const acquireUseRelease = ({ name, acquire, use, release = disposeResource, onReleaseFailure }) => Result.gen(async function* () {
|
|
123
519
|
const resource = yield* Result.await(runResult(acquire));
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
* nunca seria executado.
|
|
130
|
-
*/
|
|
520
|
+
const scope = Scope.make();
|
|
521
|
+
let released = Result.ok();
|
|
522
|
+
scope.addFinalizer(async () => {
|
|
523
|
+
released = await runRelease(name, resource, release);
|
|
524
|
+
});
|
|
131
525
|
const used = await runResult(() => use(resource));
|
|
132
|
-
|
|
133
|
-
return combineUseAndRelease(used, released);
|
|
526
|
+
await scope.close();
|
|
527
|
+
return await combineUseAndRelease(used, released, onReleaseFailure);
|
|
134
528
|
});
|
|
135
529
|
const Resource = { acquireUseRelease };
|
|
136
530
|
//#endregion
|
|
137
|
-
|
|
531
|
+
//#region src/runtime/runtime.ts
|
|
532
|
+
var Runtime = class Runtime {
|
|
533
|
+
built;
|
|
534
|
+
constructor(built) {
|
|
535
|
+
this.built = built;
|
|
536
|
+
}
|
|
537
|
+
static async make(layer, backend, options = {}) {
|
|
538
|
+
const built = await buildLayer(layer, backend, options);
|
|
539
|
+
return new Runtime(built);
|
|
540
|
+
}
|
|
541
|
+
static async run(layer, backend, program, options = {}) {
|
|
542
|
+
const runtime = await Runtime.make(layer, backend, options);
|
|
543
|
+
let value;
|
|
544
|
+
let executionFailed = false;
|
|
545
|
+
let executionFailure;
|
|
546
|
+
let programOutcome;
|
|
547
|
+
try {
|
|
548
|
+
value = await runtime.runUnchecked(async () => {
|
|
549
|
+
try {
|
|
550
|
+
const programValue = await program();
|
|
551
|
+
programOutcome = classifyRuntimeOutcome(programValue);
|
|
552
|
+
return programValue;
|
|
553
|
+
} catch (cause) {
|
|
554
|
+
programOutcome = {
|
|
555
|
+
status: "failure",
|
|
556
|
+
cause
|
|
557
|
+
};
|
|
558
|
+
throw cause;
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
} catch (cause) {
|
|
562
|
+
executionFailed = true;
|
|
563
|
+
executionFailure = cause;
|
|
564
|
+
}
|
|
565
|
+
const outcome = programOutcome ?? {
|
|
566
|
+
status: "failure",
|
|
567
|
+
cause: executionFailure
|
|
568
|
+
};
|
|
569
|
+
try {
|
|
570
|
+
await runtime.disposeWithOutcome(outcome);
|
|
571
|
+
} catch (shutdownFailure) {
|
|
572
|
+
if (!executionFailed && outcome.status === "success") throw shutdownFailure;
|
|
573
|
+
}
|
|
574
|
+
if (executionFailed) throw executionFailure;
|
|
575
|
+
return value;
|
|
576
|
+
}
|
|
577
|
+
run(program) {
|
|
578
|
+
return this.built.run(program);
|
|
579
|
+
}
|
|
580
|
+
runUnchecked(program) {
|
|
581
|
+
return this.built.run(program);
|
|
582
|
+
}
|
|
583
|
+
dispose() {
|
|
584
|
+
return this.built.dispose();
|
|
585
|
+
}
|
|
586
|
+
disposeWithOutcome(outcome) {
|
|
587
|
+
return this.built.dispose(outcome);
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
//#endregion
|
|
591
|
+
export { BuiltLayerDisposedError, DuplicateServiceError, Effect, Layer, LayerDisposeError, LayerGeneratorYieldError, LayerRegistrationError, Resource, ResourceNotDisposableError, ResourceReleaseFailure, Runtime, Scope, ScopeCloseError, ScopeClosedError, ScopeRuntimeNotConfiguredError, Service, ServiceNotFoundError, ServiceRuntime, ServiceRuntimeNotConfiguredError, buildLayer };
|
|
138
592
|
|
|
139
593
|
//# sourceMappingURL=index.mjs.map
|