@playfast/reform-resource 0.0.3 → 0.0.4

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@playfast/reform-resource",
3
3
  "playbook": "./playbook",
4
- "version": "0.0.3",
4
+ "version": "0.0.4",
5
5
  "type": "module",
6
6
  "description": "Async resources for Reform — a layer that builds synchronously, loads in the background, and flips a reactive store from pending to ready/failed. The sanctioned home for async work that would otherwise break reform's synchronous scene build.",
7
7
  "keywords": [
@@ -28,7 +28,7 @@
28
28
  "./*": "./src/*.ts"
29
29
  },
30
30
  "files": [
31
- "dist",
31
+ "src",
32
32
  "README.md"
33
33
  ],
34
34
  "scripts": {
@@ -0,0 +1,80 @@
1
+ import { type Store } from '@playfast/reform'
2
+
3
+ // The value an `AsyncResource` holds: a tagged union discriminated by `isReady`.
4
+ // A resource builds synchronously as `pending`, its load runs in the background,
5
+ // and the store flips to `ready` (or `failed`, only when an `error` schema is
6
+ // declared) when it settles. Consumed with a plain `if`:
7
+ //
8
+ // const r = yield* MyResource
9
+ // if (r.isReady) use(r.value)
10
+ // else if (r.failed) handle(r.error) // only present when E is not `never`
11
+ //
12
+ // `_tag` is the constant `'AsyncResource'` (so the value reads well in logs and a
13
+ // `Match.tag` still groups it); `isReady` is the real discriminant.
14
+
15
+ /** The initial state: the load has not settled yet. */
16
+ export interface AsyncResourcePending {
17
+ readonly _tag: 'AsyncResource'
18
+ readonly isReady: false
19
+ readonly failed: false
20
+ }
21
+
22
+ /** The load resolved; `value` is the acquired resource. */
23
+ export interface AsyncResourceReady<A> {
24
+ readonly _tag: 'AsyncResource'
25
+ readonly isReady: true
26
+ readonly value: A
27
+ }
28
+
29
+ /** The load failed with the declared error. Only occurs when `E` is not `never`. */
30
+ export interface AsyncResourceFailed<E> {
31
+ readonly _tag: 'AsyncResource'
32
+ readonly isReady: false
33
+ readonly failed: true
34
+ readonly error: E
35
+ }
36
+
37
+ /**
38
+ * The resource lifecycle, narrowed by the definition: the `Failed` arm exists
39
+ * only when the resource declares an `error` schema (a non-`never` `E`), so a
40
+ * resource with no error schema is exactly the two-arm `pending | ready` shape.
41
+ */
42
+ export type AsyncResource<A, E = never> =
43
+ | AsyncResourcePending
44
+ | AsyncResourceReady<A>
45
+ | ([E] extends [never] ? never : AsyncResourceFailed<E>)
46
+
47
+ /** The full (un-narrowed) union — held by the live store, narrowed at the read boundary. */
48
+ export type AnyAsyncResource<A, E> = AsyncResourcePending | AsyncResourceReady<A> | AsyncResourceFailed<E>
49
+
50
+ const pending: AsyncResourcePending = { _tag: 'AsyncResource', isReady: false, failed: false }
51
+ const ready = <A>(value: A): AsyncResourceReady<A> => ({ _tag: 'AsyncResource', isReady: true, value })
52
+ const failed = <E>(error: E): AsyncResourceFailed<E> => ({
53
+ _tag: 'AsyncResource',
54
+ isReady: false,
55
+ failed: true,
56
+ error,
57
+ })
58
+
59
+ /** The arm-constructor namespace exposed as `AsyncResource`. */
60
+ export interface AsyncResourceConstructors {
61
+ readonly pending: AsyncResourcePending
62
+ readonly ready: <A>(value: A) => AsyncResourceReady<A>
63
+ readonly failed: <E>(error: E) => AsyncResourceFailed<E>
64
+ }
65
+
66
+ /**
67
+ * Constructors for the arms, namespaced under the same name as the type so call
68
+ * sites read `AsyncResource.ready(v)` / `AsyncResource.failed(e)` — one obvious
69
+ * home for every arm (mirrors `AsyncData` in reform core).
70
+ */
71
+ export const AsyncResource: AsyncResourceConstructors = { pending, ready, failed }
72
+
73
+ /**
74
+ * Narrow the live store (which holds the full `AnyAsyncResource` union) to the
75
+ * arms the definition permits (drops `Failed` when `E` is `never`). The single
76
+ * documented home for that narrowing, so `Resource.live` returns it without an
77
+ * inline cast — the established `asyncData.narrowStore` pattern.
78
+ */
79
+ export const narrowStore = <A, E>(store: Store<AnyAsyncResource<A, E>>): Store<AsyncResource<A, E>> =>
80
+ store as unknown as Store<AsyncResource<A, E>>
@@ -4,6 +4,14 @@
4
4
  // synchronous scene build), forks an async load under the scene scope, and flips
5
5
  // a reactive store from `pending` to `ready`/`failed` when it settles — the
6
6
  // sanctioned home for the async work that otherwise trips `AsyncSceneLayer`.
7
- export * as Resource from './resource';
8
- export { AsyncResource, } from './asyncResource';
9
- //# sourceMappingURL=index.js.map
7
+
8
+ export * as Resource from './resource'
9
+ export type { ResourceClass, ResourceConfig, ResourceManifest } from './resource'
10
+ export {
11
+ type AnyAsyncResource,
12
+ AsyncResource,
13
+ type AsyncResourceConstructors,
14
+ type AsyncResourceFailed,
15
+ type AsyncResourcePending,
16
+ type AsyncResourceReady,
17
+ } from './asyncResource'
@@ -0,0 +1,79 @@
1
+ import { expect, it } from '@effect/vitest'
2
+ import { Context, Data, Duration, Effect, Layer, Ref, Schema as S } from 'effect'
3
+ import { Resource } from './index'
4
+
5
+ // A Resource builds synchronously as `pending`, forks its load under the scene
6
+ // scope, and flips a reactive store to `ready`/`failed` when it settles. Scoped
7
+ // acquire/release ties to the scene scope.
8
+
9
+ const tick = (ms = 30) => Effect.sleep(Duration.millis(ms))
10
+
11
+ class Boom extends Data.TaggedError('Boom')<{ readonly message: string }> {}
12
+
13
+ it.live('builds pending then resolves to ready', () => {
14
+ class Num extends Resource.make('Num', { output: S.Number }) {}
15
+ const layer = Resource.live(Num, Effect.delay(Effect.succeed(7), Duration.millis(20)))
16
+
17
+ return Effect.gen(function* () {
18
+ const store = yield* Num.store
19
+ // Synchronous build → the value is available immediately as pending.
20
+ expect(store.get().isReady).toBe(false)
21
+ yield* tick(60)
22
+ const value = store.get()
23
+ expect(value.isReady).toBe(true)
24
+ if (value.isReady) expect(value.value).toBe(7)
25
+ }).pipe(Effect.provide(layer))
26
+ })
27
+
28
+ it.live('a failing load resolves to the failed arm carrying the error', () => {
29
+ class Conn extends Resource.make('Conn', { output: S.Number, error: S.instanceOf(Boom) }) {}
30
+ const layer = Resource.live(Conn, Effect.fail(new Boom({ message: 'nope' })))
31
+
32
+ return Effect.gen(function* () {
33
+ const store = yield* Conn.store
34
+ yield* tick()
35
+ const value = store.get()
36
+ expect(value.isReady).toBe(false)
37
+ if (!value.isReady && value.failed) expect(value.error.message).toBe('nope')
38
+ }).pipe(Effect.provide(layer))
39
+ })
40
+
41
+ it.live('notifies subscribers when it flips to ready', () => {
42
+ class Num extends Resource.make('Num', { output: S.Number }) {}
43
+ const layer = Resource.live(Num, Effect.succeed(1))
44
+
45
+ return Effect.gen(function* () {
46
+ const store = yield* Num.store
47
+ const seen = { count: 0 }
48
+ const unsubscribe = store.subscribe(() => {
49
+ seen.count += 1
50
+ })
51
+ yield* tick()
52
+ unsubscribe()
53
+ expect(store.get().isReady).toBe(true)
54
+ expect(seen.count).toBeGreaterThan(0)
55
+ }).pipe(Effect.provide(layer))
56
+ })
57
+
58
+ it.live('ties acquire/release to the scene scope', () =>
59
+ Effect.gen(function* () {
60
+ const released = yield* Ref.make(false)
61
+ class Conn extends Resource.make('Conn', { output: S.Number }) {}
62
+ const layer = Resource.live(
63
+ Conn,
64
+ Effect.acquireRelease(Effect.succeed(1), () => Ref.set(released, true)),
65
+ )
66
+
67
+ yield* Effect.scoped(
68
+ Effect.gen(function* () {
69
+ const context = yield* Layer.build(layer)
70
+ const store = Context.get(context, Conn.store)
71
+ yield* tick()
72
+ expect(store.get().isReady).toBe(true)
73
+ // Still open while the scene scope is alive.
74
+ expect(yield* Ref.get(released)).toBe(false)
75
+ }),
76
+ )
77
+ // Scene scope closed → the acquire's finalizer ran.
78
+ expect(yield* Ref.get(released)).toBe(true)
79
+ }))
@@ -0,0 +1,110 @@
1
+ import {
2
+ type Manifest,
3
+ makeStore,
4
+ readTracked,
5
+ resolveScheduler,
6
+ type Store,
7
+ yieldableClass,
8
+ } from '@playfast/reform'
9
+ import { Cause, Context, Effect, Layer, Option, type Schema, Scope } from 'effect'
10
+ import {
11
+ type AnyAsyncResource,
12
+ AsyncResource,
13
+ narrowStore,
14
+ } from './asyncResource'
15
+
16
+ // `Resource` is the sanctioned home for async work that would otherwise run during
17
+ // reform's synchronous layer build (and trip `AsyncSceneLayer`). Its `live` layer
18
+ // builds synchronously — seeding a `pending` store — then forks the load under the
19
+ // scene scope and flips the store to `ready`/`failed` when it settles. The flip is
20
+ // an ordinary `Store.set`, so a render that read the resource (via `readTracked`)
21
+ // re-runs just like for any state/calc. Unlike `AsyncCalc` it has no reactive
22
+ // inputs and no query driver: a one-shot, scoped load.
23
+
24
+ export interface ResourceManifest<N extends string, A, E> extends Manifest {
25
+ readonly kind: 'Resource'
26
+ readonly name: N
27
+ readonly output: Schema.Schema<A, any>
28
+ /** Schema of the failure. Omitted ⇒ the load is infallible and there is no `Failed` arm. */
29
+ readonly error?: Schema.Schema<E, any>
30
+ }
31
+
32
+ export interface ResourceClass<out N extends string, in out A, in out E>
33
+ extends Effect.Effect<AsyncResource<A, E>, never, Store<AsyncResource<A, E>>> {
34
+ new (): {}
35
+ readonly manifest: ResourceManifest<N, A, E>
36
+ readonly store: Context.Tag<Store<AsyncResource<A, E>>, Store<AsyncResource<A, E>>>
37
+ /** The resource's name. */
38
+ readonly name: N
39
+ }
40
+
41
+ export interface ResourceConfig<A, E> {
42
+ /** Schema of the ready value. */
43
+ readonly output: Schema.Schema<A, any>
44
+ /** Schema of the failure. Omitted ⇒ infallible: no `Failed` arm in the value type. */
45
+ readonly error?: Schema.Schema<E, any>
46
+ }
47
+
48
+ /**
49
+ * Define an async resource. `output`/`error` schemas shape the value type, so
50
+ * `yield* MyResource` is typed to exactly the arms that can occur (no `Failed` arm
51
+ * without an `error` schema). The load effect itself is supplied by `Resource.live`.
52
+ */
53
+ export const make = <const N extends string, A, E = never>(
54
+ name: N,
55
+ config: ResourceConfig<A, E>,
56
+ ): ResourceClass<N, A, E> => {
57
+ const store = Context.GenericTag<Store<AsyncResource<A, E>>>(`reform/resource/${name}`)
58
+ const manifest: ResourceManifest<N, A, E> = {
59
+ kind: 'Resource',
60
+ name,
61
+ output: config.output,
62
+ ...(config.error !== undefined ? { error: config.error } : {}),
63
+ }
64
+ const read = Effect.flatMap(store, readTracked)
65
+ return yieldableClass(read, { manifest, store, name })
66
+ }
67
+
68
+ /**
69
+ * Wire the load for a resource. The layer builds synchronously (the store starts
70
+ * `pending`), then forks `acquire` and flips the store to `ready` on success or
71
+ * `failed` on a declared failure. `acquire` may be scoped: its `acquireRelease`
72
+ * finalizers attach to the scene/layer scope (via `Scope.extend`), so an opened
73
+ * connection stays alive while the resource is mounted and releases on dispose.
74
+ * A defect (an unexpected throw) is logged and leaves the resource `pending`,
75
+ * rather than vanishing silently.
76
+ */
77
+ export const live = <N extends string, A, E, R>(
78
+ resource: ResourceClass<N, A, E>,
79
+ acquire: Effect.Effect<A, E, R | Scope.Scope>,
80
+ ): Layer.Layer<Store<AsyncResource<A, E>>, never, Exclude<R, Scope.Scope>> =>
81
+ Layer.scoped(
82
+ resource.store,
83
+ Effect.gen(function* () {
84
+ const scheduler = yield* resolveScheduler
85
+ const scope = yield* Effect.scope
86
+ // The store holds the full union; the tag (and `yield* Resource`) see the
87
+ // definition-narrowed arms. Built `pending` so the sync layer build returns
88
+ // immediately and never trips `AsyncSceneLayer`.
89
+ const store = makeStore<AnyAsyncResource<A, E>>(AsyncResource.pending, scheduler)
90
+ yield* Effect.forkScoped(
91
+ acquire.pipe(
92
+ // Finalizers in `acquire` attach to the scene scope, not the load fiber:
93
+ // a resource opened here lives until the scene unmounts.
94
+ Scope.extend(scope),
95
+ Effect.matchCauseEffect({
96
+ onSuccess: (value) => Effect.sync(() => store.set(AsyncResource.ready(value))),
97
+ onFailure: (cause) =>
98
+ Option.match(Cause.failureOption(cause), {
99
+ onSome: (error) => Effect.sync(() => store.set(AsyncResource.failed(error))),
100
+ onNone: () =>
101
+ Cause.isInterruptedOnly(cause)
102
+ ? Effect.void
103
+ : Effect.logError(`reform-resource: load for '${resource.name}' failed`, cause),
104
+ }),
105
+ }),
106
+ ),
107
+ )
108
+ return narrowStore(store)
109
+ }),
110
+ )
@@ -1,48 +0,0 @@
1
- import { type Store } from '@playfast/reform';
2
- /** The initial state: the load has not settled yet. */
3
- export interface AsyncResourcePending {
4
- readonly _tag: 'AsyncResource';
5
- readonly isReady: false;
6
- readonly failed: false;
7
- }
8
- /** The load resolved; `value` is the acquired resource. */
9
- export interface AsyncResourceReady<A> {
10
- readonly _tag: 'AsyncResource';
11
- readonly isReady: true;
12
- readonly value: A;
13
- }
14
- /** The load failed with the declared error. Only occurs when `E` is not `never`. */
15
- export interface AsyncResourceFailed<E> {
16
- readonly _tag: 'AsyncResource';
17
- readonly isReady: false;
18
- readonly failed: true;
19
- readonly error: E;
20
- }
21
- /**
22
- * The resource lifecycle, narrowed by the definition: the `Failed` arm exists
23
- * only when the resource declares an `error` schema (a non-`never` `E`), so a
24
- * resource with no error schema is exactly the two-arm `pending | ready` shape.
25
- */
26
- export type AsyncResource<A, E = never> = AsyncResourcePending | AsyncResourceReady<A> | ([E] extends [never] ? never : AsyncResourceFailed<E>);
27
- /** The full (un-narrowed) union — held by the live store, narrowed at the read boundary. */
28
- export type AnyAsyncResource<A, E> = AsyncResourcePending | AsyncResourceReady<A> | AsyncResourceFailed<E>;
29
- /** The arm-constructor namespace exposed as `AsyncResource`. */
30
- export interface AsyncResourceConstructors {
31
- readonly pending: AsyncResourcePending;
32
- readonly ready: <A>(value: A) => AsyncResourceReady<A>;
33
- readonly failed: <E>(error: E) => AsyncResourceFailed<E>;
34
- }
35
- /**
36
- * Constructors for the arms, namespaced under the same name as the type so call
37
- * sites read `AsyncResource.ready(v)` / `AsyncResource.failed(e)` — one obvious
38
- * home for every arm (mirrors `AsyncData` in reform core).
39
- */
40
- export declare const AsyncResource: AsyncResourceConstructors;
41
- /**
42
- * Narrow the live store (which holds the full `AnyAsyncResource` union) to the
43
- * arms the definition permits (drops `Failed` when `E` is `never`). The single
44
- * documented home for that narrowing, so `Resource.live` returns it without an
45
- * inline cast — the established `asyncData.narrowStore` pattern.
46
- */
47
- export declare const narrowStore: <A, E>(store: Store<AnyAsyncResource<A, E>>) => Store<AsyncResource<A, E>>;
48
- //# sourceMappingURL=asyncResource.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"asyncResource.d.ts","sourceRoot":"","sources":["../src/asyncResource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAc7C,uDAAuD;AACvD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAA;IAC9B,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAA;CACvB;AAED,2DAA2D;AAC3D,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAA;IAC9B,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAClB;AAED,oFAAoF;AACpF,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAA;IAC9B,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAClB;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,IAClC,oBAAoB,GACpB,kBAAkB,CAAC,CAAC,CAAC,GACrB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAA;AAE1D,4FAA4F;AAC5F,MAAM,MAAM,gBAAgB,CAAC,CAAC,EAAE,CAAC,IAAI,oBAAoB,GAAG,kBAAkB,CAAC,CAAC,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAA;AAW1G,gEAAgE;AAChE,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAA;IACtC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,kBAAkB,CAAC,CAAC,CAAC,CAAA;IACtD,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,mBAAmB,CAAC,CAAC,CAAC,CAAA;CACzD;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,yBAAsD,CAAA;AAElF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,CAAC,EAAE,CAAC,EAAE,OAAO,KAAK,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAG,KAAK,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAClD,CAAA"}
@@ -1,23 +0,0 @@
1
- import {} from '@playfast/reform';
2
- const pending = { _tag: 'AsyncResource', isReady: false, failed: false };
3
- const ready = (value) => ({ _tag: 'AsyncResource', isReady: true, value });
4
- const failed = (error) => ({
5
- _tag: 'AsyncResource',
6
- isReady: false,
7
- failed: true,
8
- error,
9
- });
10
- /**
11
- * Constructors for the arms, namespaced under the same name as the type so call
12
- * sites read `AsyncResource.ready(v)` / `AsyncResource.failed(e)` — one obvious
13
- * home for every arm (mirrors `AsyncData` in reform core).
14
- */
15
- export const AsyncResource = { pending, ready, failed };
16
- /**
17
- * Narrow the live store (which holds the full `AnyAsyncResource` union) to the
18
- * arms the definition permits (drops `Failed` when `E` is `never`). The single
19
- * documented home for that narrowing, so `Resource.live` returns it without an
20
- * inline cast — the established `asyncData.narrowStore` pattern.
21
- */
22
- export const narrowStore = (store) => store;
23
- //# sourceMappingURL=asyncResource.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"asyncResource.js","sourceRoot":"","sources":["../src/asyncResource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,MAAM,kBAAkB,CAAA;AAiD7C,MAAM,OAAO,GAAyB,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;AAC9F,MAAM,KAAK,GAAG,CAAI,KAAQ,EAAyB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;AACvG,MAAM,MAAM,GAAG,CAAI,KAAQ,EAA0B,EAAE,CAAC,CAAC;IACvD,IAAI,EAAE,eAAe;IACrB,OAAO,EAAE,KAAK;IACd,MAAM,EAAE,IAAI;IACZ,KAAK;CACN,CAAC,CAAA;AASF;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAA8B,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AAElF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAO,KAAoC,EAA8B,EAAE,CACpG,KAA8C,CAAA"}
package/dist/index.d.ts DELETED
@@ -1,4 +0,0 @@
1
- export * as Resource from './resource';
2
- export type { ResourceClass, ResourceConfig, ResourceManifest } from './resource';
3
- export { type AnyAsyncResource, AsyncResource, type AsyncResourceConstructors, type AsyncResourceFailed, type AsyncResourcePending, type AsyncResourceReady, } from './asyncResource';
4
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAA;AACtC,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AACjF,OAAO,EACL,KAAK,gBAAgB,EACrB,aAAa,EACb,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,GACxB,MAAM,iBAAiB,CAAA"}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,EAAE;AACF,8EAA8E;AAC9E,iFAAiF;AACjF,4EAA4E;AAC5E,6EAA6E;AAE7E,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAA;AAEtC,OAAO,EAEL,aAAa,GAKd,MAAM,iBAAiB,CAAA"}
@@ -1,40 +0,0 @@
1
- import { type Manifest, type Store } from '@playfast/reform';
2
- import { Context, Effect, Layer, type Schema, Scope } from 'effect';
3
- import { AsyncResource } from './asyncResource';
4
- export interface ResourceManifest<N extends string, A, E> extends Manifest {
5
- readonly kind: 'Resource';
6
- readonly name: N;
7
- readonly output: Schema.Schema<A, any>;
8
- /** Schema of the failure. Omitted ⇒ the load is infallible and there is no `Failed` arm. */
9
- readonly error?: Schema.Schema<E, any>;
10
- }
11
- export interface ResourceClass<out N extends string, in out A, in out E> extends Effect.Effect<AsyncResource<A, E>, never, Store<AsyncResource<A, E>>> {
12
- new (): {};
13
- readonly manifest: ResourceManifest<N, A, E>;
14
- readonly store: Context.Tag<Store<AsyncResource<A, E>>, Store<AsyncResource<A, E>>>;
15
- /** The resource's name. */
16
- readonly name: N;
17
- }
18
- export interface ResourceConfig<A, E> {
19
- /** Schema of the ready value. */
20
- readonly output: Schema.Schema<A, any>;
21
- /** Schema of the failure. Omitted ⇒ infallible: no `Failed` arm in the value type. */
22
- readonly error?: Schema.Schema<E, any>;
23
- }
24
- /**
25
- * Define an async resource. `output`/`error` schemas shape the value type, so
26
- * `yield* MyResource` is typed to exactly the arms that can occur (no `Failed` arm
27
- * without an `error` schema). The load effect itself is supplied by `Resource.live`.
28
- */
29
- export declare const make: <const N extends string, A, E = never>(name: N, config: ResourceConfig<A, E>) => ResourceClass<N, A, E>;
30
- /**
31
- * Wire the load for a resource. The layer builds synchronously (the store starts
32
- * `pending`), then forks `acquire` and flips the store to `ready` on success or
33
- * `failed` on a declared failure. `acquire` may be scoped: its `acquireRelease`
34
- * finalizers attach to the scene/layer scope (via `Scope.extend`), so an opened
35
- * connection stays alive while the resource is mounted and releases on dispose.
36
- * A defect (an unexpected throw) is logged and leaves the resource `pending`,
37
- * rather than vanishing silently.
38
- */
39
- export declare const live: <N extends string, A, E, R>(resource: ResourceClass<N, A, E>, acquire: Effect.Effect<A, E, R | Scope.Scope>) => Layer.Layer<Store<AsyncResource<A, E>>, never, Exclude<R, Scope.Scope>>;
40
- //# sourceMappingURL=resource.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EAIb,KAAK,KAAK,EAEX,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAS,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,KAAK,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAClF,OAAO,EAEL,aAAa,EAEd,MAAM,iBAAiB,CAAA;AAUxB,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,CAAE,SAAQ,QAAQ;IACxE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;IAChB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IACtC,4FAA4F;IAC5F,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;CACvC;AAED,MAAM,WAAW,aAAa,CAAC,GAAG,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CACrE,SAAQ,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7E,QAAQ,EAAE,CAAA;IACV,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IAC5C,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IACnF,2BAA2B;IAC3B,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;CACjB;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,EAAE,CAAC;IAClC,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IACtC,sFAAsF;IACtF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;CACvC;AAED;;;;GAIG;AACH,eAAO,MAAM,IAAI,GAAI,KAAK,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,GAAG,KAAK,EACvD,MAAM,CAAC,EACP,QAAQ,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,KAC3B,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAUvB,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,IAAI,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAC5C,UAAU,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAChC,SAAS,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,KAC5C,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CA8BtE,CAAA"}
package/dist/resource.js DELETED
@@ -1,50 +0,0 @@
1
- import { makeStore, readTracked, resolveScheduler, yieldableClass, } from '@playfast/reform';
2
- import { Cause, Context, Effect, Layer, Option, Scope } from 'effect';
3
- import { AsyncResource, narrowStore, } from './asyncResource';
4
- /**
5
- * Define an async resource. `output`/`error` schemas shape the value type, so
6
- * `yield* MyResource` is typed to exactly the arms that can occur (no `Failed` arm
7
- * without an `error` schema). The load effect itself is supplied by `Resource.live`.
8
- */
9
- export const make = (name, config) => {
10
- const store = Context.GenericTag(`reform/resource/${name}`);
11
- const manifest = {
12
- kind: 'Resource',
13
- name,
14
- output: config.output,
15
- ...(config.error !== undefined ? { error: config.error } : {}),
16
- };
17
- const read = Effect.flatMap(store, readTracked);
18
- return yieldableClass(read, { manifest, store, name });
19
- };
20
- /**
21
- * Wire the load for a resource. The layer builds synchronously (the store starts
22
- * `pending`), then forks `acquire` and flips the store to `ready` on success or
23
- * `failed` on a declared failure. `acquire` may be scoped: its `acquireRelease`
24
- * finalizers attach to the scene/layer scope (via `Scope.extend`), so an opened
25
- * connection stays alive while the resource is mounted and releases on dispose.
26
- * A defect (an unexpected throw) is logged and leaves the resource `pending`,
27
- * rather than vanishing silently.
28
- */
29
- export const live = (resource, acquire) => Layer.scoped(resource.store, Effect.gen(function* () {
30
- const scheduler = yield* resolveScheduler;
31
- const scope = yield* Effect.scope;
32
- // The store holds the full union; the tag (and `yield* Resource`) see the
33
- // definition-narrowed arms. Built `pending` so the sync layer build returns
34
- // immediately and never trips `AsyncSceneLayer`.
35
- const store = makeStore(AsyncResource.pending, scheduler);
36
- yield* Effect.forkScoped(acquire.pipe(
37
- // Finalizers in `acquire` attach to the scene scope, not the load fiber:
38
- // a resource opened here lives until the scene unmounts.
39
- Scope.extend(scope), Effect.matchCauseEffect({
40
- onSuccess: (value) => Effect.sync(() => store.set(AsyncResource.ready(value))),
41
- onFailure: (cause) => Option.match(Cause.failureOption(cause), {
42
- onSome: (error) => Effect.sync(() => store.set(AsyncResource.failed(error))),
43
- onNone: () => Cause.isInterruptedOnly(cause)
44
- ? Effect.void
45
- : Effect.logError(`reform-resource: load for '${resource.name}' failed`, cause),
46
- }),
47
- })));
48
- return narrowStore(store);
49
- }));
50
- //# sourceMappingURL=resource.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,SAAS,EACT,WAAW,EACX,gBAAgB,EAEhB,cAAc,GACf,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAe,KAAK,EAAE,MAAM,QAAQ,CAAA;AAClF,OAAO,EAEL,aAAa,EACb,WAAW,GACZ,MAAM,iBAAiB,CAAA;AAkCxB;;;;GAIG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAClB,IAAO,EACP,MAA4B,EACJ,EAAE;IAC1B,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,CAA6B,mBAAmB,IAAI,EAAE,CAAC,CAAA;IACvF,MAAM,QAAQ,GAA8B;QAC1C,IAAI,EAAE,UAAU;QAChB,IAAI;QACJ,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC/D,CAAA;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,WAAW,CAAC,CAAA;IAC/C,OAAO,cAAc,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AACxD,CAAC,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAClB,QAAgC,EAChC,OAA6C,EAC4B,EAAE,CAC3E,KAAK,CAAC,MAAM,CACV,QAAQ,CAAC,KAAK,EACd,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,gBAAgB,CAAA;IACzC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAA;IACjC,0EAA0E;IAC1E,4EAA4E;IAC5E,iDAAiD;IACjD,MAAM,KAAK,GAAG,SAAS,CAAyB,aAAa,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;IACjF,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CACtB,OAAO,CAAC,IAAI;IACV,yEAAyE;IACzE,yDAAyD;IACzD,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EACnB,MAAM,CAAC,gBAAgB,CAAC;QACtB,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9E,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CACnB,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE;YACvC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAC5E,MAAM,EAAE,GAAG,EAAE,CACX,KAAK,CAAC,iBAAiB,CAAC,KAAK,CAAC;gBAC5B,CAAC,CAAC,MAAM,CAAC,IAAI;gBACb,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,8BAA8B,QAAQ,CAAC,IAAI,UAAU,EAAE,KAAK,CAAC;SACpF,CAAC;KACL,CAAC,CACH,CACF,CAAA;IACD,OAAO,WAAW,CAAC,KAAK,CAAC,CAAA;AAC3B,CAAC,CAAC,CACH,CAAA"}