@shivaedev/effect-contract 0.7.0 → 0.8.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 +17 -0
- package/README.md +26 -24
- package/dist/automaticLive.d.ts +3 -0
- package/dist/automaticLive.d.ts.map +1 -1
- package/dist/automaticLive.js.map +1 -1
- package/dist/bind.d.ts.map +1 -1
- package/dist/bind.js +3 -1
- package/dist/bind.js.map +1 -1
- package/dist/internal/makeHintPolicy.d.ts +4 -0
- package/dist/internal/makeHintPolicy.d.ts.map +1 -0
- package/dist/internal/makeHintPolicy.js +25 -0
- package/dist/internal/makeHintPolicy.js.map +1 -0
- package/dist/internal/queryReactivity.d.ts +6 -0
- package/dist/internal/queryReactivity.d.ts.map +1 -0
- package/dist/internal/queryReactivity.js +72 -0
- package/dist/internal/queryReactivity.js.map +1 -0
- package/dist/live.d.ts +3 -1
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +35 -3
- package/dist/live.js.map +1 -1
- package/dist/makeLiveServer.d.ts.map +1 -1
- package/dist/makeLiveServer.js +3 -3
- package/dist/makeLiveServer.js.map +1 -1
- package/dist/readPolicy.d.ts +5 -4
- package/dist/readPolicy.d.ts.map +1 -1
- package/dist/readPolicy.js +3 -0
- package/dist/readPolicy.js.map +1 -1
- package/package.json +4 -2
- package/src/automaticLive.ts +6 -2
- package/src/bind.ts +3 -1
- package/src/internal/makeHintPolicy.ts +40 -0
- package/src/internal/queryReactivity.ts +84 -0
- package/src/live.ts +36 -3
- package/src/makeLiveServer.ts +5 -3
- package/src/readPolicy.ts +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0 - 2026-10-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `authorizeReads` accepts typed policy failures and preserves them in the query's error channel. Declare those errors in the native RPC middleware's error schema alongside its denial so clients can distinguish an outage from denied access.
|
|
8
|
+
- `ReadPolicyMemoWindow`, a `Context.Reference<Duration.Input>`, configures per-subscription, per-key memoisation of successful policy results. The default is one second; provide a shorter duration or `0` to disable it. Queries always check afresh, and failures are never memoised.
|
|
9
|
+
- Coalesce live changed hints over a default 20 ms window, with one follow-up read when hints arrive during an in-flight refetch. Configure `live()` with `coalesceWindow`, or set its returned `coalesceWindow` atom; zero retains immediate invalidation. Reconciliation stays immediate.
|
|
10
|
+
- Configure automatic live connections per React `SessionBoundary` with `liveCoalesceWindow`.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Failed live hint checks omit the affected keys and keep the stream open. Scoped rechecks after one second deliver an allowed key after recovery without another change or reconnect; a `false` does not schedule a recheck.
|
|
15
|
+
|
|
16
|
+
### Breaking
|
|
17
|
+
|
|
18
|
+
- Direct readers of `ReadPolicy` now receive `Effect<boolean, unknown>` instead of an infallible Effect. Handle its error channel when invoking the reference directly. Existing infallible policies passed to `authorizeReads` still work; fallible policies should declare their errors in their RPC middleware schema.
|
|
19
|
+
|
|
3
20
|
## 0.7.0 - 2026-10-19
|
|
4
21
|
|
|
5
22
|
### Added
|
package/README.md
CHANGED
|
@@ -236,32 +236,19 @@ For a custom transport or explicit pause/status controls, the native live APIs r
|
|
|
236
236
|
|
|
237
237
|
Declare subscriptions with `stream` in the same contract. Its `success` schema describes each element, and its `rejections` become the native stream error schema. `bind` exposes `run(payload)` as a native `Stream`, preserving declared rejections, middleware errors and RPC client errors. Completion and client interruption release the server subscription. The compiler rejects a unary client for a streaming operation.
|
|
238
238
|
|
|
239
|
-
|
|
239
|
+
Each item query reads its own item key, and each list query reads its list key. Commands invalidate the keys they change; an item invalidation also refreshes its collection list. Reuse the feature's `Get`, `List`, `Rename` and `Create` declarations:
|
|
240
240
|
|
|
241
241
|
```ts
|
|
242
|
-
import { Schema } from "effect";
|
|
243
242
|
import { contract } from "@shivaedev/effect-contract/contract.ts";
|
|
244
|
-
import { collection } from "@shivaedev/effect-contract/keys.ts";
|
|
245
243
|
import { LiveHint } from "@shivaedev/effect-contract/live.ts";
|
|
246
|
-
import {
|
|
244
|
+
import { stream } from "@shivaedev/effect-contract/operation.ts";
|
|
247
245
|
import { Create, Get, List, notes, Rename } from "./notes.ts";
|
|
248
246
|
|
|
249
|
-
export const
|
|
250
|
-
const
|
|
251
|
-
payload: Get.payload,
|
|
252
|
-
success: Get.success,
|
|
253
|
-
rejections: Get.rejections,
|
|
254
|
-
reads: ({ id }) => [notes.item(id), workspace.list],
|
|
255
|
-
});
|
|
256
|
-
const LiveList = query("list", {
|
|
257
|
-
success: List.success,
|
|
258
|
-
reads: () => [notes.list, workspace.list],
|
|
259
|
-
});
|
|
260
|
-
export const Subscribe = stream("subscribe", { reads: () => [workspace.list], success: LiveHint });
|
|
261
|
-
export const LiveNotes = contract("notes", { queries: [LiveGet, LiveList], commands: [Rename, Create], streams: [Subscribe] });
|
|
247
|
+
export const Subscribe = stream("subscribe", { reads: () => [notes.list], success: LiveHint });
|
|
248
|
+
export const LiveNotes = contract("notes", { queries: [Get, List], commands: [Rename, Create], streams: [Subscribe] });
|
|
262
249
|
```
|
|
263
250
|
|
|
264
|
-
Save the shared module as `live-notes.ts`.
|
|
251
|
+
Save the shared module as `live-notes.ts`. Keep full reconciliation in the session's explicit `resyncKeys`: enumerate the item keys the session reads and its list keys. A list key alone does not cover separately registered item queries. Having every query read a broad shared key makes every invalidation of that key refetch them all.
|
|
265
252
|
|
|
266
253
|
Use `LiveNotes` as the server group and implement `notes.subscribe` with a native Stream. For streaming HTTP, use `RpcSerialization.layerNdjson` on both sides. This client mounts live coordination alongside its queries:
|
|
267
254
|
|
|
@@ -274,7 +261,8 @@ import { RpcClient, RpcSerialization } from "effect/rpc";
|
|
|
274
261
|
import { bind } from "@shivaedev/effect-contract/bind.ts";
|
|
275
262
|
import { live } from "@shivaedev/effect-contract/live.ts";
|
|
276
263
|
import { resumeSignal } from "@shivaedev/effect-contract/resume.ts";
|
|
277
|
-
import { LiveNotes
|
|
264
|
+
import { LiveNotes } from "./live-notes.ts";
|
|
265
|
+
import { notes } from "./notes.ts";
|
|
278
266
|
|
|
279
267
|
class Client extends AtomRpc.Service<Client>()("app/LiveNotes", {
|
|
280
268
|
group: LiveNotes,
|
|
@@ -286,7 +274,7 @@ const api = bind(LiveNotes, Client);
|
|
|
286
274
|
const registry = AtomRegistry.make();
|
|
287
275
|
const updates = live(Client.runtime, {
|
|
288
276
|
stream: api.subscribe.run(undefined),
|
|
289
|
-
resyncKeys:
|
|
277
|
+
resyncKeys: [notes.item(1), notes.item(2), notes.list],
|
|
290
278
|
resume: resumeSignal({ window }),
|
|
291
279
|
retryDelay: "100 millis",
|
|
292
280
|
});
|
|
@@ -294,7 +282,7 @@ const releaseQuery = registry.mount(api.get.query({ id: 1 }));
|
|
|
294
282
|
const releaseConnection = registry.mount(updates.connection);
|
|
295
283
|
```
|
|
296
284
|
|
|
297
|
-
`LiveNotes` supplies both the declaration for binding and the native RPC group for transport. Stream read keys describe its dependencies; `run` does not mount query atoms or trigger automatic invalidation. Here the caller
|
|
285
|
+
`LiveNotes` supplies both the declaration for binding and the native RPC group for transport. Stream read keys describe its dependencies; `run` does not mount query atoms or trigger automatic invalidation. Here the caller explicitly reconciles two items and their list. `bind` also accepts a larger native client group that preserves the contract RPC types. The [live updates guide](https://github.com/ShivaeDev/platform/blob/main/docs/framework/live-updates.md) shows server and client composition, and the [HTTP fixture](https://github.com/ShivaeDev/platform/tree/main/packages/effect-contract/src/test-support/live) supplies a complete working server.
|
|
298
286
|
|
|
299
287
|
| Observation | Invalidation |
|
|
300
288
|
| --- | --- |
|
|
@@ -302,6 +290,8 @@ const releaseConnection = registry.mount(updates.connection);
|
|
|
302
290
|
| `Resync` | The exact `resyncKeys` read scope. |
|
|
303
291
|
| First hint on a new or reconnected stream | The full explicit scope, even if the hint is targeted. |
|
|
304
292
|
|
|
293
|
+
Changed hints share a fixed 20 ms window by default. Their keys merge, and each affected bound query refetches once per window. A bound query already fetching keeps its request and refetches once more after it settles if further hints arrive. Set `coalesceWindow` in `live()` to a native Duration, or set `SessionBoundary`'s `liveCoalesceWindow` for automatic session connections. Zero restores immediate per-hint invalidation, including interruption of in-flight reads. Full reconciliation on first hint, reconnect, resume and unpause stays immediate. Registry teardown cancels a pending window.
|
|
294
|
+
|
|
305
295
|
`LiveHint` and `Key` are schemas. Decoding rejects unknown hint tags and item keys with an invalid ID type.
|
|
306
296
|
|
|
307
297
|
```ts
|
|
@@ -342,10 +332,22 @@ const guard = Layer.succeed(Guard, (effect, request) =>
|
|
|
342
332
|
);
|
|
343
333
|
```
|
|
344
334
|
|
|
345
|
-
The application supplies `resolveSession`, `canRead` and its middleware's `Denied` failure. Apply this middleware to the contract, including its reserved change stream. The policy returns
|
|
335
|
+
The application supplies `resolveSession`, `canRead` and its middleware's `Denied` failure. Apply this middleware to the contract, including its reserved change stream. The policy returns `Effect<boolean, PolicyError, PolicyServices>` for a key. `authorizeReads` checks every declared query read key before running its handler and supplies the same policy to `makeLiveServer` streams, with the request's Effect context. Queries need no additional authorization declaration. A `false` returns the supplied denial; a failed policy returns its original typed error. Declare policy errors alongside the denial in the native middleware's error schema so they reach the client, for example `error: Schema.Union([Denied, GitHubUnavailable])`.
|
|
346
336
|
|
|
347
337
|
Each changed hint checks its keys against that viewer's policy before delivery and omits denied keys. An empty hint is suppressed. Item and list keys are checked independently: allow a list key when the viewer may read that list, even when a changed item is private to another viewer. The list query still selects its visible rows. Without a supplied `ReadPolicy`, streams keep the default of allowing their subscribed keys; applications own the policy.
|
|
348
338
|
|
|
339
|
+
A failed hint check omits that key without ending the stream. The subscription rechecks failed keys after one second and repeats while checks fail. Once allowed, the key reaches the existing consumer even without another change or a reconnect. A `false` does not schedule a recheck. Interruption of the subscription stops its pending rechecks.
|
|
340
|
+
|
|
341
|
+
Successful boolean results are memoised per subscription stream and key for one second by default, including `false`; failures are never memoised. Queries always run their policy afresh. The memo holds at most the subscription's key count. It can delay filtering a revoked key from hints by at most that window; hints contain keys, and the subsequent query still checks current access before returning data. Set `ReadPolicyMemoWindow` around the request effect to shorten the window or use `0` to turn memoisation off:
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
import { ReadPolicyMemoWindow } from "@shivaedev/effect-contract/readPolicy.ts";
|
|
345
|
+
|
|
346
|
+
authorizeReads(effect, request, canRead, Effect.fail(new Denied())).pipe(
|
|
347
|
+
Effect.provideService(ReadPolicyMemoWindow, "100 millis"),
|
|
348
|
+
);
|
|
349
|
+
```
|
|
350
|
+
|
|
349
351
|
### API by module
|
|
350
352
|
|
|
351
353
|
Import the module that defines the name; the package has no root entry.
|
|
@@ -357,9 +359,9 @@ Import the module that defines the name; the package has no root entry.
|
|
|
357
359
|
| `rejection.ts` | `fieldRejection(struct, fields?)`; lower-level `rejectionSet(specs)`, which constructs the rejection classes, union schema and failure helpers used by operations. |
|
|
358
360
|
| `keys.ts` | `collection(name, idSchema)`; `Key` schema; `readKeys(keys)` and `invalidationKeys(keys)`. |
|
|
359
361
|
| `bind.ts` | `bind(contract, atomRpcService)`, exposing `{ query, run }` for queries, `{ run }` Effects for commands and `{ run }` native Streams for streams. |
|
|
360
|
-
| `live.ts` | `LiveHint` schema; `live(runtime, { stream, resyncKeys, resume?, retryDelay? })`, exposing `connection`, `paused` and `status`. |
|
|
362
|
+
| `live.ts` | `LiveHint` schema; `live(runtime, { stream, resyncKeys, coalesceWindow?, resume?, retryDelay? })`, exposing `connection`, `coalesceWindow`, `paused` and `status`. |
|
|
361
363
|
| `makeLiveServer.ts` | `makeLiveServer`, an Effect requiring `Reactivity` that creates scoped server hint streams and a `resync` Effect. |
|
|
362
|
-
| `readPolicy.ts` | `authorizeReads(effect, request, policy, denied)`; `ReadPolicy`, the request-local key policy used by server hint streams. |
|
|
364
|
+
| `readPolicy.ts` | `authorizeReads(effect, request, policy, denied)`; `ReadPolicy`, the request-local fallible key policy used by server hint streams; `ReadPolicyMemoWindow`, a `Context.Reference<Duration.Input>` controlling each stream's memo duration. |
|
|
363
365
|
| `serverMetadata.ts` | `QueryReads`, `CommandInvalidation` and `changesRpc(name)` for server adapters. |
|
|
364
366
|
| `resume.ts` | `resumeSignal({ window?, native? })`, returning a native numeric signal atom. |
|
|
365
367
|
|
package/dist/automaticLive.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Duration } from "effect";
|
|
1
2
|
import type * as Atom from "effect/reactivity/Atom";
|
|
2
3
|
import type * as AtomRpc from "effect/reactivity/AtomRpc";
|
|
3
4
|
import { type Key } from "./keys.ts";
|
|
@@ -6,6 +7,7 @@ import type { changesRpc } from "./serverMetadata.ts";
|
|
|
6
7
|
export declare const LiveConnection: unique symbol;
|
|
7
8
|
export interface LiveBound {
|
|
8
9
|
readonly [LiveConnection]: {
|
|
10
|
+
readonly coalesceWindow: Atom.Writable<Duration.Input>;
|
|
9
11
|
readonly connection: Atom.Atom<unknown>;
|
|
10
12
|
readonly status: Atom.Atom<LiveStatus<unknown>>;
|
|
11
13
|
};
|
|
@@ -13,6 +15,7 @@ export interface LiveBound {
|
|
|
13
15
|
export type ChangesClient = AtomRpc.AtomRpcClient<unknown, string, ReturnType<typeof changesRpc>>;
|
|
14
16
|
export declare function automaticLive(service: ChangesClient, name: string): {
|
|
15
17
|
subscription: {
|
|
18
|
+
coalesceWindow: Atom.Writable<Duration.Input, Duration.Input>;
|
|
16
19
|
connection: Atom.Atom<import("effect/reactivity/AsyncResult").AsyncResult<void, never>>;
|
|
17
20
|
paused: Atom.Writable<boolean, boolean>;
|
|
18
21
|
status: Atom.Writable<LiveStatus<import("effect/rpc/RpcClientError").RpcClientError>, LiveStatus<import("effect/rpc/RpcClientError").RpcClientError>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"automaticLive.d.ts","sourceRoot":"","sources":["../src/automaticLive.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"automaticLive.d.ts","sourceRoot":"","sources":["../src/automaticLive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAmC,MAAM,QAAQ,CAAC;AACxE,OAAO,KAAK,KAAK,IAAI,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,KAAK,GAAG,EAAY,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,KAAK,UAAU,EAAQ,MAAM,WAAW,CAAC;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAItD,eAAO,MAAM,cAAc,eAA0D,CAAC;AACtF,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE;QAC1B,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACvD,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACxC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;KAChD,CAAC;CACF;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC;AAElG,wBAAgB,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM;;;;;;;mBAG3C,SAAS,GAAG,EAAE;EAuBpC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"automaticLive.js","sourceRoot":"","sources":["../src/automaticLive.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"automaticLive.js","sourceRoot":"","sources":["../src/automaticLive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAGxE,OAAO,EAAY,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAmB,IAAI,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAqB,YAAY,EAAE,MAAM,aAAa,CAAC;AAK9D,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;AAWtF,MAAM,UAAU,aAAa,CAAC,OAAsB,EAAE,IAAY;IACjE,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,CAAiB,EAAE,CAAC,CAAC,CAAC;IACtE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,SAAS,KAAK,CAAC,KAAqB;QACnC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;YAClC,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACpC,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;gBACnB,OAAO,KAAK,CAAC;YACd,CAAC;YACD,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACd,OAAO,IAAI,CAAC;QACb,CAAC,CAAC,CAAC;QACH,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACnF,CAAC;IACF,CAAC;IACD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE;QAC1C,MAAM,EAAE,YAAY,CAAC,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;QACrE,UAAU,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC3D,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CACzC,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,EAAE,CAC5B,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,WAAW,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAC3I,CACD;KACD,CAAC,CAAC;IACH,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;AAChC,CAAC"}
|
package/dist/bind.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bind.d.ts","sourceRoot":"","sources":["../src/bind.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAU,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,KAAK,IAAI,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAC1D,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,KAAK,EAAE,GAAG,EAAa,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"bind.d.ts","sourceRoot":"","sources":["../src/bind.ts"],"names":[],"mappings":"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"}
|
package/dist/bind.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { Effect, Stream } from "effect";
|
|
2
2
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
3
|
+
import { queryReactivity } from "#internal/queryReactivity.ts";
|
|
3
4
|
import { automaticLive, LiveConnection } from "./automaticLive.js";
|
|
4
5
|
import { invalidationKeys, readKeys } from "./keys.js";
|
|
5
6
|
const invalidateAfter = Effect.fn("EffectContract.invalidateAfter")(function* (keys) {
|
|
6
7
|
yield* Reactivity.invalidate(invalidationKeys(keys));
|
|
7
8
|
});
|
|
8
9
|
function boundQuery(service, tag, query, track) {
|
|
10
|
+
const reactive = queryReactivity(service.runtime);
|
|
9
11
|
function reads(payload) {
|
|
10
12
|
return readKeys(query.reads(payload));
|
|
11
13
|
}
|
|
12
14
|
return {
|
|
13
15
|
query: (payload, options = {}) => {
|
|
14
16
|
track(query.reads(payload));
|
|
15
|
-
return service.query(tag, payload,
|
|
17
|
+
return reactive(service.query(tag, payload, options), reads(payload));
|
|
16
18
|
},
|
|
17
19
|
run: (payload) => service.use((client) => client(tag, payload)),
|
|
18
20
|
};
|
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,EAAE,MAAM,QAAQ,CAAC;AAKxC,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAG3D,OAAO,EAAE,aAAa,EAAkB,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEnF,OAAO,EAAE,gBAAgB,EAAY,QAAQ,EAAE,MAAM,WAAW,CAAC;AA6DjE,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,EAAE,KAAqC;IAC/G,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,OAAO,GAAiB,EAAE,EAAE,EAAE;YACvD,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YAC5B,OAAO,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"bind.js","sourceRoot":"","sources":["../src/bind.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { Key } from "#keys.ts";
|
|
3
|
+
export declare const makeHintPolicy: (capacity: number, retry: (keys: readonly Key[]) => Effect.Effect<void>) => Effect.Effect<(keys: readonly Key[]) => Effect.Effect<Key[], never, never>, never, import("effect/Scope").Scope>;
|
|
4
|
+
//# sourceMappingURL=makeHintPolicy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"makeHintPolicy.d.ts","sourceRoot":"","sources":["../../src/internal/makeHintPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,MAAM,EAAe,MAAM,QAAQ,CAAC;AAC1D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAGpC,eAAO,MAAM,cAAc,mCAAgE,SAAS,GAAG,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,0BAqBlH,SAAS,GAAG,EAAE,6EAc3B,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Cache, Data, Effect, Exit, Queue } from "effect";
|
|
2
|
+
import { ReadPolicy, ReadPolicyMemoWindow } from "#readPolicy.ts";
|
|
3
|
+
export const makeHintPolicy = Effect.fnUntraced(function* (capacity, retry) {
|
|
4
|
+
const policy = yield* ReadPolicy;
|
|
5
|
+
const window = yield* ReadPolicyMemoWindow;
|
|
6
|
+
const cache = yield* Cache.makeWith(policy, {
|
|
7
|
+
capacity: Math.max(1, capacity),
|
|
8
|
+
timeToLive: (exit) => (Exit.isSuccess(exit) ? window : 0),
|
|
9
|
+
});
|
|
10
|
+
const failed = new Map();
|
|
11
|
+
const wake = yield* Queue.unbounded();
|
|
12
|
+
yield* Queue.take(wake).pipe(Effect.andThen(Effect.sleep("1 second")), Effect.andThen(Effect.suspend(() => {
|
|
13
|
+
const keys = [...failed.values()];
|
|
14
|
+
failed.clear();
|
|
15
|
+
return retry(keys);
|
|
16
|
+
})), Effect.forever, Effect.forkScoped);
|
|
17
|
+
return (keys) => Effect.filter(keys, (key) => Cache.get(cache, new Data.Class(key)).pipe(Effect.catch(() => Effect.gen(function* () {
|
|
18
|
+
if (failed.size === 0) {
|
|
19
|
+
yield* Queue.offer(wake, undefined);
|
|
20
|
+
}
|
|
21
|
+
failed.set(JSON.stringify(key), key);
|
|
22
|
+
return false;
|
|
23
|
+
}))));
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=makeHintPolicy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"makeHintPolicy.js","sourceRoot":"","sources":["../../src/internal/makeHintPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE1D,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAElE,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,QAAgB,EAAE,KAAoD;IAChI,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC;IACjC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,oBAAoB,CAAC;IAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE;QAC3C,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC;QAC/B,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;KACzD,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,IAAI,GAAG,EAAe,CAAC;IACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,SAAS,EAAQ,CAAC;IAC5C,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAC3B,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,EACxC,MAAM,CAAC,OAAO,CACb,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;QACnB,MAAM,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAClC,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC,CAAC,CACF,EACD,MAAM,CAAC,OAAO,EACd,MAAM,CAAC,UAAU,CACjB,CAAC;IACF,OAAO,CAAC,IAAoB,EAAE,EAAE,CAC/B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CAC3B,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,IAAI,CAAC,KAAK,CAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAC9C,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CACjB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACrC,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;QACrC,OAAO,KAAK,CAAC;IACd,CAAC,CAAC,CACF,CACD,CACD,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
2
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
3
|
+
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
4
|
+
export declare function invalidateLive(reactivity: Reactivity.Reactivity, keys: readonly string[], active: () => boolean): void;
|
|
5
|
+
export declare function queryReactivity<R, E>(runtime: Atom.AtomRuntime<R, E>): (atom: Atom.Atom<AsyncResult.AsyncResult<unknown, unknown>>, keys: readonly string[]) => Atom.Atom<AsyncResult.AsyncResult<unknown, unknown>>;
|
|
6
|
+
//# sourceMappingURL=queryReactivity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryReactivity.d.ts","sourceRoot":"","sources":["../../src/internal/queryReactivity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,WAAW,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAC/C,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAQ3D,wBAAgB,cAAc,CAAC,UAAU,EAAE,UAAU,CAAC,UAAU,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,OAAO,QAO/G;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,sEAIzC,SAAS,MAAM,EAAE,0DA2D5C"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
3
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
4
|
+
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
5
|
+
const batches = new WeakMap();
|
|
6
|
+
export function invalidateLive(reactivity, keys, active) {
|
|
7
|
+
batches.set(reactivity, { active, seen: new Set() });
|
|
8
|
+
try {
|
|
9
|
+
reactivity.invalidateUnsafe(keys);
|
|
10
|
+
}
|
|
11
|
+
finally {
|
|
12
|
+
batches.delete(reactivity);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function queryReactivity(runtime) {
|
|
16
|
+
const service = runtime.atom(Effect.service(Reactivity.Reactivity));
|
|
17
|
+
const cached = new WeakMap();
|
|
18
|
+
return (atom, keys) => {
|
|
19
|
+
const existing = cached.get(atom);
|
|
20
|
+
if (existing !== undefined) {
|
|
21
|
+
return existing;
|
|
22
|
+
}
|
|
23
|
+
const source = Atom.setIdleTTL(Atom.readable((get) => atom.read(get), atom.refresh), 0);
|
|
24
|
+
let wrapped = Atom.transform(source, (get) => {
|
|
25
|
+
const context = get(service);
|
|
26
|
+
if (!AsyncResult.isSuccess(context)) {
|
|
27
|
+
return get(source);
|
|
28
|
+
}
|
|
29
|
+
const reactivity = context.value;
|
|
30
|
+
let pending;
|
|
31
|
+
function invalidate() {
|
|
32
|
+
const batch = batches.get(reactivity);
|
|
33
|
+
if (batch !== undefined) {
|
|
34
|
+
if (batch.seen.has(invalidate)) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
batch.seen.add(invalidate);
|
|
38
|
+
if (get.once(source).waiting) {
|
|
39
|
+
pending = batch.active;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
pending = undefined;
|
|
44
|
+
get.refresh(source);
|
|
45
|
+
}
|
|
46
|
+
get.addFinalizer(reactivity.registerUnsafe(keys, invalidate));
|
|
47
|
+
get.subscribe(source, (value) => {
|
|
48
|
+
get.setSelf(value);
|
|
49
|
+
if (pending !== undefined && !value.waiting) {
|
|
50
|
+
const active = pending;
|
|
51
|
+
pending = undefined;
|
|
52
|
+
if (active()) {
|
|
53
|
+
get.refresh(source);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
return get.once(source);
|
|
58
|
+
}, { initialValueTarget: source });
|
|
59
|
+
if (atom.keepAlive) {
|
|
60
|
+
wrapped = Atom.keepAlive(wrapped);
|
|
61
|
+
}
|
|
62
|
+
else if (atom.idleTTL !== undefined) {
|
|
63
|
+
wrapped = Atom.setIdleTTL(wrapped, atom.idleTTL);
|
|
64
|
+
}
|
|
65
|
+
if (Atom.isSerializable(atom)) {
|
|
66
|
+
Object.assign(wrapped, { [Atom.SerializableTypeId]: atom[Atom.SerializableTypeId] });
|
|
67
|
+
}
|
|
68
|
+
cached.set(atom, wrapped);
|
|
69
|
+
return wrapped;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=queryReactivity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryReactivity.js","sourceRoot":"","sources":["../../src/internal/queryReactivity.ts"],"names":[],"mappings":"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"}
|
package/dist/live.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Cause,
|
|
1
|
+
import { type Cause, Duration, Option, Schema, Stream } from "effect";
|
|
2
2
|
import * as Atom from "effect/reactivity/Atom";
|
|
3
3
|
import { Key } from "./keys.ts";
|
|
4
4
|
export declare const LiveHint: Schema.Union<readonly [Schema.Struct<{
|
|
@@ -22,12 +22,14 @@ export interface LiveStatus<E> {
|
|
|
22
22
|
readonly pending: number;
|
|
23
23
|
}
|
|
24
24
|
export interface LiveOptions<E, R> {
|
|
25
|
+
readonly coalesceWindow?: Duration.Input;
|
|
25
26
|
readonly resume?: Atom.Atom<unknown>;
|
|
26
27
|
readonly resyncKeys: readonly Key[] | (() => readonly Key[]);
|
|
27
28
|
readonly retryDelay?: Duration.Input;
|
|
28
29
|
readonly stream: Stream.Stream<LiveHint, E, R>;
|
|
29
30
|
}
|
|
30
31
|
export declare function live<R, E, TRuntimeError>(runtime: Atom.AtomRuntime<R, TRuntimeError>, options: LiveOptions<E, R>): {
|
|
32
|
+
coalesceWindow: Atom.Writable<Duration.Input, Duration.Input>;
|
|
31
33
|
connection: Atom.Atom<import("effect/reactivity/AsyncResult").AsyncResult<void, TRuntimeError>>;
|
|
32
34
|
paused: Atom.Writable<boolean, boolean>;
|
|
33
35
|
status: Atom.Writable<LiveStatus<E>, LiveStatus<E>>;
|
package/dist/live.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,QAAQ,EAAU,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9E,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAG/C,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,cAAc,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,SAAS,GAAG,EAAE,GAAG,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC,CAAC;IAC7D,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;;;;;EAwFhH"}
|
package/dist/live.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Effect, Option, Schema, Stream } from "effect";
|
|
1
|
+
import { Duration, Effect, Option, Schema, Stream } from "effect";
|
|
2
2
|
import * as Atom from "effect/reactivity/Atom";
|
|
3
3
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
4
|
+
import { invalidateLive } from "#internal/queryReactivity.ts";
|
|
4
5
|
import { invalidationKeys, Key, readKeys } from "./keys.js";
|
|
5
6
|
export const LiveHint = Schema.Union([
|
|
6
7
|
Schema.Struct({ _tag: Schema.Literal("Changed"), keys: Schema.Array(Key) }),
|
|
@@ -8,6 +9,7 @@ export const LiveHint = Schema.Union([
|
|
|
8
9
|
]);
|
|
9
10
|
const MAX_PENDING = 256;
|
|
10
11
|
export function live(runtime, options) {
|
|
12
|
+
const coalesceWindow = Atom.keepAlive(Atom.make(options.coalesceWindow ?? "20 millis"));
|
|
11
13
|
const paused = Atom.make(false);
|
|
12
14
|
const status = Atom.make({ connection: "connecting", failure: Option.none(), needsResync: true, pending: 0 });
|
|
13
15
|
const connection = runtime
|
|
@@ -17,7 +19,37 @@ export function live(runtime, options) {
|
|
|
17
19
|
function update(value) {
|
|
18
20
|
get.set(status, { ...get.once(status), ...value });
|
|
19
21
|
}
|
|
22
|
+
const pending = new Set();
|
|
23
|
+
let timer;
|
|
24
|
+
function clear() {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
timer = undefined;
|
|
27
|
+
pending.clear();
|
|
28
|
+
}
|
|
29
|
+
let active = true;
|
|
30
|
+
get.addFinalizer(() => {
|
|
31
|
+
active = false;
|
|
32
|
+
clear();
|
|
33
|
+
});
|
|
34
|
+
function changed(changedKeys) {
|
|
35
|
+
const delay = Duration.toMillis(Duration.fromInputUnsafe(get.once(coalesceWindow)));
|
|
36
|
+
if (delay === 0) {
|
|
37
|
+
reactivity.invalidateUnsafe(invalidationKeys(changedKeys));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
for (const key of invalidationKeys(changedKeys)) {
|
|
41
|
+
pending.add(key);
|
|
42
|
+
}
|
|
43
|
+
timer ??= setTimeout(() => {
|
|
44
|
+
const keys = [...pending];
|
|
45
|
+
clear();
|
|
46
|
+
if (!get.once(paused)) {
|
|
47
|
+
invalidateLive(reactivity, keys, () => active && !get.once(paused));
|
|
48
|
+
}
|
|
49
|
+
}, delay);
|
|
50
|
+
}
|
|
20
51
|
function resync() {
|
|
52
|
+
clear();
|
|
21
53
|
if (get.once(paused)) {
|
|
22
54
|
update({ needsResync: true });
|
|
23
55
|
return;
|
|
@@ -48,7 +80,7 @@ export function live(runtime, options) {
|
|
|
48
80
|
resync();
|
|
49
81
|
}
|
|
50
82
|
else {
|
|
51
|
-
|
|
83
|
+
changed(hint.keys);
|
|
52
84
|
}
|
|
53
85
|
isFirst = false;
|
|
54
86
|
}
|
|
@@ -57,6 +89,6 @@ export function live(runtime, options) {
|
|
|
57
89
|
yield* Effect.forever(attempt);
|
|
58
90
|
}))
|
|
59
91
|
.pipe(Atom.setIdleTTL(0));
|
|
60
|
-
return { connection, paused, status };
|
|
92
|
+
return { coalesceWindow, connection, paused, status };
|
|
61
93
|
}
|
|
62
94
|
//# sourceMappingURL=live.js.map
|
package/dist/live.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live.js","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"live.js","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"makeLiveServer.d.ts","sourceRoot":"","sources":["../src/makeLiveServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAS,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC/C,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"makeLiveServer.d.ts","sourceRoot":"","sources":["../src/makeLiveServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAS,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC/C,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAE3D,OAAO,EAAE,KAAK,GAAG,EAAY,MAAM,WAAW,CAAC;AAG/C,eAAO,MAAM,cAAc;;mBAGJ,SAAS,GAAG,EAAE;;;;;;;;;;;;;gCA8DnC,CAAC"}
|
package/dist/makeLiveServer.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Effect, Queue, Stream } from "effect";
|
|
2
2
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
3
|
+
import { makeHintPolicy } from "#internal/makeHintPolicy.ts";
|
|
3
4
|
import { readKeys } from "./keys.js";
|
|
4
|
-
import { ReadPolicy } from "./readPolicy.js";
|
|
5
5
|
export const makeLiveServer = Effect.gen(function* () {
|
|
6
6
|
const reactivity = yield* Reactivity.Reactivity;
|
|
7
7
|
const resyncKey = `@shivaedev/effect-contract/resync/${crypto.randomUUID()}`;
|
|
8
8
|
function stream(keys) {
|
|
9
9
|
return Stream.unwrap(Effect.gen(function* () {
|
|
10
|
-
const canRead = yield* ReadPolicy;
|
|
11
10
|
const queue = yield* Queue.unbounded();
|
|
11
|
+
const allowedKeys = yield* makeHintPolicy(keys.length, (retryKeys) => Queue.offer(queue, { _tag: "Changed", keys: retryKeys }).pipe(Effect.asVoid));
|
|
12
12
|
const pending = new Map();
|
|
13
13
|
let scheduled = false;
|
|
14
14
|
let resync = false;
|
|
@@ -47,7 +47,7 @@ export const makeLiveServer = Effect.gen(function* () {
|
|
|
47
47
|
yield* Queue.offer(queue, { _tag: "Resync" });
|
|
48
48
|
return Stream.fromQueue(queue).pipe(Stream.mapEffect((hint) => hint._tag === "Resync"
|
|
49
49
|
? Effect.succeed(hint)
|
|
50
|
-
: Effect.map(
|
|
50
|
+
: Effect.map(allowedKeys(hint.keys), (allowed) => ({ _tag: "Changed", keys: allowed }))), Stream.filter((hint) => hint._tag === "Resync" || hint.keys.length > 0));
|
|
51
51
|
}));
|
|
52
52
|
}
|
|
53
53
|
const resync = Effect.sync(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"makeLiveServer.js","sourceRoot":"","sources":["../src/makeLiveServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC/C,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,
|
|
1
|
+
{"version":3,"file":"makeLiveServer.js","sourceRoot":"","sources":["../src/makeLiveServer.ts"],"names":[],"mappings":"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"}
|
package/dist/readPolicy.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Context, Effect } from "effect";
|
|
1
|
+
import { Context, type Duration, Effect } from "effect";
|
|
2
2
|
import type { Rpc } from "effect/rpc";
|
|
3
3
|
import type { Key } from "./keys.ts";
|
|
4
|
-
export declare const ReadPolicy: Context.Reference<(key: Key) => Effect.Effect<boolean>>;
|
|
5
|
-
export declare const
|
|
4
|
+
export declare const ReadPolicy: Context.Reference<(key: Key) => Effect.Effect<boolean, unknown>>;
|
|
5
|
+
export declare const ReadPolicyMemoWindow: Context.Reference<Duration.Input>;
|
|
6
|
+
export declare const authorizeReads: <A, E, R, TPolicyServices, TDenied, TPolicyError>(effect: Effect.Effect<A, E, R>, request: {
|
|
6
7
|
readonly rpc: Rpc.AnyWithProps;
|
|
7
8
|
readonly payload: unknown;
|
|
8
|
-
}, policy: (key: Key) => Effect.Effect<boolean,
|
|
9
|
+
}, policy: (key: Key) => Effect.Effect<boolean, TPolicyError, TPolicyServices>, denied: Effect.Effect<never, TDenied, never>) => Effect.Effect<A, E | TDenied | TPolicyError, TPolicyServices | Exclude<R, never>>;
|
|
9
10
|
//# sourceMappingURL=readPolicy.d.ts.map
|
package/dist/readPolicy.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readPolicy.d.ts","sourceRoot":"","sources":["../src/readPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"readPolicy.d.ts","sourceRoot":"","sources":["../src/readPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AAChE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAGrC,eAAO,MAAM,UAAU,0BAA2B,GAAG,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAEvF,CAAC;AAEH,eAAO,MAAM,oBAAoB,mCAE/B,CAAC;AAEH,eAAO,MAAM,cAAc,GAAgC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,eAAe,EAAE,OAAO,EAAE,YAAY;kBAEhF,GAAG,CAAC,YAAY;sBAAoB,OAAO;iBACtD,GAAG,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,eAAe,CAAC,oIAgB1E,CAAC"}
|
package/dist/readPolicy.js
CHANGED
|
@@ -3,6 +3,9 @@ import { QueryReads } from "./serverMetadata.js";
|
|
|
3
3
|
export const ReadPolicy = Context.Reference("@shivaedev/effect-contract/ReadPolicy", {
|
|
4
4
|
defaultValue: () => () => Effect.succeed(true),
|
|
5
5
|
});
|
|
6
|
+
export const ReadPolicyMemoWindow = Context.Reference("@shivaedev/effect-contract/ReadPolicyMemoWindow", {
|
|
7
|
+
defaultValue: () => "1 second",
|
|
8
|
+
});
|
|
6
9
|
export const authorizeReads = Effect.fnUntraced(function* (effect, request, policy, denied) {
|
|
7
10
|
const context = yield* Effect.context();
|
|
8
11
|
function canRead(key) {
|
package/dist/readPolicy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readPolicy.js","sourceRoot":"","sources":["../src/readPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"readPolicy.js","sourceRoot":"","sources":["../src/readPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAiB,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhE,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,CAAgD,uCAAuC,EAAE;IACnI,YAAY,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;CAC9C,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,OAAO,CAAC,SAAS,CAAiB,iDAAiD,EAAE;IACxH,YAAY,EAAE,GAAG,EAAE,CAAC,UAAU;CAC9B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EACxD,MAA8B,EAC9B,OAAsE,EACtE,MAA2E,EAC3E,MAAqC;IAErC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,EAAmB,CAAC;IACzD,SAAS,OAAO,CAAC,GAAQ;QACxB,OAAO,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IACrE,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAChD,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBAC5B,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC;YACtB,CAAC;QACF,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/effect-contract",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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": {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"types": "./dist/*.d.ts",
|
|
29
29
|
"default": "./dist/*.js"
|
|
30
30
|
},
|
|
31
|
+
"./internal/*": null,
|
|
31
32
|
"./package.json": "./package.json"
|
|
32
33
|
},
|
|
33
34
|
"files": [
|
|
@@ -46,8 +47,9 @@
|
|
|
46
47
|
"@effect/platform-node": "4.0.4",
|
|
47
48
|
"@effect/vitest": "4.0.4",
|
|
48
49
|
"@shivaedev/effect-test": "0.7.1",
|
|
50
|
+
"@shivaedev/effect-test-sql": "0.7.4",
|
|
49
51
|
"@shivaedev/quality": "0.29.1",
|
|
50
|
-
"@shivaedev/test-story": "0.
|
|
52
|
+
"@shivaedev/test-story": "0.16.0",
|
|
51
53
|
"@types/node": "24.19.2",
|
|
52
54
|
"effect": "4.0.4",
|
|
53
55
|
"esbuild": "0.28.2",
|
package/src/automaticLive.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Stream, SubscriptionRef } from "effect";
|
|
1
|
+
import { type Duration, Effect, Stream, SubscriptionRef } from "effect";
|
|
2
2
|
import type * as Atom from "effect/reactivity/Atom";
|
|
3
3
|
import type * as AtomRpc from "effect/reactivity/AtomRpc";
|
|
4
4
|
import { type Key, readKeys } from "./keys.ts";
|
|
@@ -10,7 +10,11 @@ declare const window: ResumeWindow | undefined;
|
|
|
10
10
|
|
|
11
11
|
export const LiveConnection = Symbol.for("@shivaedev/effect-contract/LiveConnection");
|
|
12
12
|
export interface LiveBound {
|
|
13
|
-
readonly [LiveConnection]: {
|
|
13
|
+
readonly [LiveConnection]: {
|
|
14
|
+
readonly coalesceWindow: Atom.Writable<Duration.Input>;
|
|
15
|
+
readonly connection: Atom.Atom<unknown>;
|
|
16
|
+
readonly status: Atom.Atom<LiveStatus<unknown>>;
|
|
17
|
+
};
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
export type ChangesClient = AtomRpc.AtomRpcClient<unknown, string, ReturnType<typeof changesRpc>>;
|
package/src/bind.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type * as AtomRpc from "effect/reactivity/AtomRpc";
|
|
|
7
7
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
8
8
|
import type { Rpc, RpcSchema } from "effect/rpc";
|
|
9
9
|
import type { RpcClientError } from "effect/rpc/RpcClientError";
|
|
10
|
+
import { queryReactivity } from "#internal/queryReactivity.ts";
|
|
10
11
|
import { automaticLive, type LiveBound, LiveConnection } from "./automaticLive.ts";
|
|
11
12
|
import type { Contract, Declared, Tag } from "./contract.ts";
|
|
12
13
|
import { invalidationKeys, type Key, readKeys } from "./keys.ts";
|
|
@@ -75,13 +76,14 @@ const invalidateAfter = Effect.fn("EffectContract.invalidateAfter")(function* (k
|
|
|
75
76
|
});
|
|
76
77
|
|
|
77
78
|
function boundQuery(service: ErasedClient, tag: string, query: QueryShape, track: (keys: readonly Key[]) => void) {
|
|
79
|
+
const reactive = queryReactivity(service.runtime);
|
|
78
80
|
function reads(payload: unknown) {
|
|
79
81
|
return readKeys(query.reads(payload));
|
|
80
82
|
}
|
|
81
83
|
return {
|
|
82
84
|
query: (payload: unknown, options: QueryOptions = {}) => {
|
|
83
85
|
track(query.reads(payload));
|
|
84
|
-
return service.query(tag, payload,
|
|
86
|
+
return reactive(service.query(tag, payload, options), reads(payload));
|
|
85
87
|
},
|
|
86
88
|
run: (payload: unknown) => service.use((client) => client(tag, payload)),
|
|
87
89
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Cache, Data, Effect, Exit, Queue } from "effect";
|
|
2
|
+
import type { Key } from "#keys.ts";
|
|
3
|
+
import { ReadPolicy, ReadPolicyMemoWindow } from "#readPolicy.ts";
|
|
4
|
+
|
|
5
|
+
export const makeHintPolicy = Effect.fnUntraced(function* (capacity: number, retry: (keys: readonly Key[]) => Effect.Effect<void>) {
|
|
6
|
+
const policy = yield* ReadPolicy;
|
|
7
|
+
const window = yield* ReadPolicyMemoWindow;
|
|
8
|
+
const cache = yield* Cache.makeWith(policy, {
|
|
9
|
+
capacity: Math.max(1, capacity),
|
|
10
|
+
timeToLive: (exit) => (Exit.isSuccess(exit) ? window : 0),
|
|
11
|
+
});
|
|
12
|
+
const failed = new Map<string, Key>();
|
|
13
|
+
const wake = yield* Queue.unbounded<void>();
|
|
14
|
+
yield* Queue.take(wake).pipe(
|
|
15
|
+
Effect.andThen(Effect.sleep("1 second")),
|
|
16
|
+
Effect.andThen(
|
|
17
|
+
Effect.suspend(() => {
|
|
18
|
+
const keys = [...failed.values()];
|
|
19
|
+
failed.clear();
|
|
20
|
+
return retry(keys);
|
|
21
|
+
}),
|
|
22
|
+
),
|
|
23
|
+
Effect.forever,
|
|
24
|
+
Effect.forkScoped,
|
|
25
|
+
);
|
|
26
|
+
return (keys: readonly Key[]) =>
|
|
27
|
+
Effect.filter(keys, (key) =>
|
|
28
|
+
Cache.get(cache, new Data.Class<Key>(key)).pipe(
|
|
29
|
+
Effect.catch(() =>
|
|
30
|
+
Effect.gen(function* () {
|
|
31
|
+
if (failed.size === 0) {
|
|
32
|
+
yield* Queue.offer(wake, undefined);
|
|
33
|
+
}
|
|
34
|
+
failed.set(JSON.stringify(key), key);
|
|
35
|
+
return false;
|
|
36
|
+
}),
|
|
37
|
+
),
|
|
38
|
+
),
|
|
39
|
+
);
|
|
40
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
3
|
+
import * as Atom from "effect/reactivity/Atom";
|
|
4
|
+
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
5
|
+
|
|
6
|
+
interface Batch {
|
|
7
|
+
readonly active: () => boolean;
|
|
8
|
+
readonly seen: Set<() => void>;
|
|
9
|
+
}
|
|
10
|
+
const batches = new WeakMap<Reactivity.Reactivity, Batch>();
|
|
11
|
+
|
|
12
|
+
export function invalidateLive(reactivity: Reactivity.Reactivity, keys: readonly string[], active: () => boolean) {
|
|
13
|
+
batches.set(reactivity, { active, seen: new Set() });
|
|
14
|
+
try {
|
|
15
|
+
reactivity.invalidateUnsafe(keys);
|
|
16
|
+
} finally {
|
|
17
|
+
batches.delete(reactivity);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function queryReactivity<R, E>(runtime: Atom.AtomRuntime<R, E>) {
|
|
22
|
+
const service = runtime.atom(Effect.service(Reactivity.Reactivity));
|
|
23
|
+
type Query = Atom.Atom<AsyncResult.AsyncResult<unknown, unknown>>;
|
|
24
|
+
const cached = new WeakMap<Query, Query>();
|
|
25
|
+
return (atom: Query, keys: readonly string[]): Query => {
|
|
26
|
+
const existing = cached.get(atom);
|
|
27
|
+
if (existing !== undefined) {
|
|
28
|
+
return existing;
|
|
29
|
+
}
|
|
30
|
+
const source = Atom.setIdleTTL(
|
|
31
|
+
Atom.readable((get) => atom.read(get), atom.refresh),
|
|
32
|
+
0,
|
|
33
|
+
);
|
|
34
|
+
let wrapped = Atom.transform(
|
|
35
|
+
source,
|
|
36
|
+
(get) => {
|
|
37
|
+
const context = get(service);
|
|
38
|
+
if (!AsyncResult.isSuccess(context)) {
|
|
39
|
+
return get(source);
|
|
40
|
+
}
|
|
41
|
+
const reactivity = context.value;
|
|
42
|
+
let pending: (() => boolean) | undefined;
|
|
43
|
+
function invalidate() {
|
|
44
|
+
const batch = batches.get(reactivity);
|
|
45
|
+
if (batch !== undefined) {
|
|
46
|
+
if (batch.seen.has(invalidate)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
batch.seen.add(invalidate);
|
|
50
|
+
if (get.once(source).waiting) {
|
|
51
|
+
pending = batch.active;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
pending = undefined;
|
|
56
|
+
get.refresh(source);
|
|
57
|
+
}
|
|
58
|
+
get.addFinalizer(reactivity.registerUnsafe(keys, invalidate));
|
|
59
|
+
get.subscribe(source, (value) => {
|
|
60
|
+
get.setSelf(value);
|
|
61
|
+
if (pending !== undefined && !value.waiting) {
|
|
62
|
+
const active = pending;
|
|
63
|
+
pending = undefined;
|
|
64
|
+
if (active()) {
|
|
65
|
+
get.refresh(source);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return get.once(source);
|
|
70
|
+
},
|
|
71
|
+
{ initialValueTarget: source },
|
|
72
|
+
);
|
|
73
|
+
if (atom.keepAlive) {
|
|
74
|
+
wrapped = Atom.keepAlive(wrapped);
|
|
75
|
+
} else if (atom.idleTTL !== undefined) {
|
|
76
|
+
wrapped = Atom.setIdleTTL(wrapped, atom.idleTTL);
|
|
77
|
+
}
|
|
78
|
+
if (Atom.isSerializable(atom)) {
|
|
79
|
+
Object.assign(wrapped, { [Atom.SerializableTypeId]: atom[Atom.SerializableTypeId] });
|
|
80
|
+
}
|
|
81
|
+
cached.set(atom, wrapped);
|
|
82
|
+
return wrapped;
|
|
83
|
+
};
|
|
84
|
+
}
|
package/src/live.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { type Cause,
|
|
1
|
+
import { type Cause, Duration, Effect, Option, Schema, Stream } from "effect";
|
|
2
2
|
import * as Atom from "effect/reactivity/Atom";
|
|
3
3
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
4
|
+
import { invalidateLive } from "#internal/queryReactivity.ts";
|
|
4
5
|
import { invalidationKeys, Key, readKeys } from "./keys.ts";
|
|
5
6
|
|
|
6
7
|
export const LiveHint = Schema.Union([
|
|
@@ -15,6 +16,7 @@ export interface LiveStatus<E> {
|
|
|
15
16
|
readonly pending: number;
|
|
16
17
|
}
|
|
17
18
|
export interface LiveOptions<E, R> {
|
|
19
|
+
readonly coalesceWindow?: Duration.Input;
|
|
18
20
|
readonly resume?: Atom.Atom<unknown>;
|
|
19
21
|
readonly resyncKeys: readonly Key[] | (() => readonly Key[]);
|
|
20
22
|
readonly retryDelay?: Duration.Input;
|
|
@@ -24,6 +26,7 @@ export interface LiveOptions<E, R> {
|
|
|
24
26
|
const MAX_PENDING = 256;
|
|
25
27
|
|
|
26
28
|
export function live<R, E, TRuntimeError>(runtime: Atom.AtomRuntime<R, TRuntimeError>, options: LiveOptions<E, R>) {
|
|
29
|
+
const coalesceWindow = Atom.keepAlive(Atom.make<Duration.Input>(options.coalesceWindow ?? "20 millis"));
|
|
27
30
|
const paused = Atom.make(false);
|
|
28
31
|
const status = Atom.make<LiveStatus<E>>({ connection: "connecting", failure: Option.none(), needsResync: true, pending: 0 });
|
|
29
32
|
const connection = runtime
|
|
@@ -34,7 +37,37 @@ export function live<R, E, TRuntimeError>(runtime: Atom.AtomRuntime<R, TRuntimeE
|
|
|
34
37
|
function update(value: Partial<LiveStatus<E>>) {
|
|
35
38
|
get.set(status, { ...get.once(status), ...value });
|
|
36
39
|
}
|
|
40
|
+
const pending = new Set<string>();
|
|
41
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
42
|
+
function clear() {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
timer = undefined;
|
|
45
|
+
pending.clear();
|
|
46
|
+
}
|
|
47
|
+
let active = true;
|
|
48
|
+
get.addFinalizer(() => {
|
|
49
|
+
active = false;
|
|
50
|
+
clear();
|
|
51
|
+
});
|
|
52
|
+
function changed(changedKeys: readonly Key[]) {
|
|
53
|
+
const delay = Duration.toMillis(Duration.fromInputUnsafe(get.once(coalesceWindow)));
|
|
54
|
+
if (delay === 0) {
|
|
55
|
+
reactivity.invalidateUnsafe(invalidationKeys(changedKeys));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
for (const key of invalidationKeys(changedKeys)) {
|
|
59
|
+
pending.add(key);
|
|
60
|
+
}
|
|
61
|
+
timer ??= setTimeout(() => {
|
|
62
|
+
const keys = [...pending];
|
|
63
|
+
clear();
|
|
64
|
+
if (!get.once(paused)) {
|
|
65
|
+
invalidateLive(reactivity, keys, () => active && !get.once(paused));
|
|
66
|
+
}
|
|
67
|
+
}, delay);
|
|
68
|
+
}
|
|
37
69
|
function resync() {
|
|
70
|
+
clear();
|
|
38
71
|
if (get.once(paused)) {
|
|
39
72
|
update({ needsResync: true });
|
|
40
73
|
return;
|
|
@@ -63,7 +96,7 @@ export function live<R, E, TRuntimeError>(runtime: Atom.AtomRuntime<R, TRuntimeE
|
|
|
63
96
|
} else if (isFirst || hint._tag === "Resync") {
|
|
64
97
|
resync();
|
|
65
98
|
} else {
|
|
66
|
-
|
|
99
|
+
changed(hint.keys);
|
|
67
100
|
}
|
|
68
101
|
isFirst = false;
|
|
69
102
|
}
|
|
@@ -79,5 +112,5 @@ export function live<R, E, TRuntimeError>(runtime: Atom.AtomRuntime<R, TRuntimeE
|
|
|
79
112
|
}),
|
|
80
113
|
)
|
|
81
114
|
.pipe(Atom.setIdleTTL(0));
|
|
82
|
-
return { connection, paused, status };
|
|
115
|
+
return { coalesceWindow, connection, paused, status };
|
|
83
116
|
}
|
package/src/makeLiveServer.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Effect, Queue, Stream } from "effect";
|
|
2
2
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
3
|
+
import { makeHintPolicy } from "#internal/makeHintPolicy.ts";
|
|
3
4
|
import { type Key, readKeys } from "./keys.ts";
|
|
4
5
|
import type { LiveHint } from "./live.ts";
|
|
5
|
-
import { ReadPolicy } from "./readPolicy.ts";
|
|
6
6
|
|
|
7
7
|
export const makeLiveServer = Effect.gen(function* () {
|
|
8
8
|
const reactivity = yield* Reactivity.Reactivity;
|
|
@@ -10,8 +10,10 @@ export const makeLiveServer = Effect.gen(function* () {
|
|
|
10
10
|
function stream(keys: readonly Key[]) {
|
|
11
11
|
return Stream.unwrap(
|
|
12
12
|
Effect.gen(function* () {
|
|
13
|
-
const canRead = yield* ReadPolicy;
|
|
14
13
|
const queue = yield* Queue.unbounded<LiveHint>();
|
|
14
|
+
const allowedKeys = yield* makeHintPolicy(keys.length, (retryKeys) =>
|
|
15
|
+
Queue.offer(queue, { _tag: "Changed", keys: retryKeys }).pipe(Effect.asVoid),
|
|
16
|
+
);
|
|
15
17
|
const pending = new Map<string, Key>();
|
|
16
18
|
let scheduled = false;
|
|
17
19
|
let resync = false;
|
|
@@ -56,7 +58,7 @@ export const makeLiveServer = Effect.gen(function* () {
|
|
|
56
58
|
Stream.mapEffect((hint) =>
|
|
57
59
|
hint._tag === "Resync"
|
|
58
60
|
? Effect.succeed(hint)
|
|
59
|
-
: Effect.map(
|
|
61
|
+
: Effect.map(allowedKeys(hint.keys), (allowed): LiveHint => ({ _tag: "Changed", keys: allowed })),
|
|
60
62
|
),
|
|
61
63
|
Stream.filter((hint) => hint._tag === "Resync" || hint.keys.length > 0),
|
|
62
64
|
);
|
package/src/readPolicy.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import { Context, Effect, Option } from "effect";
|
|
1
|
+
import { Context, type Duration, Effect, Option } from "effect";
|
|
2
2
|
import type { Rpc } from "effect/rpc";
|
|
3
3
|
import type { Key } from "./keys.ts";
|
|
4
4
|
import { QueryReads } from "./serverMetadata.ts";
|
|
5
5
|
|
|
6
|
-
export const ReadPolicy = Context.Reference<(key: Key) => Effect.Effect<boolean>>("@shivaedev/effect-contract/ReadPolicy", {
|
|
6
|
+
export const ReadPolicy = Context.Reference<(key: Key) => Effect.Effect<boolean, unknown>>("@shivaedev/effect-contract/ReadPolicy", {
|
|
7
7
|
defaultValue: () => () => Effect.succeed(true),
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
-
export const
|
|
10
|
+
export const ReadPolicyMemoWindow = Context.Reference<Duration.Input>("@shivaedev/effect-contract/ReadPolicyMemoWindow", {
|
|
11
|
+
defaultValue: () => "1 second",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const authorizeReads = Effect.fnUntraced(function* <A, E, R, TPolicyServices, TDenied, TPolicyError>(
|
|
11
15
|
effect: Effect.Effect<A, E, R>,
|
|
12
16
|
request: { readonly rpc: Rpc.AnyWithProps; readonly payload: unknown },
|
|
13
|
-
policy: (key: Key) => Effect.Effect<boolean,
|
|
17
|
+
policy: (key: Key) => Effect.Effect<boolean, TPolicyError, TPolicyServices>,
|
|
14
18
|
denied: Effect.Effect<never, TDenied>,
|
|
15
19
|
) {
|
|
16
20
|
const context = yield* Effect.context<TPolicyServices>();
|