@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 +2 -2
- package/src/asyncResource.ts +80 -0
- package/{dist/index.js → src/index.ts} +11 -3
- package/src/reform-resource.test.ts +79 -0
- package/src/resource.ts +110 -0
- package/dist/asyncResource.d.ts +0 -48
- package/dist/asyncResource.d.ts.map +0 -1
- package/dist/asyncResource.js +0 -23
- package/dist/asyncResource.js.map +0 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/resource.d.ts +0 -40
- package/dist/resource.d.ts.map +0 -1
- package/dist/resource.js +0 -50
- package/dist/resource.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playfast/reform-resource",
|
|
3
3
|
"playbook": "./playbook",
|
|
4
|
-
"version": "0.0.
|
|
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
|
-
"
|
|
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
|
-
|
|
8
|
-
export
|
|
9
|
-
|
|
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
|
+
}))
|
package/src/resource.ts
ADDED
|
@@ -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
|
+
)
|
package/dist/asyncResource.d.ts
DELETED
|
@@ -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"}
|
package/dist/asyncResource.js
DELETED
|
@@ -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
|
package/dist/index.d.ts.map
DELETED
|
@@ -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"}
|
package/dist/resource.d.ts
DELETED
|
@@ -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
|
package/dist/resource.d.ts.map
DELETED
|
@@ -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
|
package/dist/resource.js.map
DELETED
|
@@ -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"}
|