@playfast/reform-resource 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -4
- package/dist/asyncResource.d.ts +27 -0
- package/dist/asyncResource.d.ts.map +1 -0
- package/dist/asyncResource.js +18 -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 +3 -0
- package/dist/index.js.map +1 -0
- package/dist/resource.d.ts +24 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +34 -0
- package/dist/resource.js.map +1 -0
- package/package.json +35 -23
- package/src/asyncResource.ts +13 -4
- package/src/reform-resource.test.ts +39 -18
- package/src/resource.ts +7 -8
package/README.md
CHANGED
|
@@ -32,10 +32,7 @@ class Config extends Resource.make('Config', {
|
|
|
32
32
|
}) {}
|
|
33
33
|
|
|
34
34
|
// Wire — `acquire` may be scoped (acquire/release tied to the scene scope).
|
|
35
|
-
const ConfigLive = Resource.live(
|
|
36
|
-
Config,
|
|
37
|
-
Effect.acquireRelease(openConfig, closeConfig),
|
|
38
|
-
)
|
|
35
|
+
const ConfigLive = Resource.live(Config, Effect.acquireRelease(openConfig, closeConfig))
|
|
39
36
|
|
|
40
37
|
// Read inside a composition.
|
|
41
38
|
const view = Effect.gen(function* () {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Store } from '@playfast/reform/internal';
|
|
2
|
+
export interface AsyncResourcePending {
|
|
3
|
+
readonly _tag: 'AsyncResource';
|
|
4
|
+
readonly isReady: false;
|
|
5
|
+
readonly failed: false;
|
|
6
|
+
}
|
|
7
|
+
export interface AsyncResourceReady<A> {
|
|
8
|
+
readonly _tag: 'AsyncResource';
|
|
9
|
+
readonly isReady: true;
|
|
10
|
+
readonly value: A;
|
|
11
|
+
}
|
|
12
|
+
export interface AsyncResourceFailed<E> {
|
|
13
|
+
readonly _tag: 'AsyncResource';
|
|
14
|
+
readonly isReady: false;
|
|
15
|
+
readonly failed: true;
|
|
16
|
+
readonly error: E;
|
|
17
|
+
}
|
|
18
|
+
export type AsyncResource<A, E = never> = AsyncResourcePending | AsyncResourceReady<A> | ([E] extends [never] ? never : AsyncResourceFailed<E>);
|
|
19
|
+
export type AnyAsyncResource<A, E> = AsyncResourcePending | AsyncResourceReady<A> | AsyncResourceFailed<E>;
|
|
20
|
+
export interface AsyncResourceConstructors {
|
|
21
|
+
readonly pending: AsyncResourcePending;
|
|
22
|
+
readonly ready: <A>(value: A) => AsyncResourceReady<A>;
|
|
23
|
+
readonly failed: <E>(error: E) => AsyncResourceFailed<E>;
|
|
24
|
+
}
|
|
25
|
+
export declare const AsyncResource: AsyncResourceConstructors;
|
|
26
|
+
export declare const narrowStore: <A, E>(store: Store<AnyAsyncResource<A, E>>) => Store<AsyncResource<A, E>>;
|
|
27
|
+
//# 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,2BAA2B,CAAA;AAEtD,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,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,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,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,MAAM,MAAM,gBAAgB,CAAC,CAAC,EAAE,CAAC,IAC7B,oBAAoB,GACpB,kBAAkB,CAAC,CAAC,CAAC,GACrB,mBAAmB,CAAC,CAAC,CAAC,CAAA;AAe1B,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,eAAO,MAAM,aAAa,EAAE,yBAAsD,CAAA;AAElF,eAAO,MAAM,WAAW,GAAI,CAAC,EAAE,CAAC,EAC9B,OAAO,KAAK,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KACnC,KAAK,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAEoB,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {} from '@playfast/reform/internal';
|
|
2
|
+
const pending = { _tag: 'AsyncResource', isReady: false, failed: false };
|
|
3
|
+
const ready = (acquired) => ({
|
|
4
|
+
_tag: 'AsyncResource',
|
|
5
|
+
isReady: true,
|
|
6
|
+
value: acquired,
|
|
7
|
+
});
|
|
8
|
+
const failed = (error) => ({
|
|
9
|
+
_tag: 'AsyncResource',
|
|
10
|
+
isReady: false,
|
|
11
|
+
failed: true,
|
|
12
|
+
error,
|
|
13
|
+
});
|
|
14
|
+
export const AsyncResource = { pending, ready, failed };
|
|
15
|
+
export const narrowStore = (store) =>
|
|
16
|
+
// oxlint-disable-next-line reform-rules/no-type-assertion -- sanctioned variance seam: the live store holds the full union; this is the one documented home for narrowing it to the definition-permitted arms (mirrors asyncData.narrowStore).
|
|
17
|
+
store;
|
|
18
|
+
//# sourceMappingURL=asyncResource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asyncResource.js","sourceRoot":"","sources":["../src/asyncResource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,MAAM,2BAA2B,CAAA;AA+BtD,MAAM,OAAO,GAAyB,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;AAC9F,MAAM,KAAK,GAAG,CAAI,QAAW,EAAyB,EAAE,CAAC,CAAC;IACxD,IAAI,EAAE,eAAe;IACrB,OAAO,EAAE,IAAI;IACb,KAAK,EAAE,QAAQ;CAChB,CAAC,CAAA;AACF,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;AAQF,MAAM,CAAC,MAAM,aAAa,GAA8B,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AAElF,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,KAAoC,EACR,EAAE;AAC9B,+OAA+O;AAC/O,KAA8C,CAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export * as Resource from './resource.js';
|
|
2
|
+
export type { ResourceClass, ResourceConfig, ResourceManifest } from './resource.js';
|
|
3
|
+
export { type AnyAsyncResource, AsyncResource, type AsyncResourceConstructors, type AsyncResourceFailed, type AsyncResourcePending, type AsyncResourceReady, } from './asyncResource.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,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 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAA;AAEtC,OAAO,EAEL,aAAa,GAKd,MAAM,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Manifest, type Store } from '@playfast/reform/internal';
|
|
2
|
+
import { Context, Effect, Layer, Option, type Schema, Scope } from 'effect';
|
|
3
|
+
import { AsyncResource } from './asyncResource.js';
|
|
4
|
+
type AnySchema<A> = Schema.Schema<A, Schema.Schema.Encoded<Schema.Schema.AnyNoContext>>;
|
|
5
|
+
export interface ResourceManifest<N extends string, A, E> extends Manifest {
|
|
6
|
+
readonly kind: 'Resource';
|
|
7
|
+
readonly name: N;
|
|
8
|
+
readonly output: AnySchema<A>;
|
|
9
|
+
readonly error: Option.Option<AnySchema<E>>;
|
|
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
|
+
readonly name: N;
|
|
16
|
+
}
|
|
17
|
+
export interface ResourceConfig<A, E> {
|
|
18
|
+
readonly output: AnySchema<A>;
|
|
19
|
+
readonly error: Option.Option<AnySchema<E>>;
|
|
20
|
+
}
|
|
21
|
+
export declare const make: <const N extends string, A, E = never>(name: N, config: ResourceConfig<A, E>) => ResourceClass<N, A, E>;
|
|
22
|
+
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>>;
|
|
23
|
+
export {};
|
|
24
|
+
//# 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,2BAA2B,CAAA;AAClC,OAAO,EAAS,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAClF,OAAO,EAAyB,aAAa,EAAe,MAAM,iBAAiB,CAAA;AAEnF,KAAK,SAAS,CAAC,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAA;AAEvF,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,SAAS,CAAC,CAAC,CAAC,CAAA;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;CAC5C;AAED,MAAM,WAAW,aAAa,CAAC,GAAG,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAE,SAAQ,MAAM,CAAC,MAAM,CAC5F,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EACnB,KAAK,EACL,KAAK,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAC3B;IACC,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,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;CACjB;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,EAAE,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,CAAA;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;CAC5C;AAED,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,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,CA4BtE,CAAA"}
|
package/dist/resource.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { makeStore, readTracked, resolveScheduler, yieldableClass, } from '@playfast/reform/internal';
|
|
2
|
+
import { Cause, Context, Effect, Layer, Option, Scope } from 'effect';
|
|
3
|
+
import { AsyncResource, narrowStore } from './asyncResource.js';
|
|
4
|
+
export const make = (name, config) => {
|
|
5
|
+
const store = Context.GenericTag(`reform/resource/${name}`);
|
|
6
|
+
const manifest = {
|
|
7
|
+
kind: 'Resource',
|
|
8
|
+
name,
|
|
9
|
+
output: config.output,
|
|
10
|
+
error: config.error,
|
|
11
|
+
};
|
|
12
|
+
const read = Effect.flatMap(store, readTracked);
|
|
13
|
+
return yieldableClass(read, { manifest, store, name });
|
|
14
|
+
};
|
|
15
|
+
export const live = (resource, acquire) => Layer.scoped(resource.store, Effect.gen(function* () {
|
|
16
|
+
const scheduler = yield* resolveScheduler;
|
|
17
|
+
const scope = yield* Effect.scope;
|
|
18
|
+
// Sync build seeds pending so layer mount never trips AsyncSceneLayer; store holds full union, tag sees narrowed arms.
|
|
19
|
+
const store = makeStore(AsyncResource.pending, scheduler);
|
|
20
|
+
yield* Effect.forkScoped(acquire.pipe(
|
|
21
|
+
// Finalizers attach to scene scope, not the load fiber — resource lives until unmount.
|
|
22
|
+
Scope.extend(scope), Effect.matchCauseEffect({
|
|
23
|
+
onSuccess: (acquired) => Effect.sync(() => store.set(AsyncResource.ready(acquired))),
|
|
24
|
+
onFailure: (cause) => Option.match(Cause.failureOption(cause), {
|
|
25
|
+
onSome: (error) => Effect.sync(() => store.set(AsyncResource.failed(error))),
|
|
26
|
+
onNone: () => Cause.isInterruptedOnly(cause)
|
|
27
|
+
? Effect.void
|
|
28
|
+
: // oxlint-disable-next-line reform-rules/no-effect-log -- reform-resource has no @play/effect-utils dep (same as reform core); the package logs via Effect directly.
|
|
29
|
+
Effect.logError(`reform-resource: load for '${resource.name}' failed`, cause),
|
|
30
|
+
}),
|
|
31
|
+
})));
|
|
32
|
+
return narrowStore(store);
|
|
33
|
+
}));
|
|
34
|
+
//# 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,2BAA2B,CAAA;AAClC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAe,KAAK,EAAE,MAAM,QAAQ,CAAA;AAClF,OAAO,EAAyB,aAAa,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AA2BnF,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,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,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,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,uHAAuH;IACvH,MAAM,KAAK,GAAG,SAAS,CAAyB,aAAa,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;IACjF,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CACtB,OAAO,CAAC,IAAI;IACV,uFAAuF;IACvF,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EACnB,MAAM,CAAC,gBAAgB,CAAC;QACtB,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;QACpF,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,oKAAoK;oBACpK,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
CHANGED
|
@@ -1,51 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playfast/reform-resource",
|
|
3
|
-
"
|
|
4
|
-
"version": "1.0.2",
|
|
5
|
-
"type": "module",
|
|
3
|
+
"version": "1.2.0",
|
|
6
4
|
"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
5
|
"keywords": [
|
|
8
|
-
"reform",
|
|
9
|
-
"effect",
|
|
10
6
|
"async",
|
|
11
|
-
"
|
|
7
|
+
"effect",
|
|
8
|
+
"layer",
|
|
12
9
|
"reactive",
|
|
13
|
-
"
|
|
10
|
+
"reform",
|
|
11
|
+
"resource"
|
|
14
12
|
],
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/playfast/reform/issues"
|
|
15
|
+
},
|
|
15
16
|
"license": "MIT",
|
|
16
17
|
"repository": {
|
|
17
18
|
"type": "git",
|
|
18
19
|
"url": "https://github.com/playfast/reform.git",
|
|
19
20
|
"directory": "packages/reform-resource"
|
|
20
21
|
},
|
|
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
22
|
"files": [
|
|
23
|
+
"dist",
|
|
31
24
|
"src",
|
|
32
25
|
"README.md"
|
|
33
26
|
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
"./package.json": "./package.json",
|
|
33
|
+
".": {
|
|
34
|
+
"playfast-src": "./src/index.ts",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./*": {
|
|
39
|
+
"playfast-src": "./src/*.ts",
|
|
40
|
+
"types": "./dist/*.d.ts",
|
|
41
|
+
"default": "./dist/*.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
34
47
|
"scripts": {
|
|
35
48
|
"clean": "rm -rf dist .tsbuildinfo",
|
|
36
49
|
"check": "tsc --noEmit",
|
|
37
|
-
"build": "tsc -p tsconfig.build.json",
|
|
50
|
+
"build": "rm -rf dist .tsbuildinfo && tsc -p tsconfig.build.json && bun ../../scripts/fix-esm-extensions.ts dist",
|
|
38
51
|
"test": "vitest run",
|
|
39
52
|
"test:watch": "vitest",
|
|
40
53
|
"coverage": "vitest run --coverage",
|
|
41
54
|
"lint": "oxlint src",
|
|
42
|
-
"lint:fix": "oxlint --fix src"
|
|
55
|
+
"lint:fix": "oxlint --fix src",
|
|
56
|
+
"prepack": "bun run build"
|
|
43
57
|
},
|
|
44
58
|
"peerDependencies": {
|
|
45
|
-
"
|
|
46
|
-
"
|
|
59
|
+
"@playfast/reform": "*",
|
|
60
|
+
"effect": "*"
|
|
47
61
|
},
|
|
48
|
-
"
|
|
49
|
-
"access": "public"
|
|
50
|
-
}
|
|
62
|
+
"playbook": "./playbook"
|
|
51
63
|
}
|
package/src/asyncResource.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Store } from '@playfast/reform'
|
|
1
|
+
import { type Store } from '@playfast/reform/internal'
|
|
2
2
|
|
|
3
3
|
export interface AsyncResourcePending {
|
|
4
4
|
readonly _tag: 'AsyncResource'
|
|
@@ -24,10 +24,17 @@ export type AsyncResource<A, E = never> =
|
|
|
24
24
|
| AsyncResourceReady<A>
|
|
25
25
|
| ([E] extends [never] ? never : AsyncResourceFailed<E>)
|
|
26
26
|
|
|
27
|
-
export type AnyAsyncResource<A, E> =
|
|
27
|
+
export type AnyAsyncResource<A, E> =
|
|
28
|
+
| AsyncResourcePending
|
|
29
|
+
| AsyncResourceReady<A>
|
|
30
|
+
| AsyncResourceFailed<E>
|
|
28
31
|
|
|
29
32
|
const pending: AsyncResourcePending = { _tag: 'AsyncResource', isReady: false, failed: false }
|
|
30
|
-
const ready = <A>(acquired: A): AsyncResourceReady<A> => ({
|
|
33
|
+
const ready = <A>(acquired: A): AsyncResourceReady<A> => ({
|
|
34
|
+
_tag: 'AsyncResource',
|
|
35
|
+
isReady: true,
|
|
36
|
+
value: acquired,
|
|
37
|
+
})
|
|
31
38
|
const failed = <E>(error: E): AsyncResourceFailed<E> => ({
|
|
32
39
|
_tag: 'AsyncResource',
|
|
33
40
|
isReady: false,
|
|
@@ -43,6 +50,8 @@ export interface AsyncResourceConstructors {
|
|
|
43
50
|
|
|
44
51
|
export const AsyncResource: AsyncResourceConstructors = { pending, ready, failed }
|
|
45
52
|
|
|
46
|
-
export const narrowStore = <A, E>(
|
|
53
|
+
export const narrowStore = <A, E>(
|
|
54
|
+
store: Store<AnyAsyncResource<A, E>>,
|
|
55
|
+
): Store<AsyncResource<A, E>> =>
|
|
47
56
|
// oxlint-disable-next-line reform-rules/no-type-assertion -- sanctioned variance seam: the live store holds the full union; this is the one documented home for narrowing it to the definition-permitted arms (mirrors asyncData.narrowStore).
|
|
48
57
|
store as unknown as Store<AsyncResource<A, E>>
|
|
@@ -1,33 +1,47 @@
|
|
|
1
1
|
import { expect, it } from '@effect/vitest'
|
|
2
|
-
import {
|
|
2
|
+
import { makeFlightGate, until } from '@playfast/reform/testkit/flight.testkit'
|
|
3
|
+
import { Context, Data, Effect, Layer, Option, Ref, Schema as S } from 'effect'
|
|
3
4
|
import { Resource } from './index'
|
|
4
5
|
|
|
5
|
-
const tick = (ms = 30) => Effect.sleep(Duration.millis(ms))
|
|
6
|
-
|
|
7
6
|
class Boom extends Data.TaggedError('Boom')<{ readonly message: string }> {}
|
|
8
7
|
|
|
9
8
|
it.live('builds pending then resolves to ready', () => {
|
|
10
9
|
class Num extends Resource.make('Num', { output: S.Number, error: Option.none() }) {}
|
|
11
|
-
const
|
|
10
|
+
const gate = makeFlightGate()
|
|
11
|
+
const layer = Resource.live(Num, gate.through(Effect.succeed(7)))
|
|
12
12
|
|
|
13
|
-
return Effect.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
return Effect.scoped(
|
|
14
|
+
Effect.gen(function* () {
|
|
15
|
+
yield* gate.hold
|
|
16
|
+
const context = yield* Layer.build(layer)
|
|
17
|
+
const store = Context.get(context, Num.store)
|
|
18
|
+
yield* gate.awaitEntry(1)
|
|
19
|
+
expect(store.get().isReady).toBe(false)
|
|
20
|
+
|
|
21
|
+
yield* gate.release
|
|
22
|
+
const value = yield* until(
|
|
23
|
+
() => store.get(),
|
|
24
|
+
(resource) => resource.isReady,
|
|
25
|
+
)
|
|
26
|
+
expect(value.isReady).toBe(true)
|
|
27
|
+
if (value.isReady) expect(value.value).toBe(7)
|
|
28
|
+
}),
|
|
29
|
+
)
|
|
21
30
|
})
|
|
22
31
|
|
|
23
32
|
it.live('a failing load resolves to the failed arm carrying the error', () => {
|
|
24
|
-
class Conn extends Resource.make('Conn', {
|
|
33
|
+
class Conn extends Resource.make('Conn', {
|
|
34
|
+
output: S.Number,
|
|
35
|
+
error: Option.some(S.instanceOf(Boom)),
|
|
36
|
+
}) {}
|
|
25
37
|
const layer = Resource.live(Conn, Effect.fail(new Boom({ message: 'nope' })))
|
|
26
38
|
|
|
27
39
|
return Effect.gen(function* () {
|
|
28
40
|
const store = yield* Conn.store
|
|
29
|
-
yield*
|
|
30
|
-
|
|
41
|
+
const value = yield* until(
|
|
42
|
+
() => store.get(),
|
|
43
|
+
(resource) => !resource.isReady && resource.failed,
|
|
44
|
+
)
|
|
31
45
|
expect(value.isReady).toBe(false)
|
|
32
46
|
if (!value.isReady && value.failed) expect(value.error.message).toBe('nope')
|
|
33
47
|
}).pipe(Effect.provide(layer))
|
|
@@ -43,7 +57,10 @@ it.live('notifies subscribers when it flips to ready', () => {
|
|
|
43
57
|
const unsubscribe = store.subscribe(() => {
|
|
44
58
|
seen.count += 1
|
|
45
59
|
})
|
|
46
|
-
yield*
|
|
60
|
+
yield* until(
|
|
61
|
+
() => seen.count,
|
|
62
|
+
(count) => count > 0,
|
|
63
|
+
)
|
|
47
64
|
unsubscribe()
|
|
48
65
|
expect(store.get().isReady).toBe(true)
|
|
49
66
|
expect(seen.count).toBeGreaterThan(0)
|
|
@@ -63,10 +80,14 @@ it.live('ties acquire/release to the scene scope', () =>
|
|
|
63
80
|
Effect.gen(function* () {
|
|
64
81
|
const context = yield* Layer.build(layer)
|
|
65
82
|
const store = Context.get(context, Conn.store)
|
|
66
|
-
yield*
|
|
83
|
+
yield* until(
|
|
84
|
+
() => store.get(),
|
|
85
|
+
(resource) => resource.isReady,
|
|
86
|
+
)
|
|
67
87
|
expect(store.get().isReady).toBe(true)
|
|
68
88
|
expect(yield* Ref.get(released)).toBe(false)
|
|
69
89
|
}),
|
|
70
90
|
)
|
|
71
91
|
expect(yield* Ref.get(released)).toBe(true)
|
|
72
|
-
})
|
|
92
|
+
}),
|
|
93
|
+
)
|
package/src/resource.ts
CHANGED
|
@@ -5,13 +5,9 @@ import {
|
|
|
5
5
|
resolveScheduler,
|
|
6
6
|
type Store,
|
|
7
7
|
yieldableClass,
|
|
8
|
-
} from '@playfast/reform'
|
|
8
|
+
} from '@playfast/reform/internal'
|
|
9
9
|
import { Cause, Context, Effect, Layer, Option, type Schema, Scope } from 'effect'
|
|
10
|
-
import {
|
|
11
|
-
type AnyAsyncResource,
|
|
12
|
-
AsyncResource,
|
|
13
|
-
narrowStore,
|
|
14
|
-
} from './asyncResource'
|
|
10
|
+
import { type AnyAsyncResource, AsyncResource, narrowStore } from './asyncResource'
|
|
15
11
|
|
|
16
12
|
type AnySchema<A> = Schema.Schema<A, Schema.Schema.Encoded<Schema.Schema.AnyNoContext>>
|
|
17
13
|
|
|
@@ -22,8 +18,11 @@ export interface ResourceManifest<N extends string, A, E> extends Manifest {
|
|
|
22
18
|
readonly error: Option.Option<AnySchema<E>>
|
|
23
19
|
}
|
|
24
20
|
|
|
25
|
-
export interface ResourceClass<out N extends string, in out A, in out E>
|
|
26
|
-
|
|
21
|
+
export interface ResourceClass<out N extends string, in out A, in out E> extends Effect.Effect<
|
|
22
|
+
AsyncResource<A, E>,
|
|
23
|
+
never,
|
|
24
|
+
Store<AsyncResource<A, E>>
|
|
25
|
+
> {
|
|
27
26
|
new (): {}
|
|
28
27
|
readonly manifest: ResourceManifest<N, A, E>
|
|
29
28
|
readonly store: Context.Tag<Store<AsyncResource<A, E>>, Store<AsyncResource<A, E>>>
|