@shivaedev/effect-contract 0.3.0 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 - 2026-10-05
4
+
5
+ ### Added
6
+
7
+ - Optional `live.ts` invalidation over native Streams, Reactivity and AtomRpc runtimes: precise changed keys, explicit full-scope reconciliation after initial/reconnected delivery, bounded paused observations, resume, typed stream failures and scoped teardown. Native query atoms own retained data and stale-request interruption.
8
+ - `Key` and `LiveHint` Schemas for existing list/item keys and Changed/Resync hints. A real streaming HTTP/browser fixture demonstrates their composition with native newline-delimited RPC serialization.
9
+ - Browser-safe `resume.ts` owns the existing native/browser resume signal without React peers; effect-react delegates its existing entry to it.
10
+
11
+ ### Changed
12
+
13
+ - `bind` accepts a native client group with additional RPC operations while preserving the declared contract's types and rejecting clients missing its operations.
14
+
3
15
  ## 0.3.0 - 2026-10-04
4
16
 
5
17
  ### Changed
package/README.md CHANGED
@@ -43,9 +43,17 @@ export const Orders = contract("orders", { queries: [GetOrder, ListOrders], comm
43
43
  | `fieldRejection` | `fieldRejection(struct, keys?)` | fields `{ field: Literals<keys>, message: String }` |
44
44
  | `bind` | `bind(contract, atomRpcService)` | `{ [query]: { query, run }, [command]: { run } }` |
45
45
  | `readKeys` / `invalidationKeys` | `(keys: Key[]) => string[]` | native Reactivity keys |
46
+ | `Key` / `LiveHint` | Schemas in `keys.ts` / `live.ts` | existing list/item keys and optional Changed/Resync hints |
47
+ | `resumeSignal` | `resumeSignal({ window?, native? })` in `resume.ts` | scoped native atom for visible browser resume/online and injected native resume |
48
+ | `live` | `live(atomRuntime, { stream, resyncKeys, resume?, retryDelay? })` | native connection atom, writable pause atom and typed status atom |
46
49
 
47
50
  Payload accepts struct fields or any schema, as in `Rpc.make`. Omitted payload and success are `Schema.Void`.
48
51
 
52
+ Optional [live updates](../../docs/framework/live-updates.md) compose native
53
+ Streams, RPC, Reactivity and AtomRegistry without React or a second cache. The
54
+ guide distinguishes library behavior, real HTTP/browser fixture evidence and
55
+ application adoption, and explains why streaming HTTP uses native NDJSON.
56
+
49
57
  ### Operations and rejections
50
58
 
51
59
  - Each operation exposes `payload`, `success`, `error` (the rejection union, or `Schema.Never` without rejections), `Rejection.X` (the class) and `reject.X(...)`, an `Effect<never, X>`.
package/dist/bind.d.ts CHANGED
@@ -9,24 +9,30 @@ import type { Rpc } from "effect/unstable/rpc";
9
9
  import type { RpcClientError } from "effect/unstable/rpc/RpcClientError";
10
10
  import type { Contract, Tag } from "./contract.ts";
11
11
  import type { CommandShape, QueryShape } from "./operation.ts";
12
- export type Failure<R extends Rpc.Any> = R extends Rpc.Rpc<infer _Tag, infer _Payload, infer _Success, infer Error, infer Middleware, infer _Requires> ? Error["Type"] | Middleware["error"]["Type"] | RpcClientError : never;
13
- export type RunFailure<R extends Rpc.Any> = R extends Rpc.Rpc<infer _Tag, infer _Payload, infer _Success, infer _Error, infer Middleware, infer _Requires> ? Failure<R> | Middleware["~ClientError"] : never;
12
+ export type Failure<R extends Rpc.Any> = R extends Rpc.Rpc<infer _Tag, infer _Payload, infer _Success, infer TError, infer TMiddleware, infer _Requires> ? TError["Type"] | TMiddleware["error"]["Type"] | RpcClientError : never;
13
+ export type RunFailure<R extends Rpc.Any> = R extends Rpc.Rpc<infer _Tag, infer _Payload, infer _Success, infer _Error, infer TMiddleware, infer _Requires> ? Failure<R> | TMiddleware["~ClientError"] : never;
14
14
  export interface QueryOptions {
15
15
  readonly headers?: Headers.Input;
16
16
  readonly serializationKey?: string;
17
17
  readonly timeToLive?: Duration.Input;
18
18
  }
19
- export interface BoundQuery<R extends Rpc.Any, Self> {
19
+ export interface BoundQuery<R extends Rpc.Any, TSelf> {
20
20
  readonly query: (payload: Rpc.Payload<R>, options?: QueryOptions) => Atom.Atom<AsyncResult.AsyncResult<Rpc.Success<R>, Failure<R>>>;
21
- readonly run: (payload: Rpc.Payload<R>) => Effect.Effect<Rpc.Success<R>, RunFailure<R>, Self>;
21
+ readonly run: (payload: Rpc.Payload<R>) => Effect.Effect<Rpc.Success<R>, RunFailure<R>, TSelf>;
22
22
  }
23
- export interface BoundCommand<R extends Rpc.Any, Self> {
24
- readonly run: (payload: Rpc.Payload<R>) => Effect.Effect<Rpc.Success<R>, RunFailure<R>, Self | Reactivity.Reactivity>;
23
+ export interface BoundCommand<R extends Rpc.Any, TSelf> {
24
+ readonly run: (payload: Rpc.Payload<R>) => Effect.Effect<Rpc.Success<R>, RunFailure<R>, TSelf | Reactivity.Reactivity>;
25
25
  }
26
- export type Bound<Name extends string, Queries extends readonly QueryShape[], Commands extends readonly CommandShape[], Rpcs extends Rpc.Any, Self> = {
27
- readonly [Query in Queries[number] as Query["name"]]: BoundQuery<Rpc.ExtractTag<Rpcs, Tag<Name, Query["name"]>>, Self>;
26
+ export type Bound<TName extends string, TQueries extends readonly QueryShape[], TCommands extends readonly CommandShape[], TRpcs extends Rpc.Any, TSelf> = {
27
+ readonly [TQuery in TQueries[number] as TQuery["name"]]: BoundQuery<Rpc.ExtractTag<TRpcs, Tag<TName, TQuery["name"]>>, TSelf>;
28
28
  } & {
29
- readonly [Command in Commands[number] as Command["name"]]: BoundCommand<Rpc.ExtractTag<Rpcs, Tag<Name, Command["name"]>>, Self>;
29
+ readonly [TCommand in TCommands[number] as TCommand["name"]]: BoundCommand<Rpc.ExtractTag<TRpcs, Tag<TName, TCommand["name"]>>, TSelf>;
30
30
  };
31
- export declare function bind<Name extends string, Queries extends readonly QueryShape[], Commands extends readonly CommandShape[], Rpcs extends Rpc.Any, Self, Id extends string>(contract: Contract<Name, Queries, Commands, Rpcs>, service: AtomRpc.AtomRpcClient<Self, Id, Rpcs>): Bound<Name, Queries, Commands, Rpcs, Self>;
31
+ type MatchingClient<TRpcs extends Rpc.Any, TClientRpcs extends Rpc.Any> = [TRpcs] extends [Extract<TClientRpcs, {
32
+ readonly _tag: TRpcs["_tag"];
33
+ }>] ? [Extract<TClientRpcs, {
34
+ readonly _tag: TRpcs["_tag"];
35
+ }>] extends [TRpcs] ? unknown : "Client must preserve the contract RPC types" : "Client must implement the contract RPCs";
36
+ export declare function bind<TName extends string, TQueries extends readonly QueryShape[], TCommands extends readonly CommandShape[], TRpcs extends Rpc.Any, TSelf, TId extends string, TClientRpcs extends Rpc.Any>(contract: Contract<TName, TQueries, TCommands, TRpcs>, service: AtomRpc.AtomRpcClient<TSelf, TId, TClientRpcs> & MatchingClient<NoInfer<TRpcs>, TClientRpcs>): Bound<TName, TQueries, TCommands, TRpcs, TSelf>;
37
+ export {};
32
38
  //# sourceMappingURL=bind.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bind.d.ts","sourceRoot":"","sources":["../src/bind.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bind.d.ts","sourceRoot":"","sources":["../src/bind.ts"],"names":[],"mappings":"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"}
package/dist/bind.js CHANGED
@@ -1,19 +1,23 @@
1
1
  import { Effect } from "effect";
2
2
  import * as Reactivity from "effect/unstable/reactivity/Reactivity";
3
3
  import { invalidationKeys, readKeys } from "./keys.js";
4
- const invalidateAfter = Effect.fn("EffectContract.invalidate")(function* (keys) {
4
+ const invalidateAfter = Effect.fn("EffectContract.invalidateAfter")(function* (keys) {
5
5
  yield* Reactivity.invalidate(invalidationKeys(keys));
6
6
  });
7
- const boundQuery = (service, tag, query) => {
8
- const reads = (payload) => readKeys(query.reads(payload));
7
+ function boundQuery(service, tag, query) {
8
+ function reads(payload) {
9
+ return readKeys(query.reads(payload));
10
+ }
9
11
  return {
10
12
  query: (payload, options = {}) => service.query(tag, payload, { ...options, reactivityKeys: reads(payload) }),
11
13
  run: (payload) => service.use((client) => client(tag, payload)),
12
14
  };
13
- };
14
- const boundCommand = (service, tag, command) => ({
15
- run: (payload) => service.use((client) => client(tag, payload)).pipe(Effect.tap((result) => invalidateAfter(command.invalidates(payload, result)))),
16
- });
15
+ }
16
+ function boundCommand(service, tag, command) {
17
+ return {
18
+ run: (payload) => service.use((client) => client(tag, payload)).pipe(Effect.tap((result) => invalidateAfter(command.invalidates(payload, result)))),
19
+ };
20
+ }
17
21
  export function bind(contract, service) {
18
22
  const { name, queries, commands } = contract.declaration;
19
23
  return Object.fromEntries([
package/dist/bind.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bind.js","sourceRoot":"","sources":["../src/bind.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAKhC,OAAO,KAAK,UAAU,MAAM,uCAAuC,CAAC;AAIpE,OAAO,EAAE,gBAAgB,EAAY,QAAQ,EAAE,MAAM,WAAW,CAAC;AA0CjE,MAAM,eAAe,GAAG,MAAM,CAAC,EAAE,CAAC,2BAA2B,CAAC,CAAC,QAAQ,CAAC,EAAE,IAAoB;IAC7F,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC;AACtD,CAAC,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG,CAAC,OAAqB,EAAE,GAAW,EAAE,KAAiB,EAAE,EAAE;IAC5E,MAAM,KAAK,GAAG,CAAC,OAAgB,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IACnE,OAAO;QACN,KAAK,EAAE,CAAC,OAAgB,EAAE,UAAwB,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACpI,GAAG,EAAE,CAAC,OAAgB,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;KACxE,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,OAAqB,EAAE,GAAW,EAAE,OAAqB,EAAE,EAAE,CAAC,CAAC;IACpF,GAAG,EAAE,CAAC,OAAgB,EAAE,EAAE,CACzB,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;CAClI,CAAC,CAAC;AAUH,MAAM,UAAU,IAAI,CACnB,QAAoG,EACpG,OAAqB;IAErB,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC;IACzD,OAAO,MAAM,CAAC,WAAW,CAAC;QACzB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,KAAK,CAAC,CAAU,CAAC;QACrG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAU,CAAC;KAChH,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"bind.js","sourceRoot":"","sources":["../src/bind.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAKhC,OAAO,KAAK,UAAU,MAAM,uCAAuC,CAAC;AAIpE,OAAO,EAAE,gBAAgB,EAAY,QAAQ,EAAE,MAAM,WAAW,CAAC;AAgDjE,MAAM,eAAe,GAAG,MAAM,CAAC,EAAE,CAAC,gCAAgC,CAAC,CAAC,QAAQ,CAAC,EAAE,IAAoB;IAClG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC;AACtD,CAAC,CAAC,CAAC;AAEH,SAAS,UAAU,CAAC,OAAqB,EAAE,GAAW,EAAE,KAAiB;IACxE,SAAS,KAAK,CAAC,OAAgB;QAC9B,OAAO,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IACvC,CAAC;IACD,OAAO;QACN,KAAK,EAAE,CAAC,OAAgB,EAAE,UAAwB,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACpI,GAAG,EAAE,CAAC,OAAgB,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;KACxE,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,OAAqB,EAAE,GAAW,EAAE,OAAqB;IAC9E,OAAO;QACN,GAAG,EAAE,CAAC,OAAgB,EAAE,EAAE,CACzB,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;KAClI,CAAC;AACH,CAAC;AAcD,MAAM,UAAU,IAAI,CACnB,QAAoG,EACpG,OAAqB;IAErB,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC;IACzD,OAAO,MAAM,CAAC,WAAW,CAAC;QACzB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,KAAK,CAAC,CAAU,CAAC;QACrG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAU,CAAC;KAChH,CAAC,CAAC;AACJ,CAAC"}
package/dist/keys.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Schema } from "effect";
1
+ import { Schema } from "effect";
2
2
  export type Identity = string | number;
3
3
  export interface ListKey<Name extends string = string> {
4
4
  readonly _tag: "List";
@@ -10,6 +10,14 @@ export interface ItemKey<Name extends string = string> {
10
10
  readonly id: Identity;
11
11
  }
12
12
  export type Key = ListKey | ItemKey;
13
+ export declare const Key: Schema.Union<readonly [Schema.Struct<{
14
+ readonly _tag: Schema.Literal<"List">;
15
+ readonly collection: Schema.String;
16
+ }>, Schema.Struct<{
17
+ readonly _tag: Schema.Literal<"Item">;
18
+ readonly collection: Schema.String;
19
+ readonly id: Schema.Union<readonly [Schema.String, Schema.Number]>;
20
+ }>]>;
13
21
  export interface Collection<Name extends string, Id extends Identity> {
14
22
  readonly item: (id: Id) => ItemKey<Name>;
15
23
  readonly list: ListKey<Name>;
@@ -1 +1 @@
1
- {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErC,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,MAAM,WAAW,OAAO,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,OAAO,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;CACtB;AAED,MAAM,MAAM,GAAG,GAAG,OAAO,GAAG,OAAO,CAAC;AAEpC,MAAM,WAAW,UAAU,CAAC,IAAI,SAAS,MAAM,EAAE,EAAE,SAAS,QAAQ;IACnE,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;CACpB;AAED,eAAO,MAAM,UAAU,GAAI,KAAK,CAAC,IAAI,SAAS,MAAM,EAAE,EAAE,SAAS,MAAM,CAAC,GAAG,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,EACxG,MAAM,IAAI,EACV,KAAK,EAAE,KACL,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,CAI5B,CAAC;AAIH,eAAO,MAAM,QAAQ,GAAI,MAAM,SAAS,GAAG,EAAE,KAAG,SAAS,MAAM,EAAiC,CAAC;AAEjG,eAAO,MAAM,gBAAgB,GAAI,MAAM,SAAS,GAAG,EAAE,KAAG,SAAS,MAAM,EAEtE,CAAC"}
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,MAAM,WAAW,OAAO,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,OAAO,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;CACtB;AAED,MAAM,MAAM,GAAG,GAAG,OAAO,GAAG,OAAO,CAAC;AAEpC,eAAO,MAAM,GAAG;;;;;;;IAGd,CAAC;AAEH,MAAM,WAAW,UAAU,CAAC,IAAI,SAAS,MAAM,EAAE,EAAE,SAAS,QAAQ;IACnE,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;CACpB;AAED,eAAO,MAAM,UAAU,GAAI,KAAK,CAAC,IAAI,SAAS,MAAM,EAAE,EAAE,SAAS,MAAM,CAAC,GAAG,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,EACxG,MAAM,IAAI,EACV,KAAK,EAAE,KACL,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,CAI5B,CAAC;AAIH,eAAO,MAAM,QAAQ,GAAI,MAAM,SAAS,GAAG,EAAE,KAAG,SAAS,MAAM,EAAiC,CAAC;AAEjG,eAAO,MAAM,gBAAgB,GAAI,MAAM,SAAS,GAAG,EAAE,KAAG,SAAS,MAAM,EAEtE,CAAC"}
package/dist/keys.js CHANGED
@@ -1,3 +1,8 @@
1
+ import { Schema } from "effect";
2
+ export const Key = Schema.Union([
3
+ Schema.Struct({ _tag: Schema.Literal("List"), collection: Schema.String }),
4
+ Schema.Struct({ _tag: Schema.Literal("Item"), collection: Schema.String, id: Schema.Union([Schema.String, Schema.Number]) }),
5
+ ]);
1
6
  export const collection = (name, _id) => ({
2
7
  item: (id) => ({ _tag: "Item", collection: name, id }),
3
8
  list: { _tag: "List", collection: name },
package/dist/keys.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAuBA,MAAM,CAAC,MAAM,UAAU,GAAG,CACzB,IAAU,EACV,GAAO,EACwB,EAAE,CAAC,CAAC;IACnC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IACtD,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE;IACxC,IAAI;CACJ,CAAC,CAAC;AAEH,MAAM,GAAG,GAAG,CAAC,GAAQ,EAAU,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;AAEzG,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAoB,EAAqB,EAAE,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAEjG,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,IAAoB,EAAqB,EAAE,CAAC;IAC5E,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CACxG,CAAC"}
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAiBhC,MAAM,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC;IAC/B,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IAC1E,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;CAC5H,CAAC,CAAC;AAQH,MAAM,CAAC,MAAM,UAAU,GAAG,CACzB,IAAU,EACV,GAAO,EACwB,EAAE,CAAC,CAAC;IACnC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IACtD,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE;IACxC,IAAI;CACJ,CAAC,CAAC;AAEH,MAAM,GAAG,GAAG,CAAC,GAAQ,EAAU,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;AAEzG,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAoB,EAAqB,EAAE,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAEjG,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,IAAoB,EAAqB,EAAE,CAAC;IAC5E,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CACxG,CAAC"}
package/dist/live.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ import { type Cause, type Duration, Option, Schema, Stream } from "effect";
2
+ import * as Atom from "effect/unstable/reactivity/Atom";
3
+ import { Key } from "./keys.ts";
4
+ export declare const LiveHint: Schema.Union<readonly [Schema.Struct<{
5
+ readonly _tag: Schema.Literal<"Changed">;
6
+ readonly keys: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
7
+ readonly _tag: Schema.Literal<"List">;
8
+ readonly collection: Schema.String;
9
+ }>, Schema.Struct<{
10
+ readonly _tag: Schema.Literal<"Item">;
11
+ readonly collection: Schema.String;
12
+ readonly id: Schema.Union<readonly [Schema.String, Schema.Number]>;
13
+ }>]>>;
14
+ }>, Schema.Struct<{
15
+ readonly _tag: Schema.Literal<"Resync">;
16
+ }>]>;
17
+ export type LiveHint = typeof LiveHint.Type;
18
+ export interface LiveStatus<E> {
19
+ readonly connection: "connecting" | "live" | "reconnecting";
20
+ readonly failure: Option.Option<Cause.Cause<E>>;
21
+ readonly needsResync: boolean;
22
+ readonly pending: number;
23
+ }
24
+ export interface LiveOptions<E, R> {
25
+ readonly resume?: Atom.Atom<unknown>;
26
+ readonly resyncKeys: readonly Key[];
27
+ readonly retryDelay?: Duration.Input;
28
+ readonly stream: Stream.Stream<LiveHint, E, R>;
29
+ }
30
+ export declare function live<R, E, TRuntimeError>(runtime: Atom.AtomRuntime<R, TRuntimeError>, options: LiveOptions<E, R>): {
31
+ connection: Atom.Atom<import("effect/unstable/reactivity/AsyncResult").AsyncResult<void, TRuntimeError>>;
32
+ paused: Atom.Writable<boolean, boolean>;
33
+ status: Atom.Writable<LiveStatus<E>, LiveStatus<E>>;
34
+ };
35
+ //# sourceMappingURL=live.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,QAAQ,EAAU,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACnF,OAAO,KAAK,IAAI,MAAM,iCAAiC,CAAC;AAExD,OAAO,EAAoB,GAAG,EAAY,MAAM,WAAW,CAAC;AAE5D,eAAO,MAAM,QAAQ;;;;;;;;;;;;IAGnB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAC;AAC5C,MAAM,WAAW,UAAU,CAAC,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,YAAY,GAAG,MAAM,GAAG,cAAc,CAAC;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AACD,MAAM,WAAW,WAAW,CAAC,CAAC,EAAE,CAAC;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,SAAS,GAAG,EAAE,CAAC;IACpC,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;CAC/C;AAID,wBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,aAAa,CAAC,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;;;;EAuDhH"}
package/dist/live.js ADDED
@@ -0,0 +1,60 @@
1
+ import { Effect, Option, Schema, Stream } from "effect";
2
+ import * as Atom from "effect/unstable/reactivity/Atom";
3
+ import * as Reactivity from "effect/unstable/reactivity/Reactivity";
4
+ import { invalidationKeys, Key, readKeys } from "./keys.js";
5
+ export const LiveHint = Schema.Union([
6
+ Schema.Struct({ _tag: Schema.Literal("Changed"), keys: Schema.Array(Key) }),
7
+ Schema.Struct({ _tag: Schema.Literal("Resync") }),
8
+ ]);
9
+ const MAX_PENDING = 256;
10
+ export function live(runtime, options) {
11
+ const paused = Atom.make(false);
12
+ const status = Atom.make({ connection: "connecting", failure: Option.none(), needsResync: true, pending: 0 });
13
+ const connection = runtime
14
+ .atom((get) => Effect.gen(function* () {
15
+ get.mount(status);
16
+ const reactivity = yield* Reactivity.Reactivity;
17
+ function update(value) {
18
+ get.set(status, { ...get.once(status), ...value });
19
+ }
20
+ function resync() {
21
+ if (get.once(paused)) {
22
+ update({ needsResync: true });
23
+ return;
24
+ }
25
+ reactivity.invalidateUnsafe(readKeys(options.resyncKeys));
26
+ update({ needsResync: false, pending: 0 });
27
+ }
28
+ get.subscribe(paused, (value) => {
29
+ if (!value) {
30
+ resync();
31
+ }
32
+ });
33
+ if (options.resume !== undefined) {
34
+ // A subscription alone does not initialize a derived signal's dependencies.
35
+ get.once(options.resume);
36
+ get.subscribe(options.resume, resync);
37
+ }
38
+ const attempt = Effect.suspend(() => {
39
+ let isFirst = true;
40
+ function observe(hint) {
41
+ update({ connection: "live", failure: Option.none() });
42
+ if (get.once(paused)) {
43
+ update({ needsResync: true, pending: Math.min(MAX_PENDING, get.once(status).pending + 1) });
44
+ }
45
+ else if (isFirst || hint._tag === "Resync") {
46
+ resync();
47
+ }
48
+ else {
49
+ reactivity.invalidateUnsafe(invalidationKeys(hint.keys));
50
+ }
51
+ isFirst = false;
52
+ }
53
+ return options.stream.pipe(Stream.runForEach((hint) => Effect.sync(() => observe(hint))), Effect.scoped, Effect.catchCause((failure) => Effect.sync(() => update({ failure: Option.some(failure) }))), Effect.andThen(Effect.sync(() => update({ connection: "reconnecting", needsResync: true }))), Effect.andThen(Effect.sleep(options.retryDelay ?? "1 second")));
54
+ });
55
+ yield* Effect.forever(attempt);
56
+ }))
57
+ .pipe(Atom.setIdleTTL(0));
58
+ return { connection, paused, status };
59
+ }
60
+ //# sourceMappingURL=live.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live.js","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACnF,OAAO,KAAK,IAAI,MAAM,iCAAiC,CAAC;AACxD,OAAO,KAAK,UAAU,MAAM,uCAAuC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE5D,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC;IACpC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IAC3E,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;CACjD,CAAC,CAAC;AAeH,MAAM,WAAW,GAAG,GAAG,CAAC;AAExB,MAAM,UAAU,IAAI,CAAsB,OAA2C,EAAE,OAA0B;IAChH,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAgB,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IAC7H,MAAM,UAAU,GAAG,OAAO;SACxB,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CACb,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAClB,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;QAChD,SAAS,MAAM,CAAC,KAA6B;YAC5C,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QACpD,CAAC;QACD,SAAS,MAAM;YACd,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBACtB,MAAM,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC9B,OAAO;YACR,CAAC;YACD,UAAU,CAAC,gBAAgB,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;YAC1D,MAAM,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QAC5C,CAAC;QACD,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;YAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;gBACZ,MAAM,EAAE,CAAC;YACV,CAAC;QACF,CAAC,CAAC,CAAC;QACH,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAClC,4EAA4E;YAC5E,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACzB,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACvC,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;YACnC,IAAI,OAAO,GAAG,IAAI,CAAC;YACnB,SAAS,OAAO,CAAC,IAAc;gBAC9B,MAAM,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACvD,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;oBACtB,MAAM,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;gBAC7F,CAAC;qBAAM,IAAI,OAAO,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAC9C,MAAM,EAAE,CAAC;gBACV,CAAC;qBAAM,CAAC;oBACP,UAAU,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC1D,CAAC;gBACD,OAAO,GAAG,KAAK,CAAC;YACjB,CAAC;YACD,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CACzB,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAC7D,MAAM,CAAC,MAAM,EACb,MAAM,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAC5F,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAC5F,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,IAAI,UAAU,CAAC,CAAC,CAC9D,CAAC;QACH,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC,CAAC,CACF;SACA,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AACvC,CAAC"}
@@ -0,0 +1,17 @@
1
+ import * as Atom from "effect/unstable/reactivity/Atom";
2
+ export type ResumeSource = (resume: () => void) => () => void;
3
+ type ResumeEvent = "online" | "visibilitychange";
4
+ export interface ResumeWindow {
5
+ readonly addEventListener: (type: ResumeEvent, listener: () => void) => void;
6
+ readonly document: {
7
+ readonly visibilityState: string;
8
+ };
9
+ readonly removeEventListener: (type: ResumeEvent, listener: () => void) => void;
10
+ }
11
+ export interface ResumeOptions {
12
+ readonly native?: ResumeSource;
13
+ readonly window?: ResumeWindow;
14
+ }
15
+ export declare function resumeSignal({ window, native }: ResumeOptions): Atom.Atom<number>;
16
+ export {};
17
+ //# sourceMappingURL=resume.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../src/resume.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,iCAAiC,CAAC;AAExD,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;AAE9D,KAAK,WAAW,GAAG,QAAQ,GAAG,kBAAkB,CAAC;AAEjD,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IAC7E,QAAQ,CAAC,QAAQ,EAAE;QAAE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD,QAAQ,CAAC,mBAAmB,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CAChF;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;CAC/B;AAcD,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAcjF"}
package/dist/resume.js ADDED
@@ -0,0 +1,28 @@
1
+ import * as Atom from "effect/unstable/reactivity/Atom";
2
+ function whileVisible(target, type) {
3
+ return (resume) => {
4
+ function visible() {
5
+ if (target.document.visibilityState === "visible") {
6
+ resume();
7
+ }
8
+ }
9
+ target.addEventListener(type, visible);
10
+ return () => target.removeEventListener(type, visible);
11
+ };
12
+ }
13
+ export function resumeSignal({ window, native }) {
14
+ const browser = window === undefined ? [] : [whileVisible(window, "visibilitychange"), whileVisible(window, "online")];
15
+ const sources = native === undefined ? browser : [...browser, native];
16
+ return Atom.readable((get) => {
17
+ let count = 0;
18
+ function resume() {
19
+ count += 1;
20
+ get.setSelf(count);
21
+ }
22
+ for (const source of sources) {
23
+ get.addFinalizer(source(resume));
24
+ }
25
+ return count;
26
+ });
27
+ }
28
+ //# sourceMappingURL=resume.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume.js","sourceRoot":"","sources":["../src/resume.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,iCAAiC,CAAC;AAiBxD,SAAS,YAAY,CAAC,MAAoB,EAAE,IAAiB;IAC5D,OAAO,CAAC,MAAM,EAAE,EAAE;QACjB,SAAS,OAAO;YACf,IAAI,MAAM,CAAC,QAAQ,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;gBACnD,MAAM,EAAE,CAAC;YACV,CAAC;QACF,CAAC;QACD,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACvC,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACxD,CAAC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAiB;IAC7D,MAAM,OAAO,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,EAAE,kBAAkB,CAAC,EAAE,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACvH,MAAM,OAAO,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,CAAC,CAAC;IACtE,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,EAAE,EAAE;QAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,SAAS,MAAM;YACd,KAAK,IAAI,CAAC,CAAC;YACX,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;QACD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC9B,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QAClC,CAAC;QACD,OAAO,KAAK,CAAC;IACd,CAAC,CAAC,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/effect-contract",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Typed query and command contracts over native Effect RPC and reactivity",
5
5
  "homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/effect-contract#readme",
6
6
  "bugs": {
@@ -15,7 +15,7 @@
15
15
  "sideEffects": false,
16
16
  "type": "module",
17
17
  "imports": {
18
- "#test/*": "./test/*",
18
+ "#test/*": "./src/test-support/*",
19
19
  "#*.ts": {
20
20
  "source": "./src/*.ts",
21
21
  "types": "./dist/*.d.ts",
@@ -34,6 +34,9 @@
34
34
  "files": [
35
35
  "dist",
36
36
  "src",
37
+ "!src/**/*.test.ts",
38
+ "!src/**/*.spec.ts",
39
+ "!src/**/test-support",
37
40
  "CHANGELOG.md",
38
41
  "README.md"
39
42
  ],
@@ -41,13 +44,17 @@
41
44
  "@shivaedev/types": "0.2.0"
42
45
  },
43
46
  "devDependencies": {
47
+ "@effect/platform-node": "4.0.0-rc.112",
44
48
  "@effect/vitest": "4.0.0-rc.112",
45
- "@shivaedev/quality": "0.9.0",
49
+ "@shivaedev/effect-test": "0.3.0",
50
+ "@shivaedev/quality": "0.15.0",
46
51
  "@types/node": "24.10.1",
47
52
  "@typescript/native": "npm:typescript@7.0.2",
48
53
  "effect": "4.0.0-rc.112",
54
+ "esbuild": "0.28.1",
55
+ "happy-dom": "20.11.2",
49
56
  "typescript": "npm:@typescript/typescript6@6.0.2",
50
- "vitest": "4.1.9"
57
+ "vitest": "4.1.11"
51
58
  },
52
59
  "peerDependencies": {
53
60
  "effect": "4.0.0-rc.112"
package/src/bind.ts CHANGED
@@ -12,13 +12,13 @@ import { invalidationKeys, type Key, readKeys } from "./keys.ts";
12
12
  import type { CommandShape, QueryShape } from "./operation.ts";
13
13
 
14
14
  export type Failure<R extends Rpc.Any> =
15
- R extends Rpc.Rpc<infer _Tag, infer _Payload, infer _Success, infer Error, infer Middleware, infer _Requires>
16
- ? Error["Type"] | Middleware["error"]["Type"] | RpcClientError
15
+ R extends Rpc.Rpc<infer _Tag, infer _Payload, infer _Success, infer TError, infer TMiddleware, infer _Requires>
16
+ ? TError["Type"] | TMiddleware["error"]["Type"] | RpcClientError
17
17
  : never;
18
18
 
19
19
  export type RunFailure<R extends Rpc.Any> =
20
- R extends Rpc.Rpc<infer _Tag, infer _Payload, infer _Success, infer _Error, infer Middleware, infer _Requires>
21
- ? Failure<R> | Middleware["~ClientError"]
20
+ R extends Rpc.Rpc<infer _Tag, infer _Payload, infer _Success, infer _Error, infer TMiddleware, infer _Requires>
21
+ ? Failure<R> | TMiddleware["~ClientError"]
22
22
  : never;
23
23
 
24
24
  export interface QueryOptions {
@@ -27,54 +27,68 @@ export interface QueryOptions {
27
27
  readonly timeToLive?: Duration.Input;
28
28
  }
29
29
 
30
- export interface BoundQuery<R extends Rpc.Any, Self> {
30
+ export interface BoundQuery<R extends Rpc.Any, TSelf> {
31
31
  readonly query: (payload: Rpc.Payload<R>, options?: QueryOptions) => Atom.Atom<AsyncResult.AsyncResult<Rpc.Success<R>, Failure<R>>>;
32
- readonly run: (payload: Rpc.Payload<R>) => Effect.Effect<Rpc.Success<R>, RunFailure<R>, Self>;
32
+ readonly run: (payload: Rpc.Payload<R>) => Effect.Effect<Rpc.Success<R>, RunFailure<R>, TSelf>;
33
33
  }
34
34
 
35
- export interface BoundCommand<R extends Rpc.Any, Self> {
36
- readonly run: (payload: Rpc.Payload<R>) => Effect.Effect<Rpc.Success<R>, RunFailure<R>, Self | Reactivity.Reactivity>;
35
+ export interface BoundCommand<R extends Rpc.Any, TSelf> {
36
+ readonly run: (payload: Rpc.Payload<R>) => Effect.Effect<Rpc.Success<R>, RunFailure<R>, TSelf | Reactivity.Reactivity>;
37
37
  }
38
38
 
39
39
  export type Bound<
40
- Name extends string,
41
- Queries extends readonly QueryShape[],
42
- Commands extends readonly CommandShape[],
43
- Rpcs extends Rpc.Any,
44
- Self,
40
+ TName extends string,
41
+ TQueries extends readonly QueryShape[],
42
+ TCommands extends readonly CommandShape[],
43
+ TRpcs extends Rpc.Any,
44
+ TSelf,
45
45
  > = {
46
- readonly [Query in Queries[number] as Query["name"]]: BoundQuery<Rpc.ExtractTag<Rpcs, Tag<Name, Query["name"]>>, Self>;
46
+ readonly [TQuery in TQueries[number] as TQuery["name"]]: BoundQuery<Rpc.ExtractTag<TRpcs, Tag<TName, TQuery["name"]>>, TSelf>;
47
47
  } & {
48
- readonly [Command in Commands[number] as Command["name"]]: BoundCommand<Rpc.ExtractTag<Rpcs, Tag<Name, Command["name"]>>, Self>;
48
+ readonly [TCommand in TCommands[number] as TCommand["name"]]: BoundCommand<Rpc.ExtractTag<TRpcs, Tag<TName, TCommand["name"]>>, TSelf>;
49
49
  };
50
50
 
51
51
  type ErasedClient = AtomRpc.AtomRpcClient<unknown, string, Rpc.Rpc<string, Schema.Top, Schema.Top, Schema.Top>>;
52
52
 
53
- const invalidateAfter = Effect.fn("EffectContract.invalidate")(function* (keys: readonly Key[]) {
53
+ type MatchingClient<TRpcs extends Rpc.Any, TClientRpcs extends Rpc.Any> = [TRpcs] extends [Extract<TClientRpcs, { readonly _tag: TRpcs["_tag"] }>]
54
+ ? [Extract<TClientRpcs, { readonly _tag: TRpcs["_tag"] }>] extends [TRpcs]
55
+ ? unknown
56
+ : "Client must preserve the contract RPC types"
57
+ : "Client must implement the contract RPCs";
58
+
59
+ const invalidateAfter = Effect.fn("EffectContract.invalidateAfter")(function* (keys: readonly Key[]) {
54
60
  yield* Reactivity.invalidate(invalidationKeys(keys));
55
61
  });
56
62
 
57
- const boundQuery = (service: ErasedClient, tag: string, query: QueryShape) => {
58
- const reads = (payload: unknown) => readKeys(query.reads(payload));
63
+ function boundQuery(service: ErasedClient, tag: string, query: QueryShape) {
64
+ function reads(payload: unknown) {
65
+ return readKeys(query.reads(payload));
66
+ }
59
67
  return {
60
68
  query: (payload: unknown, options: QueryOptions = {}) => service.query(tag, payload, { ...options, reactivityKeys: reads(payload) }),
61
69
  run: (payload: unknown) => service.use((client) => client(tag, payload)),
62
70
  };
63
- };
71
+ }
64
72
 
65
- const boundCommand = (service: ErasedClient, tag: string, command: CommandShape) => ({
66
- run: (payload: unknown) =>
67
- service.use((client) => client(tag, payload)).pipe(Effect.tap((result) => invalidateAfter(command.invalidates(payload, result)))),
68
- });
73
+ function boundCommand(service: ErasedClient, tag: string, command: CommandShape) {
74
+ return {
75
+ run: (payload: unknown) =>
76
+ service.use((client) => client(tag, payload)).pipe(Effect.tap((result) => invalidateAfter(command.invalidates(payload, result)))),
77
+ };
78
+ }
69
79
 
70
80
  export function bind<
71
- Name extends string,
72
- Queries extends readonly QueryShape[],
73
- Commands extends readonly CommandShape[],
74
- Rpcs extends Rpc.Any,
75
- Self,
76
- Id extends string,
77
- >(contract: Contract<Name, Queries, Commands, Rpcs>, service: AtomRpc.AtomRpcClient<Self, Id, Rpcs>): Bound<Name, Queries, Commands, Rpcs, Self>;
81
+ TName extends string,
82
+ TQueries extends readonly QueryShape[],
83
+ TCommands extends readonly CommandShape[],
84
+ TRpcs extends Rpc.Any,
85
+ TSelf,
86
+ TId extends string,
87
+ TClientRpcs extends Rpc.Any,
88
+ >(
89
+ contract: Contract<TName, TQueries, TCommands, TRpcs>,
90
+ service: AtomRpc.AtomRpcClient<TSelf, TId, TClientRpcs> & MatchingClient<NoInfer<TRpcs>, TClientRpcs>,
91
+ ): Bound<TName, TQueries, TCommands, TRpcs, TSelf>;
78
92
  export function bind(
79
93
  contract: { readonly declaration: Declared<string, readonly QueryShape[], readonly CommandShape[]> },
80
94
  service: ErasedClient,
package/src/keys.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Schema } from "effect";
1
+ import { Schema } from "effect";
2
2
 
3
3
  export type Identity = string | number;
4
4
 
@@ -15,6 +15,11 @@ export interface ItemKey<Name extends string = string> {
15
15
 
16
16
  export type Key = ListKey | ItemKey;
17
17
 
18
+ export const Key = Schema.Union([
19
+ Schema.Struct({ _tag: Schema.Literal("List"), collection: Schema.String }),
20
+ Schema.Struct({ _tag: Schema.Literal("Item"), collection: Schema.String, id: Schema.Union([Schema.String, Schema.Number]) }),
21
+ ]);
22
+
18
23
  export interface Collection<Name extends string, Id extends Identity> {
19
24
  readonly item: (id: Id) => ItemKey<Name>;
20
25
  readonly list: ListKey<Name>;
package/src/live.ts ADDED
@@ -0,0 +1,81 @@
1
+ import { type Cause, type Duration, Effect, Option, Schema, Stream } from "effect";
2
+ import * as Atom from "effect/unstable/reactivity/Atom";
3
+ import * as Reactivity from "effect/unstable/reactivity/Reactivity";
4
+ import { invalidationKeys, Key, readKeys } from "./keys.ts";
5
+
6
+ export const LiveHint = Schema.Union([
7
+ Schema.Struct({ _tag: Schema.Literal("Changed"), keys: Schema.Array(Key) }),
8
+ Schema.Struct({ _tag: Schema.Literal("Resync") }),
9
+ ]);
10
+ export type LiveHint = typeof LiveHint.Type;
11
+ export interface LiveStatus<E> {
12
+ readonly connection: "connecting" | "live" | "reconnecting";
13
+ readonly failure: Option.Option<Cause.Cause<E>>;
14
+ readonly needsResync: boolean;
15
+ readonly pending: number;
16
+ }
17
+ export interface LiveOptions<E, R> {
18
+ readonly resume?: Atom.Atom<unknown>;
19
+ readonly resyncKeys: readonly Key[];
20
+ readonly retryDelay?: Duration.Input;
21
+ readonly stream: Stream.Stream<LiveHint, E, R>;
22
+ }
23
+
24
+ const MAX_PENDING = 256;
25
+
26
+ export function live<R, E, TRuntimeError>(runtime: Atom.AtomRuntime<R, TRuntimeError>, options: LiveOptions<E, R>) {
27
+ const paused = Atom.make(false);
28
+ const status = Atom.make<LiveStatus<E>>({ connection: "connecting", failure: Option.none(), needsResync: true, pending: 0 });
29
+ const connection = runtime
30
+ .atom((get) =>
31
+ Effect.gen(function* () {
32
+ get.mount(status);
33
+ const reactivity = yield* Reactivity.Reactivity;
34
+ function update(value: Partial<LiveStatus<E>>) {
35
+ get.set(status, { ...get.once(status), ...value });
36
+ }
37
+ function resync() {
38
+ if (get.once(paused)) {
39
+ update({ needsResync: true });
40
+ return;
41
+ }
42
+ reactivity.invalidateUnsafe(readKeys(options.resyncKeys));
43
+ update({ needsResync: false, pending: 0 });
44
+ }
45
+ get.subscribe(paused, (value) => {
46
+ if (!value) {
47
+ resync();
48
+ }
49
+ });
50
+ if (options.resume !== undefined) {
51
+ // A subscription alone does not initialize a derived signal's dependencies.
52
+ get.once(options.resume);
53
+ get.subscribe(options.resume, resync);
54
+ }
55
+ const attempt = Effect.suspend(() => {
56
+ let isFirst = true;
57
+ function observe(hint: LiveHint) {
58
+ update({ connection: "live", failure: Option.none() });
59
+ if (get.once(paused)) {
60
+ update({ needsResync: true, pending: Math.min(MAX_PENDING, get.once(status).pending + 1) });
61
+ } else if (isFirst || hint._tag === "Resync") {
62
+ resync();
63
+ } else {
64
+ reactivity.invalidateUnsafe(invalidationKeys(hint.keys));
65
+ }
66
+ isFirst = false;
67
+ }
68
+ return options.stream.pipe(
69
+ Stream.runForEach((hint) => Effect.sync(() => observe(hint))),
70
+ Effect.scoped,
71
+ Effect.catchCause((failure) => Effect.sync(() => update({ failure: Option.some(failure) }))),
72
+ Effect.andThen(Effect.sync(() => update({ connection: "reconnecting", needsResync: true }))),
73
+ Effect.andThen(Effect.sleep(options.retryDelay ?? "1 second")),
74
+ );
75
+ });
76
+ yield* Effect.forever(attempt);
77
+ }),
78
+ )
79
+ .pipe(Atom.setIdleTTL(0));
80
+ return { connection, paused, status };
81
+ }
package/src/resume.ts ADDED
@@ -0,0 +1,44 @@
1
+ import * as Atom from "effect/unstable/reactivity/Atom";
2
+
3
+ export type ResumeSource = (resume: () => void) => () => void;
4
+
5
+ type ResumeEvent = "online" | "visibilitychange";
6
+
7
+ export interface ResumeWindow {
8
+ readonly addEventListener: (type: ResumeEvent, listener: () => void) => void;
9
+ readonly document: { readonly visibilityState: string };
10
+ readonly removeEventListener: (type: ResumeEvent, listener: () => void) => void;
11
+ }
12
+
13
+ export interface ResumeOptions {
14
+ readonly native?: ResumeSource;
15
+ readonly window?: ResumeWindow;
16
+ }
17
+
18
+ function whileVisible(target: ResumeWindow, type: ResumeEvent): ResumeSource {
19
+ return (resume) => {
20
+ function visible() {
21
+ if (target.document.visibilityState === "visible") {
22
+ resume();
23
+ }
24
+ }
25
+ target.addEventListener(type, visible);
26
+ return () => target.removeEventListener(type, visible);
27
+ };
28
+ }
29
+
30
+ export function resumeSignal({ window, native }: ResumeOptions): Atom.Atom<number> {
31
+ const browser = window === undefined ? [] : [whileVisible(window, "visibilitychange"), whileVisible(window, "online")];
32
+ const sources = native === undefined ? browser : [...browser, native];
33
+ return Atom.readable((get) => {
34
+ let count = 0;
35
+ function resume() {
36
+ count += 1;
37
+ get.setSelf(count);
38
+ }
39
+ for (const source of sources) {
40
+ get.addFinalizer(source(resume));
41
+ }
42
+ return count;
43
+ });
44
+ }