@typed/router 0.32.0 → 1.0.0-beta.1

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.
Files changed (87) hide show
  1. package/README.md +129 -2
  2. package/dist/AST.d.ts +96 -0
  3. package/dist/AST.d.ts.map +1 -0
  4. package/dist/AST.js +32 -0
  5. package/dist/CurrentRoute.d.ts +18 -0
  6. package/dist/CurrentRoute.d.ts.map +1 -0
  7. package/dist/CurrentRoute.js +18 -0
  8. package/dist/Matcher.d.ts +209 -0
  9. package/dist/Matcher.d.ts.map +1 -0
  10. package/dist/Matcher.js +633 -0
  11. package/dist/Parser.d.ts +92 -0
  12. package/dist/Parser.d.ts.map +1 -0
  13. package/dist/Parser.js +1 -0
  14. package/dist/Path.d.ts +216 -0
  15. package/dist/Path.d.ts.map +1 -0
  16. package/dist/Path.js +248 -0
  17. package/dist/Route.d.ts +57 -0
  18. package/dist/Route.d.ts.map +1 -0
  19. package/dist/Route.js +151 -0
  20. package/dist/Router.d.ts +9 -0
  21. package/dist/Router.d.ts.map +1 -0
  22. package/dist/Router.js +8 -0
  23. package/dist/Uri.d.ts +115 -0
  24. package/dist/Uri.d.ts.map +1 -0
  25. package/dist/Uri.js +1 -0
  26. package/dist/index.d.ts +5 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +4 -0
  29. package/package.json +32 -73
  30. package/src/AST.ts +166 -0
  31. package/src/CurrentRoute.ts +30 -331
  32. package/src/Matcher.test.ts +496 -0
  33. package/src/Matcher.ts +1375 -325
  34. package/src/Parser.ts +276 -0
  35. package/src/Path.test.ts +318 -0
  36. package/src/Path.ts +691 -0
  37. package/src/Route.test.ts +268 -0
  38. package/src/Route.ts +316 -0
  39. package/src/Router.ts +33 -0
  40. package/src/Uri.ts +214 -0
  41. package/src/index.ts +4 -28
  42. package/CurrentRoute/package.json +0 -6
  43. package/LICENSE +0 -21
  44. package/MatchInput/package.json +0 -6
  45. package/Matcher/package.json +0 -6
  46. package/RouteGuard/package.json +0 -6
  47. package/RouteMatch/package.json +0 -6
  48. package/dist/cjs/CurrentRoute.js +0 -170
  49. package/dist/cjs/CurrentRoute.js.map +0 -1
  50. package/dist/cjs/MatchInput.js +0 -96
  51. package/dist/cjs/MatchInput.js.map +0 -1
  52. package/dist/cjs/Matcher.js +0 -138
  53. package/dist/cjs/Matcher.js.map +0 -1
  54. package/dist/cjs/RouteGuard.js +0 -78
  55. package/dist/cjs/RouteGuard.js.map +0 -1
  56. package/dist/cjs/RouteMatch.js +0 -49
  57. package/dist/cjs/RouteMatch.js.map +0 -1
  58. package/dist/cjs/index.js +0 -53
  59. package/dist/cjs/index.js.map +0 -1
  60. package/dist/dts/CurrentRoute.d.ts +0 -94
  61. package/dist/dts/CurrentRoute.d.ts.map +0 -1
  62. package/dist/dts/MatchInput.d.ts +0 -143
  63. package/dist/dts/MatchInput.d.ts.map +0 -1
  64. package/dist/dts/Matcher.d.ts +0 -121
  65. package/dist/dts/Matcher.d.ts.map +0 -1
  66. package/dist/dts/RouteGuard.d.ts +0 -94
  67. package/dist/dts/RouteGuard.d.ts.map +0 -1
  68. package/dist/dts/RouteMatch.d.ts +0 -50
  69. package/dist/dts/RouteMatch.d.ts.map +0 -1
  70. package/dist/dts/index.d.ts +0 -24
  71. package/dist/dts/index.d.ts.map +0 -1
  72. package/dist/esm/CurrentRoute.js +0 -152
  73. package/dist/esm/CurrentRoute.js.map +0 -1
  74. package/dist/esm/MatchInput.js +0 -79
  75. package/dist/esm/MatchInput.js.map +0 -1
  76. package/dist/esm/Matcher.js +0 -130
  77. package/dist/esm/Matcher.js.map +0 -1
  78. package/dist/esm/RouteGuard.js +0 -57
  79. package/dist/esm/RouteGuard.js.map +0 -1
  80. package/dist/esm/RouteMatch.js +0 -29
  81. package/dist/esm/RouteMatch.js.map +0 -1
  82. package/dist/esm/index.js +0 -24
  83. package/dist/esm/index.js.map +0 -1
  84. package/dist/esm/package.json +0 -4
  85. package/src/MatchInput.ts +0 -303
  86. package/src/RouteGuard.ts +0 -217
  87. package/src/RouteMatch.ts +0 -104
@@ -0,0 +1,633 @@
1
+ import * as findMyWay from "find-my-way-ts";
2
+ import * as Cause from "effect/Cause";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Exit from "effect/Exit";
5
+ import { interrupt, isSuccess } from "effect/Exit";
6
+ import { dual, identity } from "effect/Function";
7
+ import * as Result from "effect/Result";
8
+ import * as Layer from "effect/Layer";
9
+ import * as Option from "effect/Option";
10
+ import { pipeArguments } from "effect/Pipeable";
11
+ import * as Schema from "effect/Schema";
12
+ import { makeFormatterDefault } from "effect/SchemaIssue";
13
+ import * as Scope from "effect/Scope";
14
+ import * as ServiceMap from "effect/ServiceMap";
15
+ import * as Stream from "effect/Stream";
16
+ import { exit } from "@typed/fx/Fx";
17
+ import { mapEffect } from "@typed/fx/Fx/combinators/mapEffect";
18
+ import { provideServices } from "@typed/fx/Fx/combinators/provide";
19
+ import { skipRepeats } from "@typed/fx/Fx/combinators/skipRepeats";
20
+ import { switchMap } from "@typed/fx/Fx/combinators/switchMap";
21
+ import { unwrap } from "@typed/fx/Fx/combinators/unwrap";
22
+ import { fromEffect, never } from "@typed/fx/Fx/constructors/fromEffect";
23
+ import { succeed } from "@typed/fx/Fx/constructors/succeed";
24
+ import { fromStream } from "@typed/fx/Fx/stream";
25
+ import { isFx } from "@typed/fx/Fx/TypeId";
26
+ import { RefSubject } from "@typed/fx/RefSubject";
27
+ import { CurrentPath, Navigation } from "@typed/navigation/Navigation";
28
+ import * as AST from "./AST.js";
29
+ import { CurrentRoute } from "./CurrentRoute.js";
30
+ import { Join, make as makeRoute } from "./Route.js";
31
+ function isMatchHandlerFn(handler) {
32
+ return typeof handler === "function";
33
+ }
34
+ function isHandlerOptions(value) {
35
+ return typeof value === "object" && value !== null && "handler" in value;
36
+ }
37
+ function parseMatchArgs(args) {
38
+ const [first, second, third] = args;
39
+ // Single arg: full options object (Overload 9)
40
+ if (second === undefined) {
41
+ const opts = first;
42
+ return {
43
+ route: opts.route,
44
+ handler: opts.handler,
45
+ guard: undefined,
46
+ layout: opts.layout,
47
+ catchFn: opts.catch,
48
+ dependencies: opts.dependencies,
49
+ };
50
+ }
51
+ // Two args
52
+ if (third === undefined) {
53
+ if (isHandlerOptions(second)) {
54
+ // Overload 3: match(route, options)
55
+ const opts = second;
56
+ return {
57
+ route: first,
58
+ handler: opts.handler,
59
+ guard: undefined,
60
+ layout: opts.layout,
61
+ catchFn: opts.catch,
62
+ dependencies: opts.dependencies,
63
+ };
64
+ }
65
+ // Overloads 1, 2, 4: match(route, handler)
66
+ return {
67
+ route: first,
68
+ handler: second,
69
+ guard: undefined,
70
+ layout: undefined,
71
+ catchFn: undefined,
72
+ dependencies: undefined,
73
+ };
74
+ }
75
+ // Three args
76
+ if (isHandlerOptions(third)) {
77
+ // Overload 7: match(route, guard, options)
78
+ const opts = third;
79
+ return {
80
+ route: first,
81
+ handler: opts.handler,
82
+ guard: second,
83
+ layout: opts.layout,
84
+ catchFn: opts.catch,
85
+ dependencies: opts.dependencies,
86
+ };
87
+ }
88
+ // Overloads 5, 6, 8: match(route, guard, handler)
89
+ return {
90
+ route: first,
91
+ handler: third,
92
+ guard: second,
93
+ layout: undefined,
94
+ catchFn: undefined,
95
+ dependencies: undefined,
96
+ };
97
+ }
98
+ class MatcherImpl {
99
+ cases;
100
+ constructor(cases) {
101
+ this.cases = cases;
102
+ this.match = this.match.bind(this);
103
+ this.catch = this.catch.bind(this);
104
+ this.catchTag = this.catchTag.bind(this);
105
+ this.layout = this.layout.bind(this);
106
+ this.provide = this.provide.bind(this);
107
+ this.provideService = this.provideService.bind(this);
108
+ }
109
+ match(...args) {
110
+ const parsed = parseMatchArgs(args);
111
+ const normalizedGuard = parsed.guard !== undefined
112
+ ? getGuard(parsed.guard)
113
+ : defaultGuard();
114
+ const routeAst = AST.route(parsed.route.ast, parsed.handler, normalizedGuard);
115
+ let matches = [routeAst];
116
+ if (parsed.layout !== undefined) {
117
+ matches = [AST.layout(matches, parsed.layout)];
118
+ }
119
+ if (parsed.catchFn !== undefined) {
120
+ matches = [AST.catchCause(matches, parsed.catchFn)];
121
+ }
122
+ if (parsed.dependencies !== undefined && parsed.dependencies.length > 0) {
123
+ matches = [AST.layer(matches, normalizeDependencies(parsed.dependencies))];
124
+ }
125
+ return new MatcherImpl([...this.cases, ...matches]);
126
+ }
127
+ prefix(route) {
128
+ return new MatcherImpl([AST.prefixed(this.cases, route.ast)]);
129
+ }
130
+ provide(...layers) {
131
+ return new MatcherImpl([AST.layer(this.cases, layers)]);
132
+ }
133
+ provideService(tag, service) {
134
+ return this.provideServices(ServiceMap.make(tag, service));
135
+ }
136
+ provideServices(services) {
137
+ return this.provide(Layer.succeedServices(services));
138
+ }
139
+ catchCause(f) {
140
+ return new MatcherImpl([AST.catchCause(this.cases, f)]);
141
+ }
142
+ catch(f) {
143
+ return this.catchCause((causeRef) => unwrap(Effect.gen(function* () {
144
+ const cause = yield* causeRef;
145
+ const result = Cause.findFail(cause);
146
+ if (Result.isFailure(result)) {
147
+ return fromEffect(Effect.failCause(result.failure));
148
+ }
149
+ return f(result.success.error);
150
+ })));
151
+ }
152
+ catchTag(tag, f) {
153
+ const rethrow = (cause) => fromEffect(Effect.failCause(cause));
154
+ return new MatcherImpl([
155
+ AST.catchCause(this.cases, (causeRef) => unwrap(Effect.gen(function* () {
156
+ const cause = yield* causeRef;
157
+ const result = Cause.findFail(cause);
158
+ if (Result.isFailure(result)) {
159
+ return rethrow(cause);
160
+ }
161
+ if (matchesTag(tag, result.success.error)) {
162
+ return f(result.success.error);
163
+ }
164
+ return rethrow(cause);
165
+ }))),
166
+ ]);
167
+ }
168
+ layout(layout) {
169
+ return new MatcherImpl([
170
+ AST.layout(this.cases, layout),
171
+ ]);
172
+ }
173
+ merge(...others) {
174
+ const allCases = [...this.cases, ...others.flatMap((m) => m.cases)];
175
+ return new MatcherImpl(allCases);
176
+ }
177
+ pipe() {
178
+ return pipeArguments(this, arguments);
179
+ }
180
+ }
181
+ function normalizeHandler(handler) {
182
+ if (isMatchHandlerFn(handler))
183
+ return (params) => toFx(handler(params));
184
+ return () => toFx(handler);
185
+ }
186
+ function toFx(value) {
187
+ if (isFx(value))
188
+ return value;
189
+ if (Stream.isStream(value))
190
+ return fromStream(value);
191
+ if (Effect.isEffect(value))
192
+ return fromEffect(value);
193
+ return succeed(value);
194
+ }
195
+ export const empty = new MatcherImpl([]);
196
+ export const match = empty.match.bind(empty);
197
+ /**
198
+ * Merge multiple matchers into one. Each matcher's layouts and provide apply only to its own routes.
199
+ * Use this so directory layouts (e.g. api/_layout) and directory dependencies apply only to routes under that directory.
200
+ */
201
+ export function merge(...matchers) {
202
+ if (matchers.length === 0) {
203
+ return empty;
204
+ }
205
+ if (matchers.length === 1) {
206
+ return matchers[0];
207
+ }
208
+ const first = matchers[0];
209
+ const rest = matchers.slice(1);
210
+ return first.merge(...rest);
211
+ }
212
+ export class RouteGuardError extends Schema.ErrorClass("@typed/router/RouteGuardError")({
213
+ _tag: Schema.tag("RouteGuardError"),
214
+ path: Schema.String,
215
+ causes: Schema.Array(Schema.Unknown),
216
+ }) {
217
+ }
218
+ export class RouteNotFound extends Schema.ErrorClass("@typed/router/RouteNotFound")({
219
+ _tag: Schema.tag("RouteNotFound"),
220
+ path: Schema.String,
221
+ }) {
222
+ }
223
+ export class RouteDecodeError extends Schema.ErrorClass("@typed/router/RouteDecodeError")({
224
+ _tag: Schema.tag("RouteDecodeError"),
225
+ path: Schema.String,
226
+ cause: Schema.String,
227
+ }) {
228
+ }
229
+ export function run(matcher) {
230
+ return unwrap(Effect.gen(function* () {
231
+ const fiberId = yield* Effect.fiberId;
232
+ const rootScope = yield* Effect.scope;
233
+ const current = yield* CurrentRoute;
234
+ const prefixed = matcher.prefix(current.route);
235
+ const entries = compile(prefixed.cases);
236
+ const router = findMyWay.make({
237
+ ignoreTrailingSlash: true,
238
+ caseSensitive: false,
239
+ });
240
+ const handlersByPath = new Map();
241
+ const memoMap = yield* Layer.makeMemoMap;
242
+ const layerManager = makeLayerManager(memoMap, rootScope, fiberId);
243
+ const layoutManager = makeLayoutManager(rootScope, fiberId);
244
+ const catchManager = makeCatchManager(rootScope, fiberId);
245
+ for (const entry of entries) {
246
+ const path = entry.route.path;
247
+ const existing = handlersByPath.get(path);
248
+ if (existing !== undefined) {
249
+ existing.push(entry);
250
+ }
251
+ else {
252
+ const list = [entry];
253
+ handlersByPath.set(path, list);
254
+ router.all(path, list);
255
+ }
256
+ }
257
+ let currentState = null;
258
+ return CurrentPath.pipe(mapEffect(Effect.fn(function* (path) {
259
+ const result = router.find("GET", path);
260
+ if (result === undefined)
261
+ return yield* new RouteNotFound({ path });
262
+ const input = { ...result.params, ...result.searchParams };
263
+ const entries = result.handler;
264
+ const guardCauses = [];
265
+ let matchedEntry = undefined;
266
+ let matchedParams = undefined;
267
+ let matchedPrepared = undefined;
268
+ for (const entry of entries) {
269
+ const params = yield* Effect.mapErrorEager(entry.decode(input), (cause) => new RouteDecodeError({ path, cause: makeFormatterDefault()(cause.issue) }));
270
+ const prepared = yield* layerManager.prepare(entry.layers);
271
+ const guardExit = yield* entry
272
+ .guard(params)
273
+ .pipe(Effect.provideServices(prepared.services), Effect.exit);
274
+ if (Exit.isFailure(guardExit)) {
275
+ guardCauses.push(guardExit.cause);
276
+ yield* prepared.rollback;
277
+ continue;
278
+ }
279
+ if (Option.isNone(guardExit.value)) {
280
+ yield* prepared.rollback;
281
+ continue;
282
+ }
283
+ matchedEntry = entry;
284
+ matchedParams = guardExit.value.value;
285
+ matchedPrepared = prepared;
286
+ break;
287
+ }
288
+ if (matchedEntry === undefined || matchedPrepared === undefined) {
289
+ return yield* new RouteGuardError({ path, causes: guardCauses });
290
+ }
291
+ yield* matchedPrepared.commit;
292
+ if (currentState !== null && currentState.entry === matchedEntry) {
293
+ yield* RefSubject.set(currentState.params, matchedParams);
294
+ yield* layoutManager.updateParams(matchedEntry.layouts, matchedParams);
295
+ return currentState.fx;
296
+ }
297
+ if (currentState !== null) {
298
+ yield* Scope.close(currentState.scope, interrupt(fiberId));
299
+ currentState = null;
300
+ }
301
+ const scope = yield* Scope.fork(rootScope);
302
+ const paramsRef = yield* RefSubject.make(matchedParams).pipe(Scope.provide(scope));
303
+ const preparedServices = matchedPrepared.services;
304
+ const handlerServices = ServiceMap.merge(preparedServices, ServiceMap.make(Scope.Scope, scope));
305
+ const handlerFx = matchedEntry
306
+ .handler(paramsRef)
307
+ .pipe(provideServices(handlerServices));
308
+ const withLayouts = yield* layoutManager.apply(matchedEntry.layouts, matchedParams, handlerFx, preparedServices);
309
+ const withCatches = yield* catchManager.apply(matchedEntry.catches, withLayouts, preparedServices);
310
+ const fx = withCatches;
311
+ currentState = {
312
+ entry: matchedEntry,
313
+ params: paramsRef,
314
+ scope,
315
+ fx,
316
+ };
317
+ return currentState.fx;
318
+ })), skipRepeats, switchMap(identity));
319
+ }));
320
+ }
321
+ export const catchCause = dual(2, (input, f) => {
322
+ const eff = Effect.gen(function* () {
323
+ const fiberId = yield* Effect.fiberId;
324
+ const rootScope = yield* Effect.scope;
325
+ const fx = isFx(input) ? input : run(input);
326
+ const manager = makeCatchManager(rootScope, fiberId);
327
+ const result = yield* manager.apply([f], fx, ServiceMap.empty());
328
+ return result;
329
+ });
330
+ return unwrap(eff);
331
+ });
332
+ export const catch_ = dual(2, (input, f) => catchCause(input, (causeRef) => unwrap(Effect.gen(function* () {
333
+ const cause = yield* causeRef;
334
+ const result = Cause.findFail(cause);
335
+ if (Result.isFailure(result)) {
336
+ return fromEffect(Effect.failCause(result.failure));
337
+ }
338
+ return f(result.success.error);
339
+ }))));
340
+ export { catch_ as catch };
341
+ export const catchTag = dual(3, (input, k, f) => catchCause(input, (causeRef) => unwrap(Effect.gen(function* () {
342
+ const cause = yield* causeRef;
343
+ const result = Cause.findFail(cause);
344
+ if (Result.isFailure(result)) {
345
+ return fromEffect(Effect.failCause(result.failure));
346
+ }
347
+ if (matchesTag(k, result.success.error)) {
348
+ return f(result.success.error);
349
+ }
350
+ return fromEffect(Effect.fail(result.success.error));
351
+ }))));
352
+ export const redirectTo = (path) => (input) => catchCause(input, (_) => Navigation.navigate(path).pipe(Effect.matchCause({
353
+ onFailure: () => never,
354
+ onSuccess: () => never,
355
+ }), unwrap));
356
+ const hasTag = (u) => typeof u === "object" &&
357
+ u !== null &&
358
+ "_tag" in u &&
359
+ typeof u["_tag"] === "string";
360
+ const matchesTag = (tag, error) => {
361
+ if (!hasTag(error))
362
+ return false;
363
+ if (typeof tag === "string")
364
+ return error._tag === tag;
365
+ return tag.some((t) => t === error._tag);
366
+ };
367
+ function isServiceMap(dep) {
368
+ return !Layer.isLayer(dep);
369
+ }
370
+ function toSingleLayer(dep) {
371
+ if (isServiceMap(dep))
372
+ return Layer.succeedServices(dep);
373
+ return dep;
374
+ }
375
+ function normalizeDependencies(dependencies) {
376
+ return dependencies.map(toSingleLayer);
377
+ }
378
+ /**
379
+ * Normalize dependency input (ServiceMap | Layer | Array of either) into a single Layer.
380
+ * Use with `.provide(normalizeDependencyInput(deps))`.
381
+ */
382
+ export function normalizeDependencyInput(input) {
383
+ const arr = Array.isArray(input) ? input : [input];
384
+ const layers = normalizeDependencies(arr);
385
+ return mergeLayers(layers);
386
+ }
387
+ function getGuard(guard) {
388
+ return "asGuard" in guard ? guard.asGuard() : guard;
389
+ }
390
+ function defaultGuard() {
391
+ return Effect.succeedSome;
392
+ }
393
+ function mergeLayers(layers) {
394
+ if (layers.length === 0)
395
+ return Layer.empty;
396
+ if (layers.length === 1)
397
+ return layers[0];
398
+ let current = layers[0];
399
+ for (let i = 1; i < layers.length; i++) {
400
+ current = Layer.merge(current, layers[i]);
401
+ }
402
+ return current;
403
+ }
404
+ /**
405
+ * @internal
406
+ */
407
+ export function compile(cases) {
408
+ const entries = [];
409
+ const visit = (matches, context) => {
410
+ for (const match of matches) {
411
+ switch (match.type) {
412
+ case "route": {
413
+ const baseRoute = makeRoute(match.route);
414
+ const prefixedRoute = applyPrefixes(baseRoute, context.prefixes);
415
+ entries.push({
416
+ route: prefixedRoute,
417
+ guard: getGuard(match.guard),
418
+ handler: normalizeHandler(match.handler),
419
+ layers: context.layers,
420
+ layouts: context.layouts,
421
+ catches: context.catches,
422
+ decode: Schema.decodeUnknownEffect(prefixedRoute.paramsSchema),
423
+ });
424
+ break;
425
+ }
426
+ case "layer": {
427
+ const merged = mergeLayers(match.deps);
428
+ visit(match.matches, {
429
+ ...context,
430
+ layers: [...context.layers, merged],
431
+ });
432
+ break;
433
+ }
434
+ case "layout": {
435
+ visit(match.matches, {
436
+ ...context,
437
+ layouts: [...context.layouts, match.layout],
438
+ });
439
+ break;
440
+ }
441
+ case "prefixed": {
442
+ visit(match.matches, {
443
+ ...context,
444
+ prefixes: [...context.prefixes, match.prefix],
445
+ });
446
+ break;
447
+ }
448
+ case "catch": {
449
+ visit(match.matches, {
450
+ ...context,
451
+ catches: [...context.catches, match.f],
452
+ });
453
+ break;
454
+ }
455
+ }
456
+ }
457
+ };
458
+ visit(cases, { layers: [], layouts: [], catches: [], prefixes: [] });
459
+ return entries;
460
+ }
461
+ function applyPrefixes(route, prefixes) {
462
+ if (prefixes.length === 0)
463
+ return route;
464
+ const prefixRoutes = prefixes.map((prefix) => makeRoute(prefix));
465
+ return Join(...prefixRoutes, route);
466
+ }
467
+ // Parallel scope cleanup helper
468
+ const closeScopes = (scopes, fiberId) => Effect.forEach(scopes, (scope) => Scope.close(scope, interrupt(fiberId)), {
469
+ concurrency: "unbounded",
470
+ discard: true,
471
+ });
472
+ /**
473
+ * @internal
474
+ */
475
+ export function makeLayerManager(memoMap, rootScope, fiberId) {
476
+ const states = new Map();
477
+ let order = [];
478
+ let cachedDesiredSet = undefined;
479
+ let cachedOrder = undefined;
480
+ const prepare = (desired) => Effect.gen(function* () {
481
+ const desiredSet = cachedOrder === desired
482
+ ? cachedDesiredSet
483
+ : ((cachedDesiredSet = new Set(desired)), (cachedOrder = desired), cachedDesiredSet);
484
+ const removed = order.filter((layer) => !desiredSet.has(layer));
485
+ const added = [];
486
+ let services = ServiceMap.empty();
487
+ for (const layer of desired) {
488
+ const existing = states.get(layer);
489
+ if (existing) {
490
+ services = ServiceMap.merge(services, existing.services);
491
+ continue;
492
+ }
493
+ const scope = yield* Scope.fork(rootScope);
494
+ const buildExit = yield* Layer.buildWithMemoMap(layer, memoMap, scope).pipe(Effect.provideServices(services), Effect.exit);
495
+ if (Exit.isFailure(buildExit)) {
496
+ for (let i = added.length - 1; i >= 0; i--) {
497
+ const addedLayer = added[i];
498
+ const addedState = states.get(addedLayer);
499
+ if (addedState) {
500
+ states.delete(addedLayer);
501
+ yield* Scope.close(addedState.scope, interrupt(fiberId));
502
+ }
503
+ }
504
+ yield* Scope.close(scope, buildExit);
505
+ return yield* Effect.failCause(buildExit.cause);
506
+ }
507
+ const servicesForLayer = buildExit.value;
508
+ services = ServiceMap.merge(services, servicesForLayer);
509
+ states.set(layer, { scope, services: servicesForLayer });
510
+ added.push(layer);
511
+ }
512
+ const commit = Effect.gen(function* () {
513
+ for (let i = removed.length - 1; i >= 0; i--) {
514
+ const layer = removed[i];
515
+ const state = states.get(layer);
516
+ if (state) {
517
+ states.delete(layer);
518
+ yield* Scope.close(state.scope, interrupt(fiberId));
519
+ }
520
+ }
521
+ order = desired;
522
+ });
523
+ const rollback = Effect.gen(function* () {
524
+ for (let i = added.length - 1; i >= 0; i--) {
525
+ const layer = added[i];
526
+ const state = states.get(layer);
527
+ if (state) {
528
+ states.delete(layer);
529
+ yield* Scope.close(state.scope, interrupt(fiberId));
530
+ }
531
+ }
532
+ });
533
+ return { services, commit, rollback };
534
+ });
535
+ return { prepare };
536
+ }
537
+ /**
538
+ * @internal
539
+ */
540
+ export function makeLayoutManager(rootScope, fiberId) {
541
+ const states = new Map();
542
+ let active = [];
543
+ const removeUnused = (layouts) => Effect.gen(function* () {
544
+ const next = new Set(layouts);
545
+ const removed = active.filter((layout) => !next.has(layout));
546
+ const scopes = removed.map((layout) => {
547
+ const state = states.get(layout);
548
+ states.delete(layout);
549
+ return state.scope;
550
+ });
551
+ yield* closeScopes(scopes, fiberId);
552
+ active = layouts;
553
+ });
554
+ const apply = (layouts, paramsValue, inner, services) => Effect.gen(function* () {
555
+ let current = inner;
556
+ for (let i = layouts.length - 1; i >= 0; i--) {
557
+ const layout = layouts[i];
558
+ const state = states.get(layout);
559
+ if (state === undefined) {
560
+ const scope = yield* Scope.fork(rootScope);
561
+ const params = yield* RefSubject.make(paramsValue).pipe(Scope.provide(scope));
562
+ const content = yield* RefSubject.make(Effect.succeed(current), {
563
+ eq: (left, right) => left === right,
564
+ }).pipe(Scope.provide(scope));
565
+ const fx = layout({ params, content: content.pipe(switchMap(identity)) }).pipe(provideServices(ServiceMap.merge(services, ServiceMap.make(Scope.Scope, scope))));
566
+ states.set(layout, { params, content, fx, scope });
567
+ current = fx;
568
+ }
569
+ else {
570
+ yield* RefSubject.set(state.params, paramsValue);
571
+ // @effect-diagnostics-next-line floatingEffect:off
572
+ yield* RefSubject.set(state.content, current);
573
+ current = state.fx;
574
+ }
575
+ }
576
+ yield* removeUnused(layouts);
577
+ return current;
578
+ });
579
+ const updateParams = (layouts, paramsValue) => Effect.forEach(layouts, (layout) => {
580
+ const state = states.get(layout);
581
+ return state !== undefined ? RefSubject.set(state.params, paramsValue) : Effect.void;
582
+ }, { discard: true });
583
+ return { apply, updateParams };
584
+ }
585
+ /**
586
+ * @internal
587
+ */
588
+ export function makeCatchManager(rootScope, fiberId) {
589
+ const states = new Map();
590
+ let active = [];
591
+ const removeUnused = (catches) => Effect.gen(function* () {
592
+ const next = new Set(catches);
593
+ const removed = active.filter((c) => !next.has(c));
594
+ const scopes = removed.map((c) => {
595
+ const state = states.get(c);
596
+ states.delete(c);
597
+ return state.scope;
598
+ });
599
+ yield* closeScopes(scopes, fiberId);
600
+ active = catches;
601
+ });
602
+ const apply = (catches, inner, services) => Effect.gen(function* () {
603
+ let current = inner;
604
+ for (let i = catches.length - 1; i >= 0; i--) {
605
+ const catcher = catches[i];
606
+ const state = states.get(catcher);
607
+ if (state === undefined) {
608
+ const scope = yield* Scope.fork(rootScope);
609
+ const causes = yield* RefSubject.make(Cause.fail(undefined)).pipe(Scope.provide(scope));
610
+ const content = yield* RefSubject.make(Effect.succeed(current), {
611
+ eq: (left, right) => left === right,
612
+ }).pipe(Scope.provide(scope));
613
+ const fallback = catcher(causes).pipe(provideServices(ServiceMap.merge(services, ServiceMap.make(Scope.Scope, scope))));
614
+ const fx = content.pipe(switchMap(identity), exit, mapEffect(Effect.fn(function* (e) {
615
+ if (isSuccess(e))
616
+ return succeed(e.value);
617
+ yield* RefSubject.set(causes, e.cause);
618
+ return fallback;
619
+ })), skipRepeats, switchMap(identity));
620
+ states.set(catcher, { causes, content, fx, scope });
621
+ current = fx;
622
+ }
623
+ else {
624
+ // @effect-diagnostics-next-line floatingEffect:off
625
+ yield* RefSubject.set(state.content, current);
626
+ current = state.fx;
627
+ }
628
+ }
629
+ yield* removeUnused(catches);
630
+ return current;
631
+ });
632
+ return { apply };
633
+ }