@playfast/reform-resource 0.0.3
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 +52 -0
- package/dist/asyncResource.d.ts +48 -0
- package/dist/asyncResource.d.ts.map +1 -0
- package/dist/asyncResource.js +23 -0
- package/dist/asyncResource.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/resource.d.ts +40 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +50 -0
- package/dist/resource.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# @playfast/reform-resource
|
|
2
|
+
|
|
3
|
+
Async resources for [Reform](../reform).
|
|
4
|
+
|
|
5
|
+
Reform renders synchronously, so it builds a scene's whole layer graph with
|
|
6
|
+
`runSync`. A layer whose acquire does async work (open a connection, fetch config,
|
|
7
|
+
`Effect.sleep`) breaks that build — reform now reports it as `AsyncSceneLayer` and
|
|
8
|
+
points you here.
|
|
9
|
+
|
|
10
|
+
A `Resource` is the sanctioned home for that async work:
|
|
11
|
+
|
|
12
|
+
- its `live` layer **builds synchronously**, seeding a `pending` value;
|
|
13
|
+
- it **forks the load under the scene scope** (so `acquireRelease` finalizers
|
|
14
|
+
release when the scene unmounts);
|
|
15
|
+
- when the load settles it flips a reactive store to `ready` (or `failed`), which
|
|
16
|
+
re-renders any composition that read the resource — exactly like a state/calc.
|
|
17
|
+
|
|
18
|
+
Unlike `AsyncCalc`, a `Resource` has no reactive inputs and no query driver: it is
|
|
19
|
+
a one-shot, scoped load.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { Resource } from '@playfast/reform-resource'
|
|
25
|
+
import { Effect, Schema as S } from 'effect'
|
|
26
|
+
|
|
27
|
+
// Define — output schema shapes the ready value; add `error` for a failed arm.
|
|
28
|
+
class Config extends Resource.make('Config', { output: S.Struct({ url: S.String }) }) {}
|
|
29
|
+
|
|
30
|
+
// Wire — `acquire` may be scoped (acquire/release tied to the scene scope).
|
|
31
|
+
const ConfigLive = Resource.live(
|
|
32
|
+
Config,
|
|
33
|
+
Effect.acquireRelease(openConfig, closeConfig),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
// Read inside a composition.
|
|
37
|
+
const view = Effect.gen(function* () {
|
|
38
|
+
const config = yield* Config
|
|
39
|
+
return config.isReady ? render(config.value) : spinner()
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The value is a union discriminated by `isReady`:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
type AsyncResource<A, E = never> =
|
|
47
|
+
| { _tag: 'AsyncResource'; isReady: false; failed: false } // pending
|
|
48
|
+
| { _tag: 'AsyncResource'; isReady: true; value: A } // ready
|
|
49
|
+
| { _tag: 'AsyncResource'; isReady: false; failed: true; error: E } // only when E ≠ never
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The public API is re-exported from [the package index](./src/index.ts).
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @playfast/reform-resource — async resources for reform.
|
|
2
|
+
//
|
|
3
|
+
// A `Resource` is a layer that builds synchronously (so it's safe in reform's
|
|
4
|
+
// synchronous scene build), forks an async load under the scene scope, and flips
|
|
5
|
+
// a reactive store from `pending` to `ready`/`failed` when it settles — the
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@playfast/reform-resource",
|
|
3
|
+
"playbook": "./playbook",
|
|
4
|
+
"version": "0.0.3",
|
|
5
|
+
"type": "module",
|
|
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
|
+
"keywords": [
|
|
8
|
+
"reform",
|
|
9
|
+
"effect",
|
|
10
|
+
"async",
|
|
11
|
+
"resource",
|
|
12
|
+
"reactive",
|
|
13
|
+
"layer"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/playfast/reform.git",
|
|
19
|
+
"directory": "packages/reform-resource"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/playfast/reform/issues"
|
|
23
|
+
},
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"exports": {
|
|
26
|
+
"./package.json": "./package.json",
|
|
27
|
+
".": "./src/index.ts",
|
|
28
|
+
"./*": "./src/*.ts"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"clean": "rm -rf dist .tsbuildinfo",
|
|
36
|
+
"check": "tsc --noEmit",
|
|
37
|
+
"build": "tsc -p tsconfig.build.json",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"test:watch": "vitest",
|
|
40
|
+
"coverage": "vitest run --coverage",
|
|
41
|
+
"lint": "oxlint src",
|
|
42
|
+
"lint:fix": "oxlint --fix src"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"effect": "*",
|
|
46
|
+
"@playfast/reform": "*"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
}
|
|
51
|
+
}
|