@shivaedev/contract 0.19.0 → 0.20.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +149 -180
  3. package/dist/automaticLive.d.ts +8 -7
  4. package/dist/automaticLive.d.ts.map +1 -1
  5. package/dist/automaticLive.js +101 -51
  6. package/dist/automaticLive.js.map +1 -1
  7. package/dist/bind.d.ts +1 -0
  8. package/dist/bind.d.ts.map +1 -1
  9. package/dist/bind.js +4 -4
  10. package/dist/bind.js.map +1 -1
  11. package/dist/clientOptions.d.ts +6 -5
  12. package/dist/clientOptions.d.ts.map +1 -1
  13. package/dist/internal/clientOperations.d.ts +2 -2
  14. package/dist/internal/clientOperations.d.ts.map +1 -1
  15. package/dist/internal/clientOperations.js +10 -14
  16. package/dist/internal/clientOperations.js.map +1 -1
  17. package/dist/internal/queryReactivity.d.ts +1 -1
  18. package/dist/internal/queryReactivity.d.ts.map +1 -1
  19. package/dist/internal/queryReactivity.js +1 -2
  20. package/dist/internal/queryReactivity.js.map +1 -1
  21. package/dist/internal/queryResult.d.ts +3 -0
  22. package/dist/internal/queryResult.d.ts.map +1 -0
  23. package/dist/internal/queryResult.js +19 -0
  24. package/dist/internal/queryResult.js.map +1 -0
  25. package/dist/internal/{httpClient.d.ts → rpcClient.d.ts} +6 -4
  26. package/dist/internal/rpcClient.d.ts.map +1 -0
  27. package/dist/internal/rpcClient.js +31 -0
  28. package/dist/internal/rpcClient.js.map +1 -0
  29. package/dist/live.d.ts +1 -0
  30. package/dist/live.d.ts.map +1 -1
  31. package/dist/live.js +14 -1
  32. package/dist/live.js.map +1 -1
  33. package/dist/makeLiveServer.js +6 -4
  34. package/dist/makeLiveServer.js.map +1 -1
  35. package/package.json +3 -3
  36. package/src/automaticLive.ts +113 -70
  37. package/src/bind.ts +7 -6
  38. package/src/clientOptions.ts +6 -5
  39. package/src/internal/clientOperations.ts +14 -25
  40. package/src/internal/queryReactivity.ts +1 -7
  41. package/src/internal/queryResult.ts +19 -0
  42. package/src/internal/rpcClient.ts +53 -0
  43. package/src/live.ts +14 -1
  44. package/src/makeLiveServer.ts +6 -6
  45. package/dist/internal/httpClient.d.ts.map +0 -1
  46. package/dist/internal/httpClient.js +0 -13
  47. package/dist/internal/httpClient.js.map +0 -1
  48. package/src/internal/httpClient.ts +0 -21
package/dist/live.js CHANGED
@@ -30,6 +30,9 @@ export function live(runtime, options) {
30
30
  get.addFinalizer(() => {
31
31
  active = false;
32
32
  clear();
33
+ if (get.registry.getNodes().has(status)) {
34
+ update({ connection: "connecting", failure: Option.none(), needsResync: true, pending: 0 });
35
+ }
33
36
  });
34
37
  function changed(changedKeys) {
35
38
  const window = get.once(coalesceWindow);
@@ -73,6 +76,15 @@ export function live(runtime, options) {
73
76
  get.once(options.resume);
74
77
  get.subscribe(options.resume, resync);
75
78
  }
79
+ let connected = false;
80
+ function reconcile() {
81
+ if (connected || options.initialResync !== false) {
82
+ resync();
83
+ }
84
+ else {
85
+ update({ needsResync: false });
86
+ }
87
+ }
76
88
  const attempt = Effect.suspend(() => {
77
89
  let isFirst = true;
78
90
  function observe(hint) {
@@ -81,11 +93,12 @@ export function live(runtime, options) {
81
93
  update({ needsResync: true, pending: Math.min(MAX_PENDING, get.once(status).pending + 1) });
82
94
  }
83
95
  else if (isFirst || hint._tag === "Resync") {
84
- resync();
96
+ reconcile();
85
97
  }
86
98
  else {
87
99
  changed(hint.keys);
88
100
  }
101
+ connected = true;
89
102
  isFirst = false;
90
103
  }
91
104
  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")));
package/dist/live.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"live.js","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"live.js","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"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"}
@@ -48,10 +48,12 @@ export const makeLiveServer = Effect.gen(function* () {
48
48
  }));
49
49
  yield* Queue.offer(queue, { _tag: "Resync" });
50
50
  function authorizeHint(hint) {
51
- if (hint._tag === "Resync") {
52
- return Effect.succeed(hint);
53
- }
54
- return Effect.map(subscription?.authorized ?? Effect.succeed(true), (allowed) => changedHint(allowed ? hint.keys : [], subscription?.scopes));
51
+ return Effect.map(subscription?.authorized ?? Effect.succeed(true), (allowed) => {
52
+ if (!allowed) {
53
+ return changedHint([], subscription?.scopes);
54
+ }
55
+ return hint._tag === "Resync" ? hint : changedHint(hint.keys, subscription?.scopes);
56
+ });
55
57
  }
56
58
  return Stream.fromQueue(queue).pipe(Stream.mapEffect(authorizeHint), Stream.filter((hint) => hint._tag === "Resync" || hint.keys.length > 0));
57
59
  }));
@@ -1 +1 @@
1
- {"version":3,"file":"makeLiveServer.js","sourceRoot":"","sources":["../src/makeLiveServer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"makeLiveServer.js","sourceRoot":"","sources":["../src/makeLiveServer.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/contract",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "Typed query and command contracts over native Effect RPC and reactivity",
5
5
  "keywords": [
6
6
  "effect"
@@ -50,10 +50,10 @@
50
50
  "devDependencies": {
51
51
  "@effect/platform-node": "4.0.5",
52
52
  "@effect/vitest": "4.0.5",
53
- "@shivaedev/quality": "0.43.0",
53
+ "@shivaedev/quality": "0.44.1",
54
54
  "@shivaedev/test": "0.10.0",
55
55
  "@shivaedev/test-sql": "0.10.2",
56
- "@shivaedev/test-story": "0.22.0",
56
+ "@shivaedev/test-story": "0.23.0",
57
57
  "@types/node": "24.19.2",
58
58
  "effect": "4.0.5",
59
59
  "esbuild": "0.28.2",
@@ -1,11 +1,14 @@
1
- import { type Duration, Effect, Stream, SubscriptionRef } from "effect";
1
+ import { type Duration, Effect, Option, Stream } from "effect";
2
+ import * as AsyncResult from "effect/reactivity/AsyncResult";
2
3
  import * as Atom from "effect/reactivity/Atom";
3
- import * as AtomRegistry from "effect/reactivity/AtomRegistry";
4
4
  import type * as AtomRpc from "effect/reactivity/AtomRpc";
5
- import { type Key, readKeys } from "@shivaedev/changes/keys.ts";
5
+ import type { Key } from "@shivaedev/changes/keys.ts";
6
6
  import type { BindingOwner } from "#internal/bindingOwner.ts";
7
7
  import { localQueryKeys, queryHint } from "#internal/queryHints.ts";
8
+ import { queryResult } from "#internal/queryResult.ts";
9
+ import { socketIdentity } from "#internal/rpcClient.ts";
8
10
  import { type LiveStatus, live } from "#live.ts";
11
+ import { queryCoalesceWindow } from "#queryCoalesceWindow.ts";
9
12
  import { type ResumeSource, type ResumeWindow, resumeSignal } from "#resume.ts";
10
13
  import type { changesRpc, QueryReference } from "#serverMetadata.ts";
11
14
 
@@ -16,83 +19,123 @@ export interface LiveBound {
16
19
  readonly [LiveConnection]: {
17
20
  readonly coalesceWindow: Atom.Writable<Duration.Input>;
18
21
  readonly connection: Atom.Atom<unknown>;
22
+ readonly identity?: Atom.Writable<string | undefined>;
19
23
  readonly status: Atom.Atom<LiveStatus<unknown>>;
20
24
  };
21
25
  }
22
26
 
23
27
  export type ChangesClient = AtomRpc.AtomRpcClient<unknown, string, ReturnType<typeof changesRpc>>;
24
28
 
25
- interface Tracked {
26
- readonly keys: readonly Key[];
27
- readonly reference: QueryReference;
28
- }
29
-
30
- export function automaticLive(service: ChangesClient, name: string, owner?: BindingOwner, resume?: ResumeSource) {
31
- const eager = owner === undefined ? Effect.runSync(SubscriptionRef.make<ReadonlyMap<object, Tracked>>(new Map())) : undefined;
32
- const known = new Set<string>();
33
- const tracked = Atom.keepAlive(Atom.make(() => eager ?? Effect.runSync(SubscriptionRef.make<ReadonlyMap<object, Tracked>>(new Map()))));
34
- function track(registry: AtomRegistry.AtomRegistry | undefined, reference: QueryReference, keys: readonly Key[]) {
35
- const fresh =
36
- eager === undefined
37
- ? keys
38
- : keys.filter((key) => {
39
- const id = readKeys([key])[0] ?? "";
40
- if (known.has(id)) {
41
- return false;
42
- }
43
- known.add(id);
44
- return true;
45
- });
46
- if (fresh.length === 0) {
47
- return () => undefined;
48
- }
49
- const token = {};
50
- const state = eager ?? registry?.get(tracked);
51
- if (state === undefined) {
52
- throw new Error("Session query tracking requires its owning registry");
53
- }
54
- Effect.runSync(SubscriptionRef.update(state, (current) => new Map([...current, [token, { keys: fresh, reference }]])));
55
- if (eager !== undefined) {
56
- return () => undefined;
29
+ export function automaticLive(service: ChangesClient, name: string, owner?: BindingOwner, resume?: ResumeSource, enabled = true) {
30
+ function assertOwner(get: Atom.AtomContext) {
31
+ if (owner !== undefined && owner.registry !== get.registry) {
32
+ throw new Error("A session-scoped query belongs to its binding registry");
57
33
  }
58
- return () =>
59
- Effect.runSync(
60
- SubscriptionRef.update(state, (current) => {
61
- const next = new Map(current);
62
- next.delete(token);
63
- return next;
64
- }),
65
- );
66
34
  }
67
- function subscribe(current: ReadonlyMap<object, Tracked>) {
68
- if (current.size === 0) {
69
- return Stream.never;
35
+ const coalesceWindow = Atom.keepAlive(Atom.make<Duration.Input>("20 millis"));
36
+ const statuses = Atom.make<ReadonlyMap<object, LiveStatus<unknown>>>(new Map());
37
+ const status = Atom.map(statuses, (current): LiveStatus<unknown> => {
38
+ const values = [...current.values()];
39
+ let connection: LiveStatus<unknown>["connection"] = "live";
40
+ if (values.some((value) => value.connection === "connecting")) {
41
+ connection = "connecting";
70
42
  }
71
- const queries = [...current.values()].map(({ reference }) => reference);
72
- return Stream.unwrap(
73
- service.use((client) =>
74
- Effect.succeed(client(`${name}.$changes`, { queries }).pipe(Stream.map((hint) => queryHint(hint, [...current.values()])))),
75
- ),
76
- );
77
- }
78
- const signal = resumeSignal({ ...(typeof window === "undefined" ? {} : { window }), ...(resume === undefined ? {} : { native: resume }) });
79
- const subscription = live(service.runtime, {
80
- keyContext: owner?.context,
81
- resume: signal,
82
- resyncKeys: (registry) => [...Effect.runSync(SubscriptionRef.get(registry.get(tracked))).values()].flatMap(({ keys }) => localQueryKeys(keys)),
83
- stream: Stream.unwrap(
84
- Effect.map(AtomRegistry.AtomRegistry, (registry) =>
85
- Stream.zipLatest(SubscriptionRef.changes(registry.get(tracked)), AtomRegistry.toStream(registry, signal)).pipe(
86
- Stream.switchMap(([current]) => subscribe(current)),
87
- ),
88
- ),
89
- ),
43
+ if (values.some((value) => value.connection === "reconnecting")) {
44
+ connection = "reconnecting";
45
+ }
46
+ return {
47
+ connection,
48
+ failure: values.find((value) => Option.isSome(value.failure))?.failure ?? Option.none(),
49
+ needsResync: values.some((value) => value.needsResync),
50
+ pending: values.reduce((sum, value) => sum + value.pending, 0),
51
+ };
90
52
  });
53
+ const signal = resumeSignal({ ...(typeof window === "undefined" ? {} : { window }), ...(resume === undefined ? {} : { native: resume }) });
91
54
  const connection = Atom.make((get) => {
92
- if (owner !== undefined && owner.registry !== get.registry) {
93
- throw new Error("A session-scoped live connection belongs to its binding registry");
94
- }
95
- return get(subscription.connection);
55
+ assertOwner(get);
56
+ get.mount(service.runtime);
57
+ return get(status);
96
58
  }).pipe(Atom.setIdleTTL(0));
97
- return { subscription: { ...subscription, connection }, track };
59
+ function query<A, E>(
60
+ atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>,
61
+ reference: QueryReference,
62
+ keys: readonly Key[],
63
+ equivalent: (left: A, right: A) => boolean,
64
+ ) {
65
+ if (!enabled) {
66
+ return atom;
67
+ }
68
+ const ready = Atom.make(false);
69
+ const subscription = live(service.runtime, {
70
+ initialResync: false,
71
+ keyContext: owner?.context,
72
+ resume: signal,
73
+ resyncKeys: localQueryKeys(keys),
74
+ stream: Stream.unwrap(service.use((client) => Effect.succeed(client(`${name}.$changes`, { queries: [reference] })))).pipe(
75
+ Stream.map((hint) => queryHint(hint, [{ keys }])),
76
+ ),
77
+ });
78
+ const gated = Atom.readable(
79
+ (get) => {
80
+ assertOwner(get);
81
+ const registry = get.registry;
82
+ get.set(ready, false);
83
+ get.once(queryCoalesceWindow(gated));
84
+ get.subscribe(queryCoalesceWindow(gated), (value) => get.set(queryCoalesceWindow(atom), value), { immediate: true });
85
+ const token = {};
86
+ function observe(value: LiveStatus<unknown>) {
87
+ registry.update(statuses, (current) => new Map([...current, [token, value]]));
88
+ if (value.connection === "live") {
89
+ registry.set(ready, true);
90
+ }
91
+ }
92
+ get.addFinalizer(() => {
93
+ if (registry.getNodes().has(statuses)) {
94
+ registry.update(statuses, (current) => {
95
+ const next = new Map(current);
96
+ next.delete(token);
97
+ return next;
98
+ });
99
+ }
100
+ });
101
+ get.addFinalizer(registry.subscribe(subscription.status, observe, { immediate: true }));
102
+ get.addFinalizer(registry.subscribe(coalesceWindow, (value) => registry.set(subscription.coalesceWindow, value), { immediate: true }));
103
+ let started = false;
104
+ function start() {
105
+ if (started) {
106
+ return;
107
+ }
108
+ started = true;
109
+ get.refresh(atom);
110
+ const publish = queryResult<A, E>(equivalent, (value) => get.setSelf(value));
111
+ get.subscribe(atom, publish);
112
+ publish(get.once(atom));
113
+ }
114
+ get.once(ready);
115
+ get.subscribe(ready, (value) => {
116
+ if (value) {
117
+ start();
118
+ }
119
+ });
120
+ get.subscribe(subscription.status, (value) => {
121
+ if (!started && Option.isSome(value.failure)) {
122
+ get.setSelf(AsyncResult.failure(value.failure.value));
123
+ }
124
+ });
125
+ get.mount(subscription.connection);
126
+ if (get.once(ready)) {
127
+ start();
128
+ return get.once(atom);
129
+ }
130
+ return AsyncResult.initial();
131
+ },
132
+ (refresh) => refresh(atom),
133
+ ).pipe(Atom.setIdleTTL(0));
134
+ Object.assign(gated, { initialValueTarget: atom });
135
+ if (Atom.isSerializable(atom)) {
136
+ Object.assign(gated, { [Atom.SerializableTypeId]: atom[Atom.SerializableTypeId] });
137
+ }
138
+ return gated;
139
+ }
140
+ return { query, subscription: { coalesceWindow, connection, identity: socketIdentity, status } };
98
141
  }
package/src/bind.ts CHANGED
@@ -12,7 +12,7 @@ import type { ClientOptions, MadeClient } from "#clientOptions.ts";
12
12
  import type { Contract, Declared, Tag } from "#contract.ts";
13
13
  import { bindingOwner } from "#internal/bindingOwner.ts";
14
14
  import { boundCommand, boundQuery, boundStream } from "#internal/clientOperations.ts";
15
- import { type HttpGroup, httpClient } from "#internal/httpClient.ts";
15
+ import { type RpcSource, rpcClient } from "#internal/rpcClient.ts";
16
16
  import type { CommandShape, QueryShape, StreamShape } from "#operation.ts";
17
17
  import type { MissingReadPolicy } from "#readPolicyErrors.ts";
18
18
  import type { ResumeSource } from "#resume.ts";
@@ -71,6 +71,7 @@ type MatchingClient<TRpcs extends Rpc.Any, TClientRpcs extends Rpc.Any> = [TRpcs
71
71
  : "Client must implement the contract RPCs";
72
72
 
73
73
  interface BindOptions {
74
+ readonly live?: boolean;
74
75
  readonly registry?: AtomRegistry.AtomRegistry;
75
76
  readonly resume?: ResumeSource;
76
77
  }
@@ -105,10 +106,10 @@ function bindApi(
105
106
  ) {
106
107
  const { name, queries, commands, streams, keyScope } = contract.declaration;
107
108
  const owner = bindingOwner(keyScope, options?.registry);
108
- const automatic = automaticLive(service, name, owner, options?.resume);
109
+ const automatic = automaticLive(service, name, owner, options?.resume, options?.live);
109
110
  return Object.assign(
110
111
  Object.fromEntries([
111
- ...queries.map((query) => [query.name, boundQuery(service, `${name}.${query.name}`, query, automatic.track, owner)] as const),
112
+ ...queries.map((query) => [query.name, boundQuery(service, `${name}.${query.name}`, query, automatic, owner)] as const),
112
113
  ...commands.map((command) => [command.name, boundCommand(service, `${name}.${command.name}`, command, owner)] as const),
113
114
  ...streams.map((stream) => [stream.name, boundStream(service, `${name}.${stream.name}`)] as const),
114
115
  ]),
@@ -131,15 +132,15 @@ export function makeClient<
131
132
  : { readonly registry?: AtomRegistry.AtomRegistry }),
132
133
  ): MadeClient<TName, TQueries, TCommands, TRpcs, TStreams, TExtra>;
133
134
  export function makeClient(
134
- contract: HttpGroup & {
135
+ contract: RpcSource & {
135
136
  readonly declaration: Declared<string, readonly QueryShape[], readonly CommandShape[], readonly StreamShape[], "session" | undefined>;
136
137
  },
137
138
  options: ClientOptions & {
138
- readonly group?: HttpGroup;
139
+ readonly group?: RpcSource;
139
140
  readonly registry?: AtomRegistry.AtomRegistry;
140
141
  },
141
142
  ): Layer.Layer<never> {
142
- const connection = httpClient(contract.declaration.name, options.group ?? contract, options);
143
+ const connection = rpcClient(contract.declaration.name, options.group ?? contract, options);
143
144
  const api = bindApi(contract, connection.Client, options);
144
145
  return Object.assign(connection, { api });
145
146
  }
@@ -1,18 +1,19 @@
1
1
  import type { Context, Effect, Layer, Scope } from "effect";
2
- import type { HttpClient } from "effect/http/HttpClient";
3
2
  import type * as Atom from "effect/reactivity/Atom";
4
3
  import type * as AtomRpc from "effect/reactivity/AtomRpc";
5
- import type { Rpc, RpcClient, RpcSerialization } from "effect/rpc";
4
+ import type { Rpc, RpcClient } from "effect/rpc";
6
5
  import type { RpcClientError } from "effect/rpc/RpcClientError";
6
+ import type * as Socket from "effect/socket/Socket";
7
7
  import type { LiveBound } from "#automaticLive.ts";
8
8
  import type { Bound } from "#bind.ts";
9
9
  import type { CommandShape, QueryShape, StreamShape } from "#operation.ts";
10
10
 
11
11
  export interface ClientOptions {
12
- readonly httpClient?: Layer.Layer<HttpClient>;
13
- readonly serialization?: Layer.Layer<RpcSerialization.RpcSerialization>;
14
- readonly transformClient?: NonNullable<Parameters<typeof RpcClient.layerProtocolHttp>[0]["transformClient"]>;
12
+ readonly headers?: Readonly<Record<string, string>>;
13
+ readonly live?: boolean;
14
+ readonly protocol?: Layer.Layer<RpcClient.Protocol>;
15
15
  readonly url: string;
16
+ readonly webSocketConstructor?: Layer.Layer<Socket.WebSocketConstructor>;
16
17
  }
17
18
 
18
19
  export type ClientService<TName extends string, TRpcs extends Rpc.Any> = Context.ServiceClass.Shape<
@@ -1,4 +1,4 @@
1
- import { Effect, Option, type Schema, Stream } from "effect";
1
+ import { Effect, Option, Schema, Stream } from "effect";
2
2
  import * as AtomRegistry from "effect/reactivity/AtomRegistry";
3
3
  import type * as AtomRpc from "effect/reactivity/AtomRpc";
4
4
  import * as Reactivity from "effect/reactivity/Reactivity";
@@ -13,9 +13,6 @@ import { queryReactivity } from "./queryReactivity.ts";
13
13
 
14
14
  type ErasedClient = AtomRpc.AtomRpcClient<unknown, string, Rpc.Rpc<string, Schema.Top, Schema.Top, Schema.Top>>;
15
15
 
16
- const queryIds = new WeakMap<object, number>();
17
- let nextQueryId = 0;
18
-
19
16
  type ErasedStreamingClient = AtomRpc.AtomRpcClient<
20
17
  unknown,
21
18
  string,
@@ -26,14 +23,9 @@ function run<A, E>(service: ErasedClient, effect: Effect.Effect<A, E, unknown>,
26
23
  return owner === undefined ? effect : Effect.suspend(() => AtomRegistry.getResult(owner.registry, service.runtime.atom(effect)));
27
24
  }
28
25
 
29
- export function boundQuery(
30
- service: ErasedClient,
31
- tag: string,
32
- query: QueryShape,
33
- track: ReturnType<typeof automaticLive>["track"],
34
- owner?: BindingOwner,
35
- ) {
26
+ export function boundQuery(service: ErasedClient, tag: string, query: QueryShape, automatic: ReturnType<typeof automaticLive>, owner?: BindingOwner) {
36
27
  const reactive = queryReactivity(service.runtime);
28
+ const cached = new WeakMap<ReturnType<typeof service.query>, ReturnType<typeof service.query>>();
37
29
  return {
38
30
  query: (payload: unknown, options: QueryOptions = {}) => {
39
31
  if (query.readPolicy === undefined) {
@@ -42,26 +34,23 @@ export function boundQuery(
42
34
  );
43
35
  }
44
36
  const atom = service.query(tag, payload, options);
45
- let id = queryIds.get(atom);
46
- if (id === undefined) {
47
- id = nextQueryId;
48
- nextQueryId += 1;
49
- queryIds.set(atom, id);
37
+ const existing = cached.get(atom);
38
+ if (existing !== undefined) {
39
+ return existing;
50
40
  }
51
41
  const local = query.reads(payload, Option.none());
52
- const keys = [...local, { _tag: "List" as const, collection: `@query/${tag}/${id}` }];
42
+ const keys = [...local, { _tag: "List" as const, collection: `@query/${tag}/${crypto.randomUUID()}` }];
53
43
  const dependencies =
54
44
  local.length === 0 ? [...keys, { _tag: "List" as const, collection: `@query/${tag.slice(0, tag.lastIndexOf("."))}` }] : keys;
55
45
  const reference = { payload, query: query.name };
56
- if (owner === undefined) {
57
- track(undefined, reference, keys);
58
- }
59
- return reactive(
60
- atom,
61
- readKeys(dependencies, owner?.context),
62
- (registry) => (owner === undefined ? () => undefined : track(registry, reference, keys)),
63
- owner?.registry,
46
+ const result = automatic.query(
47
+ reactive(atom, readKeys(dependencies, owner?.context), owner?.registry),
48
+ reference,
49
+ keys,
50
+ Schema.toEquivalence(query.success),
64
51
  );
52
+ cached.set(atom, result);
53
+ return result;
65
54
  },
66
55
  run: (payload: unknown) =>
67
56
  run(
@@ -10,12 +10,7 @@ export function queryReactivity<R, E>(runtime: Atom.AtomRuntime<R, E>) {
10
10
  const service = runtime.atom(Effect.service(Reactivity.Reactivity));
11
11
  type Query = Atom.Atom<AsyncResult.AsyncResult<unknown, unknown>>;
12
12
  const cached = new WeakMap<Query, Query>();
13
- return (
14
- atom: Query,
15
- keys: readonly string[],
16
- track: (registry: AtomRegistry.AtomRegistry) => () => void,
17
- owner?: AtomRegistry.AtomRegistry,
18
- ): Query => {
13
+ return (atom: Query, keys: readonly string[], owner?: AtomRegistry.AtomRegistry): Query => {
19
14
  const existing = cached.get(atom);
20
15
  if (existing !== undefined) {
21
16
  return existing;
@@ -35,7 +30,6 @@ export function queryReactivity<R, E>(runtime: Atom.AtomRuntime<R, E>) {
35
30
  return get(source);
36
31
  }
37
32
  const reactivity = context.value;
38
- get.addFinalizer(track(get.registry));
39
33
  let pending: (() => boolean) | undefined;
40
34
  let timer: ReturnType<typeof setTimeout> | undefined;
41
35
  function clear() {
@@ -0,0 +1,19 @@
1
+ import * as AsyncResult from "effect/reactivity/AsyncResult";
2
+
3
+ export function queryResult<A, E>(equivalent: (left: A, right: A) => boolean, publish: (value: AsyncResult.AsyncResult<A, E>) => void) {
4
+ let settled: AsyncResult.Success<A, E> | undefined;
5
+ return (value: AsyncResult.AsyncResult<A, E>) => {
6
+ if (!AsyncResult.isSuccess(value)) {
7
+ publish(value);
8
+ return;
9
+ }
10
+ if (settled !== undefined && equivalent(settled.value, value.value)) {
11
+ publish(value.waiting ? AsyncResult.waiting(settled) : settled);
12
+ return;
13
+ }
14
+ if (!value.waiting) {
15
+ settled = value;
16
+ }
17
+ publish(value);
18
+ };
19
+ }
@@ -0,0 +1,53 @@
1
+ import { type Context, Effect, Layer } from "effect";
2
+ import * as Atom from "effect/reactivity/Atom";
3
+ import * as AtomRpc from "effect/reactivity/AtomRpc";
4
+ import { type Rpc, RpcClient, RpcGroup, RpcSerialization } from "effect/rpc";
5
+ import * as Socket from "effect/socket/Socket";
6
+ import type { ClientOptions } from "#clientOptions.ts";
7
+
8
+ export interface RpcSource {
9
+ readonly annotations: Context.Context<never>;
10
+ readonly requests: ReadonlyMap<string, Rpc.Any>;
11
+ }
12
+
13
+ export function rpcClient(name: string, source: RpcSource, options: ClientOptions) {
14
+ const group = RpcGroup.make(...source.requests.values()).annotateMerge(source.annotations);
15
+ function sharedProtocol(identity?: string) {
16
+ return socketProtocol({
17
+ headers: options.headers,
18
+ identity,
19
+ url: options.url.replace(/^http/u, "ws"),
20
+ webSocketConstructor: options.webSocketConstructor,
21
+ });
22
+ }
23
+ const protocol = options.protocol ?? sharedProtocol();
24
+ const Client = AtomRpc.Service<unknown>()(`${name}/Client`, {
25
+ group,
26
+ protocol: options.protocol ?? ((get) => sharedProtocol(get(socketIdentity))),
27
+ });
28
+ const client = Effect.flatMap(Layer.build(protocol), (context) => RpcClient.make(group).pipe(Effect.provideContext(context)));
29
+ const layer = Layer.effect(Client, RpcClient.make(group, { flatten: true })).pipe(Layer.provide(protocol));
30
+ return Object.assign(layer, { Client, client, runtime: Client.runtime });
31
+ }
32
+
33
+ export const socketIdentity = Atom.make<string | undefined>(undefined);
34
+
35
+ const socketMemoMap = Layer.makeMemoMapUnsafe();
36
+ const socketProtocol = Atom.family(
37
+ (options: {
38
+ readonly url: string;
39
+ readonly headers: ClientOptions["headers"];
40
+ readonly identity: string | undefined;
41
+ readonly webSocketConstructor: ClientOptions["webSocketConstructor"];
42
+ }) => {
43
+ const protocol = RpcClient.layerProtocolSocket().pipe(
44
+ Layer.provide(
45
+ Socket.layerWebSocket(options.url, { headers: options.headers }).pipe(
46
+ Layer.provide(options.webSocketConstructor ?? Socket.layerWebSocketConstructorGlobal),
47
+ ),
48
+ ),
49
+ Layer.provide(RpcSerialization.layerNdjson),
50
+ );
51
+ return Layer.effectContext(Effect.flatMap(Effect.scope, (scope) => Layer.buildWithMemoMap(protocol, socketMemoMap, scope)));
52
+ },
53
+ );
package/src/live.ts CHANGED
@@ -18,6 +18,7 @@ export interface LiveStatus<E> {
18
18
  }
19
19
  export interface LiveOptions<E, R> {
20
20
  readonly coalesceWindow?: Duration.Input;
21
+ readonly initialResync?: boolean;
21
22
  readonly keyContext?: KeyContext | undefined;
22
23
  readonly resume?: Atom.Atom<unknown>;
23
24
  readonly resyncKeys: readonly Key[] | ((registry: AtomRegistry.AtomRegistry) => readonly Key[]);
@@ -50,6 +51,9 @@ export function live<R, E, TRuntimeError>(runtime: Atom.AtomRuntime<R, TRuntimeE
50
51
  get.addFinalizer(() => {
51
52
  active = false;
52
53
  clear();
54
+ if (get.registry.getNodes().has(status)) {
55
+ update({ connection: "connecting", failure: Option.none(), needsResync: true, pending: 0 });
56
+ }
53
57
  });
54
58
  function changed(changedKeys: readonly Key[]) {
55
59
  const window = get.once(coalesceWindow);
@@ -93,6 +97,14 @@ export function live<R, E, TRuntimeError>(runtime: Atom.AtomRuntime<R, TRuntimeE
93
97
  get.once(options.resume);
94
98
  get.subscribe(options.resume, resync);
95
99
  }
100
+ let connected = false;
101
+ function reconcile() {
102
+ if (connected || options.initialResync !== false) {
103
+ resync();
104
+ } else {
105
+ update({ needsResync: false });
106
+ }
107
+ }
96
108
  const attempt = Effect.suspend(() => {
97
109
  let isFirst = true;
98
110
  function observe(hint: LiveHint) {
@@ -100,10 +112,11 @@ export function live<R, E, TRuntimeError>(runtime: Atom.AtomRuntime<R, TRuntimeE
100
112
  if (get.once(paused)) {
101
113
  update({ needsResync: true, pending: Math.min(MAX_PENDING, get.once(status).pending + 1) });
102
114
  } else if (isFirst || hint._tag === "Resync") {
103
- resync();
115
+ reconcile();
104
116
  } else {
105
117
  changed(hint.keys);
106
118
  }
119
+ connected = true;
107
120
  isFirst = false;
108
121
  }
109
122
  return options.stream.pipe(
@@ -56,12 +56,12 @@ export const makeLiveServer = Effect.gen(function* () {
56
56
  );
57
57
  yield* Queue.offer(queue, { _tag: "Resync" });
58
58
  function authorizeHint(hint: LiveHint) {
59
- if (hint._tag === "Resync") {
60
- return Effect.succeed(hint);
61
- }
62
- return Effect.map(subscription?.authorized ?? Effect.succeed(true), (allowed) =>
63
- changedHint(allowed ? hint.keys : [], subscription?.scopes),
64
- );
59
+ return Effect.map(subscription?.authorized ?? Effect.succeed(true), (allowed) => {
60
+ if (!allowed) {
61
+ return changedHint([], subscription?.scopes);
62
+ }
63
+ return hint._tag === "Resync" ? hint : changedHint(hint.keys, subscription?.scopes);
64
+ });
65
65
  }
66
66
  return Stream.fromQueue(queue).pipe(
67
67
  Stream.mapEffect(authorizeHint),
@@ -1 +0,0 @@
1
- {"version":3,"file":"httpClient.d.ts","sourceRoot":"","sources":["../../src/internal/httpClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAErD,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,KAAK,GAAG,EAAyC,MAAM,YAAY,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7C,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;CAChD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa;;;;;;;;;;;EASjF"}