@shivaedev/effect-contract 0.5.0 → 0.6.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 +21 -0
- package/README.md +23 -32
- package/dist/automaticLive.d.ts +22 -0
- package/dist/automaticLive.d.ts.map +1 -0
- package/dist/automaticLive.js +29 -0
- package/dist/automaticLive.js.map +1 -0
- package/dist/bind.d.ts +2 -1
- package/dist/bind.d.ts.map +1 -1
- package/dist/bind.js +10 -5
- package/dist/bind.js.map +1 -1
- package/dist/contract.d.ts +1 -1
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +4 -2
- package/dist/contract.js.map +1 -1
- package/dist/keys.d.ts +4 -4
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js +13 -9
- package/dist/keys.js.map +1 -1
- package/dist/live.d.ts +1 -1
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +1 -1
- package/dist/live.js.map +1 -1
- package/dist/makeLiveServer.d.ts.map +1 -1
- package/dist/makeLiveServer.js +38 -10
- package/dist/makeLiveServer.js.map +1 -1
- package/dist/serverMetadata.d.ts +30 -0
- package/dist/serverMetadata.d.ts.map +1 -0
- package/dist/serverMetadata.js +10 -0
- package/dist/serverMetadata.js.map +1 -0
- package/package.json +13 -57
- package/src/automaticLive.ts +44 -0
- package/src/bind.ts +16 -8
- package/src/contract.ts +4 -3
- package/src/keys.ts +14 -10
- package/src/live.ts +2 -2
- package/src/makeLiveServer.ts +41 -12
- package/src/serverMetadata.ts +13 -0
- package/docs/north-star.md +0 -45
- package/docs/roadmap.md +0 -26
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0 - 2026-10-18
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Contracts attach command invalidation metadata and a host-served change subscription. `serverMetadata.ts` exports `CommandInvalidation` and `changesRpc(name)` for server adapters.
|
|
8
|
+
- Bindings expose the `LiveConnection` symbol from `automaticLive.ts`; React session boundaries mount its `connection` atom and track the binding's query keys. `LiveBound` describes its connection and status atoms; `automaticLive(service, name)` and `ChangesClient` expose the underlying tracked coordination. `live` also accepts a function for `resyncKeys`.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Live server hints contain only changed keys intersecting the subscription, so one item's change does not refetch unrelated item queries.
|
|
13
|
+
|
|
14
|
+
### Breaking
|
|
15
|
+
|
|
16
|
+
- Contracts reserve the `<contract>.$changes` RPC name for automatic subscriptions. Use Platform's database host and NDJSON transport for automatic live queries, and remove application subscription handlers and `live()` mounting when using a React session boundary.
|
|
17
|
+
|
|
18
|
+
## 0.5.1 - 2026-10-17
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Publish against `@shivaedev/types` 0.2.1 so framework consumers share the current version.
|
|
23
|
+
|
|
3
24
|
## 0.5.0 - 2026-10-13
|
|
4
25
|
|
|
5
26
|
### Breaking
|
package/README.md
CHANGED
|
@@ -131,7 +131,7 @@ export const NotesHandlers = Notes.toLayer(Effect.gen(function* () {
|
|
|
131
131
|
}));
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
-
Save this module as `handlers.ts`.
|
|
134
|
+
Save this module as `handlers.ts`. An application can put repository and service calls in these handlers. The contract checks handler result and rejection types; it does not implement persistence, authorization or transactions.
|
|
135
135
|
|
|
136
136
|
Native `.middleware(M)` returns a contract that retains its declarations. For example, a native middleware service can supply request identity:
|
|
137
137
|
|
|
@@ -180,7 +180,7 @@ export class NotesClient extends AtomRpc.Service<NotesClient>()("app/NotesClient
|
|
|
180
180
|
export const api = bind(Notes, NotesClient);
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
-
Save the client module as `client.ts`. The [native RPC guide](https://github.com/ShivaeDev/platform/blob/main/docs/framework/native-rpc.md) covers hosting; the [
|
|
183
|
+
Save the client module as `client.ts`. The [native RPC guide](https://github.com/ShivaeDev/platform/blob/main/docs/framework/native-rpc.md) covers hosting; the [reference composition](https://github.com/ShivaeDev/platform/blob/main/docs/framework/composed-example.md) composes handlers, request identity, SQL and a rendered client.
|
|
184
184
|
|
|
185
185
|
### 3. Every day: read and change
|
|
186
186
|
|
|
@@ -226,11 +226,15 @@ The dependency rule is deliberately asymmetric:
|
|
|
226
226
|
|
|
227
227
|
`readKeys` and `invalidationKeys` produce these native keys and remove duplicates. An item change refreshes that item and its list, preserving other item queries. A list-only invalidation refreshes list queries and leaves item queries alone. These keys match native Reactivity's record-key hashing of `{ notes: [1] }`.
|
|
228
228
|
|
|
229
|
-
Client invalidation follows RPC success.
|
|
229
|
+
Client invalidation follows RPC success. Platform database hosts also publish declared command invalidations automatically after the handler succeeds, excluding keys already announced by repository commits, using `transact` from [`effect-sql`](https://github.com/ShivaeDev/platform/tree/main/packages/effect-sql); commit-bound changes belong to [`effect-changes`](https://github.com/ShivaeDev/platform/tree/main/packages/effect-changes).
|
|
230
230
|
|
|
231
|
-
### 4.
|
|
231
|
+
### 4. Live invalidation and resume
|
|
232
232
|
|
|
233
|
-
|
|
233
|
+
Platform database hosts serve the contract's reserved `<contract>.$changes` stream automatically. With NDJSON transport, `bind` and a React `SessionBoundary` connect query atoms without an application subscribe operation or `live()` call. The boundary finds bound APIs returned directly by `connect` or as properties such as `client.api`. See [automatic changes](https://github.com/ShivaeDev/platform/blob/main/docs/framework/changes.md) for defaults.
|
|
234
|
+
|
|
235
|
+
For a custom transport or explicit pause/status controls, the native live APIs remain available:
|
|
236
|
+
|
|
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.
|
|
234
238
|
|
|
235
239
|
Every query that should reconcile together must read the same explicit scope. This variant of the feature adds `workspace.list` alongside each query's item or list key:
|
|
236
240
|
|
|
@@ -259,7 +263,7 @@ export const LiveNotes = contract("notes", { queries: [LiveGet, LiveList], comma
|
|
|
259
263
|
|
|
260
264
|
Save the shared module as `live-notes.ts`. A collection list key alone does not cover its item queries. The common `workspace.list` key expresses that the caller intends to reconcile all queries that read it.
|
|
261
265
|
|
|
262
|
-
Use `LiveNotes` as the server group and implement `notes.subscribe` with a native Stream. For streaming HTTP,
|
|
266
|
+
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:
|
|
263
267
|
|
|
264
268
|
```ts
|
|
265
269
|
import { Layer } from "effect";
|
|
@@ -298,7 +302,7 @@ const releaseConnection = registry.mount(updates.connection);
|
|
|
298
302
|
| `Resync` | The exact `resyncKeys` read scope. |
|
|
299
303
|
| First hint on a new or reconnected stream | The full explicit scope, even if the hint is targeted. |
|
|
300
304
|
|
|
301
|
-
`LiveHint` and `Key` are schemas.
|
|
305
|
+
`LiveHint` and `Key` are schemas. Decoding rejects unknown hint tags and item keys with an invalid ID type.
|
|
302
306
|
|
|
303
307
|
```ts
|
|
304
308
|
registry.set(updates.paused, true);
|
|
@@ -306,11 +310,11 @@ registry.set(updates.paused, false);
|
|
|
306
310
|
const status = registry.get(updates.status);
|
|
307
311
|
```
|
|
308
312
|
|
|
309
|
-
While paused,
|
|
313
|
+
While paused, live coordination keeps observing hints and suppresses automatic refresh. `status.pending` saturates at 256 observations. Unpausing reconciles the explicit scope and resets the count. A resume signal also requests reconciliation: an injected native source and visible browser `online` and `visibilitychange` events request it, while hidden events do not.
|
|
310
314
|
|
|
311
|
-
`status` exposes `connection`, `failure`, `needsResync` and `pending`.
|
|
315
|
+
`status` exposes `connection`, `failure`, `needsResync` and `pending`. A reconnection reconciles changes missed while disconnected, and successful delivery clears a typed stream failure. Native query atoms keep previous successful data during a read failure and reject stale responses after invalidation. Query failures remain in their own `AsyncResult`; stream failures appear in `status.failure`.
|
|
312
316
|
|
|
313
|
-
The resume signal is a scoped native atom.
|
|
317
|
+
The resume signal is a scoped native atom. Disposing the registry removes its browser listeners, cleans up an injected source, including one used only through `live`, and releases the stream subscription:
|
|
314
318
|
|
|
315
319
|
```ts
|
|
316
320
|
releaseQuery();
|
|
@@ -320,6 +324,10 @@ registry.dispose();
|
|
|
320
324
|
|
|
321
325
|
Call the release functions and dispose the registry when the application's root no longer owns them.
|
|
322
326
|
|
|
327
|
+
### Server hint streams
|
|
328
|
+
|
|
329
|
+
Import `makeLiveServer` from `@shivaedev/effect-contract/makeLiveServer.ts` and create it once with the server's `Reactivity` service. After application authorization, return `hints.stream([orders.item(id)])` from the native streaming RPC handler. Each subscription starts with `Resync`; invalidating its read keys produces `Changed` with those declared keys. `hints.resync` sends `Resync` to every live stream owned by that helper, including streams for otherwise unrelated keys. Independent helpers keep their resync scopes separate even when they share Reactivity. The native stream owns registration and interruption. Keep this server helper separate from the browser's `live` coordinator.
|
|
330
|
+
|
|
323
331
|
### API by module
|
|
324
332
|
|
|
325
333
|
Import the module that defines the name; the package has no root entry.
|
|
@@ -332,21 +340,10 @@ Import the module that defines the name; the package has no root entry.
|
|
|
332
340
|
| `keys.ts` | `collection(name, idSchema)`; `Key` schema; `readKeys(keys)` and `invalidationKeys(keys)`. |
|
|
333
341
|
| `bind.ts` | `bind(contract, atomRpcService)`, exposing `{ query, run }` for queries, `{ run }` Effects for commands and `{ run }` native Streams for streams. |
|
|
334
342
|
| `live.ts` | `LiveHint` schema; `live(runtime, { stream, resyncKeys, resume?, retryDelay? })`, exposing `connection`, `paused` and `status`. |
|
|
343
|
+
| `makeLiveServer.ts` | `makeLiveServer`, an Effect requiring `Reactivity` that creates scoped server hint streams and a `resync` Effect. |
|
|
335
344
|
| `resume.ts` | `resumeSignal({ window?, native? })`, returning a native numeric signal atom. |
|
|
336
345
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
| Module | Exported types |
|
|
340
|
-
| --- | --- |
|
|
341
|
-
| `operation.ts` | `Query`, `Command`, `Streaming`, `QueryShape`, `CommandShape`, `StreamShape`, `OperationShape`, `PayloadSchema`. |
|
|
342
|
-
| `contract.ts` | `Contract`, `Declared`, `OperationRpc`, `Tag`. |
|
|
343
|
-
| `rejection.ts` | `FieldRejection`, `MatchingTags`, `Reject`, `RejectedBy`, `RejectionClass`, `RejectionSpecs`, `Rejections`, `RejectionSet`, `RejectionUnion`, `RejectionValue`, `TaggedRejection`. |
|
|
344
|
-
| `keys.ts` | `Collection`, `Identity`, `ItemKey`, `ListKey`, `Key`. |
|
|
345
|
-
| `bind.ts` | `Bound`, `BoundQuery`, `BoundCommand`, `BoundStream`, `Failure`, `RunFailure`, `QueryOptions`. |
|
|
346
|
-
| `live.ts` | `LiveHint`, `LiveOptions`, `LiveStatus`. |
|
|
347
|
-
| `resume.ts` | `ResumeOptions`, `ResumeSource`, `ResumeWindow`. |
|
|
348
|
-
|
|
349
|
-
The shape types describe operation declarations, the bound types describe client members, and the rejection types derive classes and failures from declared specs. A native resume source receives a callback and returns a cleanup function. `ResumeWindow` describes the browser event and visibility methods the signal uses.
|
|
346
|
+
A query atom takes native query options as its optional second argument, for example `api.get.query({ id: 1 }, { timeToLive: "1 minute" })`. A native resume source receives a callback and returns a cleanup function.
|
|
350
347
|
|
|
351
348
|
### Install, setup and limits
|
|
352
349
|
|
|
@@ -354,16 +351,10 @@ The shape types describe operation declarations, the bound types describe client
|
|
|
354
351
|
pnpm add @shivaedev/effect-contract effect@4.0.4
|
|
355
352
|
```
|
|
356
353
|
|
|
357
|
-
`effect` is a peer
|
|
354
|
+
`effect` is a peer, and the package needs Node.js 24 or newer. Choose the native server host separately; this package supplies no server process or HTTP transport implementation.
|
|
358
355
|
|
|
359
356
|
- Put schema declarations where server and browser can share them. Put service, authorization and transaction implementations on the server. React rendering and editable forms belong to `effect-react` and `effect-form`.
|
|
360
357
|
- Run command Effects with the Reactivity instance that owns the queries to refresh, as in the registry example.
|
|
361
358
|
- Choose a common read key for every query in a full reconciliation scope. A list key does not implicitly cover item queries.
|
|
362
|
-
- Supply browser or native resume sources explicitly.
|
|
363
|
-
- Live hints carry refresh requests, not authoritative data or durable history. Applications own dependency discovery, authenticated subscriptions, delivery across processes, SSR and deployment behavior.
|
|
364
|
-
|
|
365
|
-
The package's design priorities are in [docs/north-star.md](docs/north-star.md), and package work and maintainer questions are in [docs/roadmap.md](docs/roadmap.md).
|
|
366
|
-
|
|
367
|
-
### Server hint streams
|
|
368
|
-
|
|
369
|
-
Import `makeLiveServer` from `@shivaedev/effect-contract/makeLiveServer.ts` and create it once with the server's `Reactivity` service. After application authorization, return `hints.stream([orders.item(id)])` from the native streaming RPC handler. Each subscription starts with `Resync`; invalidating its read keys produces `Changed` with those declared keys. `hints.resync` sends `Resync` to every live stream owned by that helper, including streams for otherwise unrelated keys. Independent helpers keep their resync scopes separate even when they share Reactivity. The native stream owns registration and interruption. Keep this server helper separate from the browser's `live` coordinator.
|
|
359
|
+
- Supply browser or native resume sources explicitly.
|
|
360
|
+
- Live hints carry refresh requests, not authoritative data or durable history. Applications own dependency discovery, authenticated subscriptions, delivery across processes, SSR and deployment behavior.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type * as Atom from "effect/reactivity/Atom";
|
|
2
|
+
import type * as AtomRpc from "effect/reactivity/AtomRpc";
|
|
3
|
+
import { type Key } from "./keys.ts";
|
|
4
|
+
import { type LiveStatus } from "./live.ts";
|
|
5
|
+
import type { changesRpc } from "./serverMetadata.ts";
|
|
6
|
+
export declare const LiveConnection: unique symbol;
|
|
7
|
+
export interface LiveBound {
|
|
8
|
+
readonly [LiveConnection]: {
|
|
9
|
+
readonly connection: Atom.Atom<unknown>;
|
|
10
|
+
readonly status: Atom.Atom<LiveStatus<unknown>>;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export type ChangesClient = AtomRpc.AtomRpcClient<unknown, string, ReturnType<typeof changesRpc>>;
|
|
14
|
+
export declare function automaticLive(service: ChangesClient, name: string): {
|
|
15
|
+
subscription: {
|
|
16
|
+
connection: Atom.Atom<import("effect/reactivity/AsyncResult").AsyncResult<void, never>>;
|
|
17
|
+
paused: Atom.Writable<boolean, boolean>;
|
|
18
|
+
status: Atom.Writable<LiveStatus<import("effect/rpc/RpcClientError").RpcClientError>, LiveStatus<import("effect/rpc/RpcClientError").RpcClientError>>;
|
|
19
|
+
};
|
|
20
|
+
track: (reads: readonly Key[]) => void;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=automaticLive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"automaticLive.d.ts","sourceRoot":"","sources":["../src/automaticLive.ts"],"names":[],"mappings":"AACA,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;QAAE,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAA;KAAE,CAAC;CACxH;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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Effect, Stream, SubscriptionRef } from "effect";
|
|
2
|
+
import { readKeys } from "./keys.js";
|
|
3
|
+
import { live } from "./live.js";
|
|
4
|
+
import { resumeSignal } from "./resume.js";
|
|
5
|
+
export const LiveConnection = Symbol.for("@shivaedev/effect-contract/LiveConnection");
|
|
6
|
+
export function automaticLive(service, name) {
|
|
7
|
+
const keys = Effect.runSync(SubscriptionRef.make([]));
|
|
8
|
+
const known = new Set();
|
|
9
|
+
function track(reads) {
|
|
10
|
+
const fresh = reads.filter((key) => {
|
|
11
|
+
const id = readKeys([key])[0] ?? "";
|
|
12
|
+
if (known.has(id)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
known.add(id);
|
|
16
|
+
return true;
|
|
17
|
+
});
|
|
18
|
+
if (fresh.length > 0) {
|
|
19
|
+
Effect.runSync(SubscriptionRef.update(keys, (current) => [...current, ...fresh]));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const subscription = live(service.runtime, {
|
|
23
|
+
resume: resumeSignal(typeof window === "undefined" ? {} : { window }),
|
|
24
|
+
resyncKeys: () => Effect.runSync(SubscriptionRef.get(keys)),
|
|
25
|
+
stream: SubscriptionRef.changes(keys).pipe(Stream.switchMap((current) => current.length === 0 ? Stream.never : Stream.unwrap(service.use((client) => Effect.succeed(client(`${name}.$changes`, { keys: current })))))),
|
|
26
|
+
});
|
|
27
|
+
return { subscription, track };
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=automaticLive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"automaticLive.js","sourceRoot":"","sources":["../src/automaticLive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAGzD,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;AAOtF,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
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 } from "effect/rpc";
|
|
9
9
|
import type { RpcClientError } from "effect/rpc/RpcClientError";
|
|
10
|
+
import { type LiveBound } from "./automaticLive.ts";
|
|
10
11
|
import type { Contract, Tag } from "./contract.ts";
|
|
11
12
|
import type { CommandShape, QueryShape, StreamShape } from "./operation.ts";
|
|
12
13
|
export type Failure<R extends Rpc.Any> = R extends Rpc.Rpc<infer _Tag, infer _Payload, infer _Success, infer _Error, infer TMiddleware, infer _Requires> ? Rpc.ErrorExit<R> | TMiddleware["error"]["Type"] | RpcClientError : never;
|
|
@@ -38,6 +39,6 @@ type MatchingClient<TRpcs extends Rpc.Any, TClientRpcs extends Rpc.Any> = [TRpcs
|
|
|
38
39
|
}>] ? [Extract<TClientRpcs, {
|
|
39
40
|
readonly _tag: TRpcs["_tag"];
|
|
40
41
|
}>] extends [TRpcs] ? unknown : "Client must preserve the contract RPC types" : "Client must implement the contract RPCs";
|
|
41
|
-
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, TStreams extends readonly StreamShape[]>(contract: Contract<TName, TQueries, TCommands, TRpcs, TStreams>, service: AtomRpc.AtomRpcClient<TSelf, TId, TClientRpcs> & MatchingClient<NoInfer<TRpcs>, TClientRpcs>): Bound<TName, TQueries, TCommands, TRpcs, TSelf, TStreams
|
|
42
|
+
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, TStreams extends readonly StreamShape[]>(contract: Contract<TName, TQueries, TCommands, TRpcs, TStreams>, service: AtomRpc.AtomRpcClient<TSelf, TId, TClientRpcs> & MatchingClient<NoInfer<TRpcs>, TClientRpcs>): Bound<TName, TQueries, TCommands, TRpcs, TSelf, TStreams> & LiveBound;
|
|
42
43
|
export {};
|
|
43
44
|
//# sourceMappingURL=bind.d.ts.map
|
package/dist/bind.d.ts.map
CHANGED
|
@@ -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,15 +1,19 @@
|
|
|
1
1
|
import { Effect, Stream } from "effect";
|
|
2
2
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
3
|
+
import { automaticLive, LiveConnection } from "./automaticLive.js";
|
|
3
4
|
import { invalidationKeys, readKeys } from "./keys.js";
|
|
4
5
|
const invalidateAfter = Effect.fn("EffectContract.invalidateAfter")(function* (keys) {
|
|
5
6
|
yield* Reactivity.invalidate(invalidationKeys(keys));
|
|
6
7
|
});
|
|
7
|
-
function boundQuery(service, tag, query) {
|
|
8
|
+
function boundQuery(service, tag, query, track) {
|
|
8
9
|
function reads(payload) {
|
|
9
10
|
return readKeys(query.reads(payload));
|
|
10
11
|
}
|
|
11
12
|
return {
|
|
12
|
-
query: (payload, options = {}) =>
|
|
13
|
+
query: (payload, options = {}) => {
|
|
14
|
+
track(query.reads(payload));
|
|
15
|
+
return service.query(tag, payload, { ...options, reactivityKeys: reads(payload) });
|
|
16
|
+
},
|
|
13
17
|
run: (payload) => service.use((client) => client(tag, payload)),
|
|
14
18
|
};
|
|
15
19
|
}
|
|
@@ -25,10 +29,11 @@ function boundStream(service, tag) {
|
|
|
25
29
|
}
|
|
26
30
|
export function bind(contract, service) {
|
|
27
31
|
const { name, queries, commands, streams } = contract.declaration;
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
const automatic = automaticLive(service, name);
|
|
33
|
+
return Object.assign(Object.fromEntries([
|
|
34
|
+
...queries.map((query) => [query.name, boundQuery(service, `${name}.${query.name}`, query, automatic.track)]),
|
|
30
35
|
...commands.map((command) => [command.name, boundCommand(service, `${name}.${command.name}`, command)]),
|
|
31
36
|
...streams.map((stream) => [stream.name, boundStream(service, `${name}.${stream.name}`)]),
|
|
32
|
-
]);
|
|
37
|
+
]), { [LiveConnection]: automatic.subscription });
|
|
33
38
|
}
|
|
34
39
|
//# sourceMappingURL=bind.js.map
|
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;
|
|
1
|
+
{"version":3,"file":"bind.js","sourceRoot":"","sources":["../src/bind.ts"],"names":[],"mappings":"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"}
|
package/dist/contract.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Schema } from "effect";
|
|
2
2
|
import { Rpc, RpcGroup, type RpcMiddleware, type RpcSchema } from "effect/rpc";
|
|
3
3
|
import type { CommandShape, OperationShape, QueryShape, StreamShape } from "./operation.ts";
|
|
4
4
|
export type Tag<TContract extends string, TName extends string> = `${TContract}.${TName}`;
|
package/dist/contract.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
|
package/dist/contract.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Context } from "effect";
|
|
1
2
|
import { Rpc, RpcGroup } from "effect/rpc";
|
|
3
|
+
import { CommandInvalidation, changesRpc } from "./serverMetadata.js";
|
|
2
4
|
function assertUnique(operations) {
|
|
3
5
|
const duplicated = operations.map(({ name }) => name).filter((name, index, names) => names.indexOf(name) !== index);
|
|
4
6
|
if (duplicated.length > 0) {
|
|
@@ -20,7 +22,7 @@ export function contract(name, operations) {
|
|
|
20
22
|
payload: operation.payload,
|
|
21
23
|
stream: operation.kind === "stream",
|
|
22
24
|
success: operation.success,
|
|
23
|
-
}));
|
|
24
|
-
return withDeclaration(RpcGroup.make(...rpcs), { commands, name, queries, streams });
|
|
25
|
+
}).annotateMerge(operation.kind === "command" ? Context.make(CommandInvalidation, operation.invalidates) : Context.empty()));
|
|
26
|
+
return withDeclaration(RpcGroup.make(...rpcs, changesRpc(name)), { commands, name, queries, streams });
|
|
25
27
|
}
|
|
26
28
|
//# sourceMappingURL=contract.js.map
|
package/dist/contract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAe,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAsC,MAAM,YAAY,CAAC;AAE/E,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AA4CtE,SAAS,YAAY,CAAC,UAAqC;IAC1D,MAAM,UAAU,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC;IACpH,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvG,CAAC;AACF,CAAC;AAYD,SAAS,eAAe,CAAC,KAAY,EAAE,WAAwB;IAC9D,MAAM,gBAAgB,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtD,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE;QAC3B,WAAW;QACX,UAAU,EAAE,CAAC,UAAoC,EAAE,EAAE,CAAC,eAAe,CAAC,gBAAgB,CAAC,UAAU,CAAC,EAAE,WAAW,CAAC;KAChH,CAAC,CAAC;AACJ,CAAC;AAeD,MAAM,UAAU,QAAQ,CACvB,IAAY,EACZ,UAAgJ;IAEhJ,MAAM,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE,GAAG,UAAU,CAAC;IACjE,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,CAAC,GAAG,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CACpE,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC,IAAI,EAAE,EAAE;QACrC,KAAK,EAAE,SAAS,CAAC,KAAK;QACtB,OAAO,EAAE,SAAS,CAAC,OAAO;QAC1B,MAAM,EAAE,SAAS,CAAC,IAAI,KAAK,QAAQ;QACnC,OAAO,EAAE,SAAS,CAAC,OAAO;KAC1B,CAAC,CAAC,aAAa,CAAC,SAAS,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,mBAAmB,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAC3H,CAAC;IACF,OAAO,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AACxG,CAAC"}
|
package/dist/keys.d.ts
CHANGED
|
@@ -23,9 +23,9 @@ export interface Collection<TName extends string, TId extends Identity> {
|
|
|
23
23
|
readonly list: ListKey<TName>;
|
|
24
24
|
readonly name: TName;
|
|
25
25
|
}
|
|
26
|
-
export declare
|
|
26
|
+
export declare function collection<const TName extends string, TId extends Schema.Top & {
|
|
27
27
|
readonly Type: Identity;
|
|
28
|
-
}>(name: TName, _id: TId)
|
|
29
|
-
export declare
|
|
30
|
-
export declare
|
|
28
|
+
}>(name: TName, _id: TId): Collection<TName, TId["Type"]>;
|
|
29
|
+
export declare function readKeys(keys: readonly Key[]): readonly string[];
|
|
30
|
+
export declare function invalidationKeys(keys: readonly Key[]): readonly string[];
|
|
31
31
|
//# sourceMappingURL=keys.d.ts.map
|
package/dist/keys.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,KAAK,SAAS,MAAM,GAAG,MAAM;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC;CAC3B;AAED,MAAM,WAAW,OAAO,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC;IAC3B,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,KAAK,SAAS,MAAM,EAAE,GAAG,SAAS,QAAQ;IACrE,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;IAC3C,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;CACrB;AAED,
|
|
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,KAAK,SAAS,MAAM,GAAG,MAAM;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC;CAC3B;AAED,MAAM,WAAW,OAAO,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC;IAC3B,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,KAAK,SAAS,MAAM,EAAE,GAAG,SAAS,QAAQ;IACrE,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;IAC3C,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;CACrB;AAED,wBAAgB,UAAU,CAAC,KAAK,CAAC,KAAK,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,CAAC,GAAG,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,EAC1G,IAAI,EAAE,KAAK,EACX,GAAG,EAAE,GAAG,GACN,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAMhC;AAMD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,EAAE,GAAG,SAAS,MAAM,EAAE,CAEhE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,GAAG,EAAE,GAAG,SAAS,MAAM,EAAE,CAExE"}
|
package/dist/keys.js
CHANGED
|
@@ -3,16 +3,20 @@ export const Key = Schema.Union([
|
|
|
3
3
|
Schema.Struct({ _tag: Schema.Literal("List"), collection: Schema.String }),
|
|
4
4
|
Schema.Struct({ _tag: Schema.Literal("Item"), collection: Schema.String, id: Schema.Union([Schema.String, Schema.Number]) }),
|
|
5
5
|
]);
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
export function collection(name, _id) {
|
|
7
|
+
return {
|
|
8
|
+
item: (id) => ({ _tag: "Item", collection: name, id }),
|
|
9
|
+
list: { _tag: "List", collection: name },
|
|
10
|
+
name,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
11
13
|
function own(key) {
|
|
12
14
|
return key._tag === "List" ? key.collection : `${key.collection}:${key.id}`;
|
|
13
15
|
}
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
export function readKeys(keys) {
|
|
17
|
+
return [...new Set(keys.map(own))];
|
|
18
|
+
}
|
|
19
|
+
export function invalidationKeys(keys) {
|
|
20
|
+
return [...new Set(keys.flatMap((key) => (key._tag === "List" ? [key.collection] : [own(key), key.collection])))];
|
|
21
|
+
}
|
|
18
22
|
//# sourceMappingURL=keys.js.map
|
package/dist/keys.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,UAAU,UAAU,CACzB,IAAW,EACX,GAAQ;IAER,OAAO;QACN,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QACtD,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE;QACxC,IAAI;KACJ,CAAC;AACH,CAAC;AAED,SAAS,GAAG,CAAC,GAAQ;IACpB,OAAO,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAoB;IAC5C,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAoB;IACpD,OAAO,CAAC,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,CAAC,CAAC;AACnH,CAAC"}
|
package/dist/live.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export interface LiveStatus<E> {
|
|
|
23
23
|
}
|
|
24
24
|
export interface LiveOptions<E, R> {
|
|
25
25
|
readonly resume?: Atom.Atom<unknown>;
|
|
26
|
-
readonly resyncKeys: readonly Key[];
|
|
26
|
+
readonly resyncKeys: readonly Key[] | (() => readonly Key[]);
|
|
27
27
|
readonly retryDelay?: Duration.Input;
|
|
28
28
|
readonly stream: Stream.Stream<LiveHint, E, R>;
|
|
29
29
|
}
|
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,KAAK,QAAQ,EAAU,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACnF,OAAO,KAAK,IAAI,MAAM,wBAAwB,CAAC;AAE/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,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,SAAS,GAAG,EAAE,CAAC;
|
|
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,wBAAwB,CAAC;AAE/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,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;;;;EAuDhH"}
|
package/dist/live.js
CHANGED
|
@@ -22,7 +22,7 @@ export function live(runtime, options) {
|
|
|
22
22
|
update({ needsResync: true });
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
|
-
reactivity.invalidateUnsafe(readKeys(options.resyncKeys));
|
|
25
|
+
reactivity.invalidateUnsafe(readKeys(typeof options.resyncKeys === "function" ? options.resyncKeys() : options.resyncKeys));
|
|
26
26
|
update({ needsResync: false, pending: 0 });
|
|
27
27
|
}
|
|
28
28
|
get.subscribe(paused, (value) => {
|
package/dist/live.js.map
CHANGED
|
@@ -1 +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,wBAAwB,CAAC;AAC/C,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,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;
|
|
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,
|
|
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;AAC3D,OAAO,EAAE,KAAK,GAAG,EAAY,MAAM,WAAW,CAAC;AAG/C,eAAO,MAAM,cAAc;;mBAGJ,SAAS,GAAG,EAAE;;;;;;;;;;;;;gCAoDnC,CAAC"}
|
package/dist/makeLiveServer.js
CHANGED
|
@@ -1,24 +1,52 @@
|
|
|
1
|
-
import { Effect, Stream } from "effect";
|
|
1
|
+
import { Effect, Queue, Stream } from "effect";
|
|
2
2
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
3
3
|
import { readKeys } from "./keys.js";
|
|
4
4
|
export const makeLiveServer = Effect.gen(function* () {
|
|
5
5
|
const reactivity = yield* Reactivity.Reactivity;
|
|
6
6
|
const resyncKey = `@shivaedev/effect-contract/resync/${crypto.randomUUID()}`;
|
|
7
|
-
let generation = 0;
|
|
8
7
|
function stream(keys) {
|
|
9
|
-
return Stream.unwrap(Effect.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
return Stream.unwrap(Effect.gen(function* () {
|
|
9
|
+
const queue = yield* Queue.unbounded();
|
|
10
|
+
const pending = new Map();
|
|
11
|
+
let scheduled = false;
|
|
12
|
+
let resync = false;
|
|
13
|
+
let active = true;
|
|
14
|
+
function flush() {
|
|
15
|
+
if (scheduled) {
|
|
16
|
+
return;
|
|
15
17
|
}
|
|
16
|
-
|
|
18
|
+
scheduled = true;
|
|
19
|
+
queueMicrotask(() => {
|
|
20
|
+
scheduled = false;
|
|
21
|
+
if (active) {
|
|
22
|
+
Queue.offerUnsafe(queue, resync ? { _tag: "Resync" } : { _tag: "Changed", keys: [...pending.values()] });
|
|
23
|
+
}
|
|
24
|
+
pending.clear();
|
|
25
|
+
resync = false;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const cancel = keys.map((key) => {
|
|
29
|
+
const own = readKeys([key]);
|
|
30
|
+
return reactivity.registerUnsafe(own, () => {
|
|
31
|
+
pending.set(own[0] ?? "", key);
|
|
32
|
+
flush();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
cancel.push(reactivity.registerUnsafe([resyncKey], () => {
|
|
36
|
+
resync = true;
|
|
37
|
+
flush();
|
|
17
38
|
}));
|
|
39
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => {
|
|
40
|
+
active = false;
|
|
41
|
+
for (const stop of cancel) {
|
|
42
|
+
stop();
|
|
43
|
+
}
|
|
44
|
+
}));
|
|
45
|
+
yield* Queue.offer(queue, { _tag: "Resync" });
|
|
46
|
+
return Stream.fromQueue(queue);
|
|
18
47
|
}));
|
|
19
48
|
}
|
|
20
49
|
const resync = Effect.sync(() => {
|
|
21
|
-
generation += 1;
|
|
22
50
|
reactivity.invalidateUnsafe([resyncKey]);
|
|
23
51
|
});
|
|
24
52
|
return { resync, stream };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"makeLiveServer.js","sourceRoot":"","sources":["../src/makeLiveServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"makeLiveServer.js","sourceRoot":"","sources":["../src/makeLiveServer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Context, Schema } from "effect";
|
|
2
|
+
import { Rpc } from "effect/rpc";
|
|
3
|
+
import { Key } from "./keys.ts";
|
|
4
|
+
declare const CommandInvalidation_base: Context.ServiceClass<CommandInvalidation, "@shivaedev/effect-contract/CommandInvalidation", import("@shivaedev/types/bivariant.ts").Bivariant<(payload: unknown, result: unknown) => readonly Key[]>>;
|
|
5
|
+
export declare class CommandInvalidation extends CommandInvalidation_base {
|
|
6
|
+
}
|
|
7
|
+
export declare function changesRpc(name: string): Rpc.Rpc<`${string}.$changes`, Schema.Struct<{
|
|
8
|
+
keys: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
9
|
+
readonly _tag: Schema.Literal<"List">;
|
|
10
|
+
readonly collection: Schema.String;
|
|
11
|
+
}>, Schema.Struct<{
|
|
12
|
+
readonly _tag: Schema.Literal<"Item">;
|
|
13
|
+
readonly collection: Schema.String;
|
|
14
|
+
readonly id: Schema.Union<readonly [Schema.String, Schema.Number]>;
|
|
15
|
+
}>]>>;
|
|
16
|
+
}>, import("effect/rpc/RpcSchema").Stream<Schema.Union<readonly [Schema.Struct<{
|
|
17
|
+
readonly _tag: Schema.Literal<"Changed">;
|
|
18
|
+
readonly keys: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
19
|
+
readonly _tag: Schema.Literal<"List">;
|
|
20
|
+
readonly collection: Schema.String;
|
|
21
|
+
}>, Schema.Struct<{
|
|
22
|
+
readonly _tag: Schema.Literal<"Item">;
|
|
23
|
+
readonly collection: Schema.String;
|
|
24
|
+
readonly id: Schema.Union<readonly [Schema.String, Schema.Number]>;
|
|
25
|
+
}>]>>;
|
|
26
|
+
}>, Schema.Struct<{
|
|
27
|
+
readonly _tag: Schema.Literal<"Resync">;
|
|
28
|
+
}>]>, Schema.Never>, Schema.Never, never, never>;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=serverMetadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serverMetadata.d.ts","sourceRoot":"","sources":["../src/serverMetadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;;AAIhC,qBAAa,mBAAoB,SAAQ,wBAExC;CAAG;AAEJ,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;iDAEtC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Context, Schema } from "effect";
|
|
2
|
+
import { Rpc } from "effect/rpc";
|
|
3
|
+
import { Key } from "./keys.js";
|
|
4
|
+
import { LiveHint } from "./live.js";
|
|
5
|
+
export class CommandInvalidation extends Context.Service()("@shivaedev/effect-contract/CommandInvalidation") {
|
|
6
|
+
}
|
|
7
|
+
export function changesRpc(name) {
|
|
8
|
+
return Rpc.make(`${name}.$changes`, { payload: { keys: Schema.Array(Key) }, stream: true, success: LiveHint });
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=serverMetadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serverMetadata.js","sourceRoot":"","sources":["../src/serverMetadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGrC,MAAM,OAAO,mBAAoB,SAAQ,OAAO,CAAC,OAAO,EAAoD,CAC3G,gDAAgD,CAChD;CAAG;AAEJ,MAAM,UAAU,UAAU,CAAC,IAAY;IACtC,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,WAAW,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;AAChH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/effect-contract",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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": {
|
|
@@ -23,55 +23,12 @@
|
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"exports": {
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
"types": "./dist
|
|
29
|
-
"
|
|
30
|
-
"import": "./dist/bind.js",
|
|
31
|
-
"default": "./dist/bind.js"
|
|
32
|
-
},
|
|
33
|
-
"./contract.ts": {
|
|
34
|
-
"types": "./dist/contract.d.ts",
|
|
35
|
-
"source": "./src/contract.ts",
|
|
36
|
-
"import": "./dist/contract.js",
|
|
37
|
-
"default": "./dist/contract.js"
|
|
38
|
-
},
|
|
39
|
-
"./keys.ts": {
|
|
40
|
-
"types": "./dist/keys.d.ts",
|
|
41
|
-
"source": "./src/keys.ts",
|
|
42
|
-
"import": "./dist/keys.js",
|
|
43
|
-
"default": "./dist/keys.js"
|
|
44
|
-
},
|
|
45
|
-
"./live.ts": {
|
|
46
|
-
"types": "./dist/live.d.ts",
|
|
47
|
-
"source": "./src/live.ts",
|
|
48
|
-
"import": "./dist/live.js",
|
|
49
|
-
"default": "./dist/live.js"
|
|
50
|
-
},
|
|
51
|
-
"./makeLiveServer.ts": {
|
|
52
|
-
"types": "./dist/makeLiveServer.d.ts",
|
|
53
|
-
"source": "./src/makeLiveServer.ts",
|
|
54
|
-
"import": "./dist/makeLiveServer.js",
|
|
55
|
-
"default": "./dist/makeLiveServer.js"
|
|
56
|
-
},
|
|
57
|
-
"./operation.ts": {
|
|
58
|
-
"types": "./dist/operation.d.ts",
|
|
59
|
-
"source": "./src/operation.ts",
|
|
60
|
-
"import": "./dist/operation.js",
|
|
61
|
-
"default": "./dist/operation.js"
|
|
62
|
-
},
|
|
63
|
-
"./rejection.ts": {
|
|
64
|
-
"types": "./dist/rejection.d.ts",
|
|
65
|
-
"source": "./src/rejection.ts",
|
|
66
|
-
"import": "./dist/rejection.js",
|
|
67
|
-
"default": "./dist/rejection.js"
|
|
26
|
+
"./*.ts": {
|
|
27
|
+
"source": "./src/*.ts",
|
|
28
|
+
"types": "./dist/*.d.ts",
|
|
29
|
+
"default": "./dist/*.js"
|
|
68
30
|
},
|
|
69
|
-
"./
|
|
70
|
-
"types": "./dist/resume.d.ts",
|
|
71
|
-
"source": "./src/resume.ts",
|
|
72
|
-
"import": "./dist/resume.js",
|
|
73
|
-
"default": "./dist/resume.js"
|
|
74
|
-
}
|
|
31
|
+
"./package.json": "./package.json"
|
|
75
32
|
},
|
|
76
33
|
"files": [
|
|
77
34
|
"dist",
|
|
@@ -79,24 +36,23 @@
|
|
|
79
36
|
"!src/**/*.test.ts",
|
|
80
37
|
"!src/**/*.spec.ts",
|
|
81
38
|
"!src/**/test-support",
|
|
82
|
-
"docs",
|
|
83
39
|
"CHANGELOG.md",
|
|
84
40
|
"README.md"
|
|
85
41
|
],
|
|
86
42
|
"dependencies": {
|
|
87
|
-
"@shivaedev/types": "0.2.
|
|
43
|
+
"@shivaedev/types": "0.2.1"
|
|
88
44
|
},
|
|
89
45
|
"devDependencies": {
|
|
90
46
|
"@effect/platform-node": "4.0.4",
|
|
91
47
|
"@effect/vitest": "4.0.4",
|
|
92
|
-
"@shivaedev/effect-test": "0.
|
|
93
|
-
"@shivaedev/quality": "0.
|
|
94
|
-
"@
|
|
95
|
-
"@
|
|
48
|
+
"@shivaedev/effect-test": "0.7.1",
|
|
49
|
+
"@shivaedev/quality": "0.28.0",
|
|
50
|
+
"@shivaedev/test-story": "0.12.0",
|
|
51
|
+
"@types/node": "24.19.2",
|
|
96
52
|
"effect": "4.0.4",
|
|
97
53
|
"esbuild": "0.28.2",
|
|
98
|
-
"happy-dom": "20.14.
|
|
99
|
-
"typescript": "
|
|
54
|
+
"happy-dom": "20.14.6",
|
|
55
|
+
"typescript": "7.0.2",
|
|
100
56
|
"vitest": "5.0.3"
|
|
101
57
|
},
|
|
102
58
|
"peerDependencies": {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Effect, Stream, SubscriptionRef } from "effect";
|
|
2
|
+
import type * as Atom from "effect/reactivity/Atom";
|
|
3
|
+
import type * as AtomRpc from "effect/reactivity/AtomRpc";
|
|
4
|
+
import { type Key, readKeys } from "./keys.ts";
|
|
5
|
+
import { type LiveStatus, live } from "./live.ts";
|
|
6
|
+
import { type ResumeWindow, resumeSignal } from "./resume.ts";
|
|
7
|
+
import type { changesRpc } from "./serverMetadata.ts";
|
|
8
|
+
|
|
9
|
+
declare const window: ResumeWindow | undefined;
|
|
10
|
+
|
|
11
|
+
export const LiveConnection = Symbol.for("@shivaedev/effect-contract/LiveConnection");
|
|
12
|
+
export interface LiveBound {
|
|
13
|
+
readonly [LiveConnection]: { readonly connection: Atom.Atom<unknown>; readonly status: Atom.Atom<LiveStatus<unknown>> };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ChangesClient = AtomRpc.AtomRpcClient<unknown, string, ReturnType<typeof changesRpc>>;
|
|
17
|
+
|
|
18
|
+
export function automaticLive(service: ChangesClient, name: string) {
|
|
19
|
+
const keys = Effect.runSync(SubscriptionRef.make<readonly Key[]>([]));
|
|
20
|
+
const known = new Set<string>();
|
|
21
|
+
function track(reads: readonly Key[]) {
|
|
22
|
+
const fresh = reads.filter((key) => {
|
|
23
|
+
const id = readKeys([key])[0] ?? "";
|
|
24
|
+
if (known.has(id)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
known.add(id);
|
|
28
|
+
return true;
|
|
29
|
+
});
|
|
30
|
+
if (fresh.length > 0) {
|
|
31
|
+
Effect.runSync(SubscriptionRef.update(keys, (current) => [...current, ...fresh]));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const subscription = live(service.runtime, {
|
|
35
|
+
resume: resumeSignal(typeof window === "undefined" ? {} : { window }),
|
|
36
|
+
resyncKeys: () => Effect.runSync(SubscriptionRef.get(keys)),
|
|
37
|
+
stream: SubscriptionRef.changes(keys).pipe(
|
|
38
|
+
Stream.switchMap((current) =>
|
|
39
|
+
current.length === 0 ? Stream.never : Stream.unwrap(service.use((client) => Effect.succeed(client(`${name}.$changes`, { keys: current })))),
|
|
40
|
+
),
|
|
41
|
+
),
|
|
42
|
+
});
|
|
43
|
+
return { subscription, track };
|
|
44
|
+
}
|
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 { automaticLive, type LiveBound, LiveConnection } from "./automaticLive.ts";
|
|
10
11
|
import type { Contract, Declared, Tag } from "./contract.ts";
|
|
11
12
|
import { invalidationKeys, type Key, readKeys } from "./keys.ts";
|
|
12
13
|
import type { CommandShape, QueryShape, StreamShape } from "./operation.ts";
|
|
@@ -73,12 +74,15 @@ const invalidateAfter = Effect.fn("EffectContract.invalidateAfter")(function* (k
|
|
|
73
74
|
yield* Reactivity.invalidate(invalidationKeys(keys));
|
|
74
75
|
});
|
|
75
76
|
|
|
76
|
-
function boundQuery(service: ErasedClient, tag: string, query: QueryShape) {
|
|
77
|
+
function boundQuery(service: ErasedClient, tag: string, query: QueryShape, track: (keys: readonly Key[]) => void) {
|
|
77
78
|
function reads(payload: unknown) {
|
|
78
79
|
return readKeys(query.reads(payload));
|
|
79
80
|
}
|
|
80
81
|
return {
|
|
81
|
-
query: (payload: unknown, options: QueryOptions = {}) =>
|
|
82
|
+
query: (payload: unknown, options: QueryOptions = {}) => {
|
|
83
|
+
track(query.reads(payload));
|
|
84
|
+
return service.query(tag, payload, { ...options, reactivityKeys: reads(payload) });
|
|
85
|
+
},
|
|
82
86
|
run: (payload: unknown) => service.use((client) => client(tag, payload)),
|
|
83
87
|
};
|
|
84
88
|
}
|
|
@@ -108,15 +112,19 @@ export function bind<
|
|
|
108
112
|
>(
|
|
109
113
|
contract: Contract<TName, TQueries, TCommands, TRpcs, TStreams>,
|
|
110
114
|
service: AtomRpc.AtomRpcClient<TSelf, TId, TClientRpcs> & MatchingClient<NoInfer<TRpcs>, TClientRpcs>,
|
|
111
|
-
): Bound<TName, TQueries, TCommands, TRpcs, TSelf, TStreams
|
|
115
|
+
): Bound<TName, TQueries, TCommands, TRpcs, TSelf, TStreams> & LiveBound;
|
|
112
116
|
export function bind(
|
|
113
117
|
contract: { readonly declaration: Declared<string, readonly QueryShape[], readonly CommandShape[], readonly StreamShape[]> },
|
|
114
118
|
service: AtomRpc.AtomRpcClient<unknown, string, Rpc.Any>,
|
|
115
119
|
): unknown {
|
|
116
120
|
const { name, queries, commands, streams } = contract.declaration;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
const automatic = automaticLive(service, name);
|
|
122
|
+
return Object.assign(
|
|
123
|
+
Object.fromEntries([
|
|
124
|
+
...queries.map((query) => [query.name, boundQuery(service, `${name}.${query.name}`, query, automatic.track)] as const),
|
|
125
|
+
...commands.map((command) => [command.name, boundCommand(service, `${name}.${command.name}`, command)] as const),
|
|
126
|
+
...streams.map((stream) => [stream.name, boundStream(service, `${name}.${stream.name}`)] as const),
|
|
127
|
+
]),
|
|
128
|
+
{ [LiveConnection]: automatic.subscription },
|
|
129
|
+
);
|
|
122
130
|
}
|
package/src/contract.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { Context, type Schema } from "effect";
|
|
2
2
|
import { Rpc, RpcGroup, type RpcMiddleware, type RpcSchema } from "effect/rpc";
|
|
3
3
|
import type { CommandShape, OperationShape, QueryShape, StreamShape } from "./operation.ts";
|
|
4
|
+
import { CommandInvalidation, changesRpc } from "./serverMetadata.ts";
|
|
4
5
|
|
|
5
6
|
export type Tag<TContract extends string, TName extends string> = `${TContract}.${TName}`;
|
|
6
7
|
|
|
@@ -94,7 +95,7 @@ export function contract(
|
|
|
94
95
|
payload: operation.payload,
|
|
95
96
|
stream: operation.kind === "stream",
|
|
96
97
|
success: operation.success,
|
|
97
|
-
}),
|
|
98
|
+
}).annotateMerge(operation.kind === "command" ? Context.make(CommandInvalidation, operation.invalidates) : Context.empty()),
|
|
98
99
|
);
|
|
99
|
-
return withDeclaration(RpcGroup.make(...rpcs), { commands, name, queries, streams });
|
|
100
|
+
return withDeclaration(RpcGroup.make(...rpcs, changesRpc(name)), { commands, name, queries, streams });
|
|
100
101
|
}
|
package/src/keys.ts
CHANGED
|
@@ -26,21 +26,25 @@ export interface Collection<TName extends string, TId extends Identity> {
|
|
|
26
26
|
readonly name: TName;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export
|
|
29
|
+
export function collection<const TName extends string, TId extends Schema.Top & { readonly Type: Identity }>(
|
|
30
30
|
name: TName,
|
|
31
31
|
_id: TId,
|
|
32
|
-
): Collection<TName, TId["Type"]>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
): Collection<TName, TId["Type"]> {
|
|
33
|
+
return {
|
|
34
|
+
item: (id) => ({ _tag: "Item", collection: name, id }),
|
|
35
|
+
list: { _tag: "List", collection: name },
|
|
36
|
+
name,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
37
39
|
|
|
38
40
|
function own(key: Key): string {
|
|
39
41
|
return key._tag === "List" ? key.collection : `${key.collection}:${key.id}`;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
export
|
|
44
|
+
export function readKeys(keys: readonly Key[]): readonly string[] {
|
|
45
|
+
return [...new Set(keys.map(own))];
|
|
46
|
+
}
|
|
43
47
|
|
|
44
|
-
export
|
|
45
|
-
...new Set(keys.flatMap((key) => (key._tag === "List" ? [key.collection] : [own(key), key.collection])))
|
|
46
|
-
|
|
48
|
+
export function invalidationKeys(keys: readonly Key[]): readonly string[] {
|
|
49
|
+
return [...new Set(keys.flatMap((key) => (key._tag === "List" ? [key.collection] : [own(key), key.collection])))];
|
|
50
|
+
}
|
package/src/live.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface LiveStatus<E> {
|
|
|
16
16
|
}
|
|
17
17
|
export interface LiveOptions<E, R> {
|
|
18
18
|
readonly resume?: Atom.Atom<unknown>;
|
|
19
|
-
readonly resyncKeys: readonly Key[];
|
|
19
|
+
readonly resyncKeys: readonly Key[] | (() => readonly Key[]);
|
|
20
20
|
readonly retryDelay?: Duration.Input;
|
|
21
21
|
readonly stream: Stream.Stream<LiveHint, E, R>;
|
|
22
22
|
}
|
|
@@ -39,7 +39,7 @@ export function live<R, E, TRuntimeError>(runtime: Atom.AtomRuntime<R, TRuntimeE
|
|
|
39
39
|
update({ needsResync: true });
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
|
-
reactivity.invalidateUnsafe(readKeys(options.resyncKeys));
|
|
42
|
+
reactivity.invalidateUnsafe(readKeys(typeof options.resyncKeys === "function" ? options.resyncKeys() : options.resyncKeys));
|
|
43
43
|
update({ needsResync: false, pending: 0 });
|
|
44
44
|
}
|
|
45
45
|
get.subscribe(paused, (value) => {
|
package/src/makeLiveServer.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Stream } from "effect";
|
|
1
|
+
import { Effect, Queue, Stream } from "effect";
|
|
2
2
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
3
3
|
import { type Key, readKeys } from "./keys.ts";
|
|
4
4
|
import type { LiveHint } from "./live.ts";
|
|
@@ -6,26 +6,55 @@ import type { LiveHint } from "./live.ts";
|
|
|
6
6
|
export const makeLiveServer = Effect.gen(function* () {
|
|
7
7
|
const reactivity = yield* Reactivity.Reactivity;
|
|
8
8
|
const resyncKey = `@shivaedev/effect-contract/resync/${crypto.randomUUID()}`;
|
|
9
|
-
let generation = 0;
|
|
10
9
|
function stream(keys: readonly Key[]) {
|
|
11
10
|
return Stream.unwrap(
|
|
12
|
-
Effect.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
Effect.gen(function* () {
|
|
12
|
+
const queue = yield* Queue.unbounded<LiveHint>();
|
|
13
|
+
const pending = new Map<string, Key>();
|
|
14
|
+
let scheduled = false;
|
|
15
|
+
let resync = false;
|
|
16
|
+
let active = true;
|
|
17
|
+
function flush() {
|
|
18
|
+
if (scheduled) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
scheduled = true;
|
|
22
|
+
queueMicrotask(() => {
|
|
23
|
+
scheduled = false;
|
|
24
|
+
if (active) {
|
|
25
|
+
Queue.offerUnsafe(queue, resync ? { _tag: "Resync" } : { _tag: "Changed", keys: [...pending.values()] });
|
|
20
26
|
}
|
|
21
|
-
|
|
27
|
+
pending.clear();
|
|
28
|
+
resync = false;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const cancel = keys.map((key) => {
|
|
32
|
+
const own = readKeys([key]);
|
|
33
|
+
return reactivity.registerUnsafe(own, () => {
|
|
34
|
+
pending.set(own[0] ?? "", key);
|
|
35
|
+
flush();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
cancel.push(
|
|
39
|
+
reactivity.registerUnsafe([resyncKey], () => {
|
|
40
|
+
resync = true;
|
|
41
|
+
flush();
|
|
22
42
|
}),
|
|
23
43
|
);
|
|
44
|
+
yield* Effect.addFinalizer(() =>
|
|
45
|
+
Effect.sync(() => {
|
|
46
|
+
active = false;
|
|
47
|
+
for (const stop of cancel) {
|
|
48
|
+
stop();
|
|
49
|
+
}
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
yield* Queue.offer(queue, { _tag: "Resync" });
|
|
53
|
+
return Stream.fromQueue(queue);
|
|
24
54
|
}),
|
|
25
55
|
);
|
|
26
56
|
}
|
|
27
57
|
const resync = Effect.sync(() => {
|
|
28
|
-
generation += 1;
|
|
29
58
|
reactivity.invalidateUnsafe([resyncKey]);
|
|
30
59
|
});
|
|
31
60
|
return { resync, stream };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Context, Schema } from "effect";
|
|
2
|
+
import { Rpc } from "effect/rpc";
|
|
3
|
+
import { Key } from "./keys.ts";
|
|
4
|
+
import { LiveHint } from "./live.ts";
|
|
5
|
+
import type { CommandShape } from "./operation.ts";
|
|
6
|
+
|
|
7
|
+
export class CommandInvalidation extends Context.Service<CommandInvalidation, CommandShape["invalidates"]>()(
|
|
8
|
+
"@shivaedev/effect-contract/CommandInvalidation",
|
|
9
|
+
) {}
|
|
10
|
+
|
|
11
|
+
export function changesRpc(name: string) {
|
|
12
|
+
return Rpc.make(`${name}.$changes`, { payload: { keys: Schema.Array(Key) }, stream: true, success: LiveHint });
|
|
13
|
+
}
|
package/docs/north-star.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# North star
|
|
2
|
-
|
|
3
|
-
## The problem
|
|
4
|
-
|
|
5
|
-
A feature crosses several boundaries. A service knows its input, result and expected failures. An RPC operation repeats them. A client needs the same types, then separately needs to know which queries a successful write should refresh. When those descriptions live apart, they drift: a failure becomes an untyped message, a newly created item does not refresh, or a broad invalidation replaces unrelated views.
|
|
6
|
-
|
|
7
|
-
Effect already provides the underlying pieces: Schema, typed Effects, Rpc, RpcGroup, middleware, AtomRpc, Reactivity and Streams. The missing piece is a small declaration that ties the feature's boundary and read/write dependencies together. Replacing those native pieces would introduce another model to learn and another owner for errors, interruption and resource lifetime.
|
|
8
|
-
|
|
9
|
-
## The ideal
|
|
10
|
-
|
|
11
|
-
A feature declares its boundary once. Its payload and success come from Schema. Its expected failures are tagged rejection classes that handlers and clients share. Queries name the dependencies they read. Commands name the dependencies their successful results change, including IDs returned by a create operation.
|
|
12
|
-
|
|
13
|
-
That declaration is an ordinary native RPC group. Implementing, securing, hosting and calling it uses the APIs an Effect engineer already knows. The client binding adds only the mechanical work: register declared read keys on native query atoms and invalidate declared change keys after a successful call.
|
|
14
|
-
|
|
15
|
-
Freshness has one vocabulary. An item change invalidates that item and its collection list. A reconciliation request uses an explicit read scope, rather than guessing that a list key covers all item queries. Optional live hints and resume signals request those same reads again. They do not hold replacement data; the successful query remains authoritative.
|
|
16
|
-
|
|
17
|
-
## What good looks like
|
|
18
|
-
|
|
19
|
-
- A reader finds the input, result, rejection and dependency rule together, and an agent can infer the types without repeating them at call sites.
|
|
20
|
-
- A domain service can raise a rejection class that several operations reuse. Generated field rejections name the form fields they describe.
|
|
21
|
-
- An ordinary query/command feature needs no subscription. A feature needing live freshness composes a native Stream with the same registry and keys.
|
|
22
|
-
- A successful command refreshes affected views. A rejected command leaves those reads alone. A reconnect reconciles the caller's explicit scope, including changes missed while disconnected.
|
|
23
|
-
- Native atoms retain query state and own stale-request interruption. The helper never becomes another cache or a parallel resource owner.
|
|
24
|
-
- Browser code can import declarations, bindings and resume signals without bringing in React or server implementation packages.
|
|
25
|
-
- Type tests, native client tests and real HTTP tests make different claims. Each claim stops at the boundary its evidence actually exercises.
|
|
26
|
-
|
|
27
|
-
## Trade-offs
|
|
28
|
-
|
|
29
|
-
- **Native composition over a shorter host setup.** Applications choose their native middleware, server host, serialization and AtomRpc service. The package removes repeated declarations without concealing those decisions.
|
|
30
|
-
- **Precise dependency keys over convenient broad refresh.** A feature must name its reads and changes. Reconciliation must name a common scope when it needs one. Incorrect dependency discovery is an application error the library cannot infer away.
|
|
31
|
-
- **Ordinary calls over mandatory streaming.** Live invalidation is optional. No query waits for a journal sequence, subscription cursor or change acknowledgment to return its domain value.
|
|
32
|
-
- **Bounded observations over replay.** Paused live coordination records a bounded observation count and asks for current values on resume. It does not retain an unbounded list of events or promise recovery of each source change.
|
|
33
|
-
- **Typed expected failures over transport-owned business wording.** Rejection schemas describe domain failures. Authentication policy, user-facing text and error presentation remain with the application and neighbouring packages.
|
|
34
|
-
- **Consumer evidence over speculative helpers.** Add an abstraction after a consuming feature demonstrates repeated work. Preserve the native route for cases the helper does not cover.
|
|
35
|
-
|
|
36
|
-
## What it deliberately leaves out
|
|
37
|
-
|
|
38
|
-
- Service implementations, repositories, SQL transactions and commit-bound publication. These belong to `effect-service`, `effect-sql` and `effect-changes`.
|
|
39
|
-
- Authentication policy, session resolution and request identity implementation. The shared server boundary belongs to `platform`; the application's host supplies its policy.
|
|
40
|
-
- React hooks, rendering, editable drafts and form submission state. These belong to `effect-react` and `effect-form`.
|
|
41
|
-
- A second client cache, a custom RPC transport or a required mutation atom.
|
|
42
|
-
- An event journal, replay cursor, authoritative data stream, offline writes or cross-process delivery policy.
|
|
43
|
-
- Claims of SSR, authenticated live delivery, physical browser lifecycle or native-device behavior based only on a local library fixture.
|
|
44
|
-
|
|
45
|
-
Package status belongs in [roadmap.md](roadmap.md). Framework composition and deployment decisions belong in the [framework roadmap](https://github.com/ShivaeDev/platform/blob/main/docs/framework/roadmap.md).
|
package/docs/roadmap.md
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# Roadmap
|
|
2
|
-
|
|
3
|
-
This file owns effect-contract's implementation status and package questions. The [framework roadmap](https://github.com/ShivaeDev/platform/blob/main/docs/framework/roadmap.md) owns application composition, adoption and deployment validation; it links here instead of repeating these package milestones.
|
|
4
|
-
|
|
5
|
-
## Built
|
|
6
|
-
|
|
7
|
-
- [x] `query`, `command` and `contract`: Schema payloads/results, namespaced native RPC groups, inline declarations, typed defaults, native handler layers and middleware. Compiler fixtures and native client round-trips cover the declared types and failures.
|
|
8
|
-
- [x] `stream(name, { payload?, success, rejections?, reads })` declarations in `contract(..., { streams })` map to native RPC with `stream: true`. Bound `run` returns a native Stream. Compiler checks cover payloads, elements, errors, duplicate names and unary-client mismatch; real HTTP tests cover delivery, declared rejection, completion and client interruption releasing the server subscription. Work Board uses this path for its live subscription.
|
|
9
|
-
- [x] Shared tagged rejection classes, generated operation-specific rejection classes and typed `reject` helpers. Field rejections derive and optionally narrow a struct's field names.
|
|
10
|
-
- [x] Unique operation names checked in literal declarations and at construction when arrays conceal them.
|
|
11
|
-
- [x] Collection item/list keys with native read hashing, deduplication and item-to-list mutation invalidation. Native Reactivity tests distinguish affected and unrelated items.
|
|
12
|
-
- [x] Native AtomRpc binding with query atoms and per-call Effects. Successful commands invalidate their declared keys, including keys derived from results; rejected commands do not invalidate. Client type checks cover matching, larger, incomplete and incompatible RPC groups.
|
|
13
|
-
- [x] Optional `LiveHint`/`Key` schemas and native live coordination: precise refresh, explicit first/reconnected scope reconciliation, a paused count capped at 256, typed stream failures and registry teardown. The framework guide owns the composed HTTP fixture evidence.
|
|
14
|
-
- [x] `makeLiveServer` turns committed Reactivity keys into scoped hint streams and resyncs every active read scope. [Server evidence](https://github.com/ShivaeDev/platform/blob/main/packages/effect-contract/src/makeLiveServer.test.ts).
|
|
15
|
-
- [x] Shared browser/native `resumeSignal`. Synthetic event tests cover visible versus hidden browser events, injected native callbacks, native SWR composition and listener disposal. The installed consumer checks an otherwise unobserved source's initialization and cleanup through `live`.
|
|
16
|
-
- [x] Installed-package runtime and type fixtures for contracts, keys, rejection fields and live pause/resume composition.
|
|
17
|
-
|
|
18
|
-
## Next
|
|
19
|
-
|
|
20
|
-
No additional declaration or live helper is selected beyond the server helper used by the reference order handlers. A concrete consuming feature should establish the next repeated task before this package adds an abstraction. Host/proxy behavior, authenticated subscriptions, SSR and physical browser/mobile lifecycle remain framework or application validation work.
|
|
21
|
-
|
|
22
|
-
## Open questions
|
|
23
|
-
|
|
24
|
-
- Should query freshness, including live coordination and resume, remain this package's long-term scope? The APIs live here and public package consumers use them; the original declaration boundary does not by itself decide every future lifecycle helper.
|
|
25
|
-
- Is the exported lower-level `rejectionSet` intended for application authors as well as operation construction? It is public and the operation tests exercise its implementation; direct standalone use has no dedicated regression fixture.
|
|
26
|
-
- Which concrete consumer requirement should select the next package helper? There is no package-specific committed API backlog beyond the existing declarations, binding and freshness composition.
|