@shivaedev/effect-contract 0.0.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 - 2026-10-04
4
+
5
+ ### Changed
6
+
7
+ - Consolidate duplicate test cases around observable package behavior.
8
+ - The function members of `Contract`, `Query` and `Command` are declared as properties, so TypeScript checks their parameters strictly. `QueryShape.reads` and `CommandShape.invalidates` stay bivariant, so a contract still takes operations with typed payloads; the package now depends on `@shivaedev/types` for `Bivariant`.
9
+
10
+ - Validate packed consumers and executable bins through the shared workspace gate.
11
+
12
+ - Build with the `@shivaedev/quality` tsconfig presets, which target ESNext and allow only erasable TypeScript syntax.
13
+
14
+ - Type-check with TypeScript 7 only. TypeScript 6 still builds the package.
15
+
16
+ - Lint and format with the `@shivaedev/quality` Biome preset through `quality lint` at the repository root, which replaces the package's `check` script.
17
+
18
+ - Run tests through the `@shivaedev/quality` Vitest projects, which take a test's environment from its file name. Type tests are named `*.typecheck.test.ts`.
19
+
20
+ - Keep `package.json` in sort-package-json key order, checked by `quality lint`.
21
+
22
+ ## 0.1.0 - 2026-09-26
23
+
24
+ ### Added
25
+
26
+ - Declare queries and commands with typed rejections and reactivity keys; group
27
+ them into a native `RpcGroup` with namespaced tags.
28
+ - Bind a contract to a native `AtomRpc` service: query atoms register declared
29
+ read keys; command runs invalidate declared keys after success.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ShivaeDev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,89 @@
1
1
  # @shivaedev/effect-contract
2
2
 
3
- This version only reserves the name. Releases are published from https://github.com/ShivaeDev/platform.
3
+ Declare queries and commands once. Each declaration becomes an ordinary native Effect `Rpc`, grouped into a native `RpcGroup`, plus the typed rejection classes and reactivity keys that application code would otherwise repeat by hand. Handlers, middleware, servers and clients stay native.
4
+
5
+ ```ts
6
+ import { Schema } from "effect";
7
+ import { collection, command, contract, fieldRejection, query } from "@shivaedev/effect-contract";
8
+
9
+ class Order extends Schema.Class<Order>("Order")({ id: Schema.Number, name: Schema.String, quantity: Schema.Number }) {}
10
+ class OrderNotFound extends Schema.TaggedError<OrderNotFound>()("OrderNotFound", {}) {}
11
+ const OrderDraft = Schema.Struct({ name: Order.fields.name, quantity: Order.fields.quantity });
12
+
13
+ export const orders = collection("orders", Order.fields.id);
14
+
15
+ export const GetOrder = query("get", {
16
+ payload: { id: Schema.Number },
17
+ success: Order,
18
+ rejections: { OrderNotFound },
19
+ reads: ({ id }) => [orders.item(id)],
20
+ });
21
+ export const ListOrders = query("list", { success: Schema.Array(Order), reads: () => [orders.list] });
22
+ export const SaveOrder = command("save", {
23
+ payload: { id: Schema.Number, ...OrderDraft.fields },
24
+ success: Order,
25
+ rejections: { OrderNotFound, OrderValidation: fieldRejection(OrderDraft) },
26
+ invalidates: ({ id }) => [orders.item(id)],
27
+ });
28
+
29
+ export const Orders = contract("orders", { queries: [GetOrder, ListOrders], commands: [SaveOrder] }).middleware(Authentication);
30
+ ```
31
+
32
+ ## API
33
+
34
+ | Export | Signature (simplified) | Result |
35
+ | --- | --- | --- |
36
+ | `query` | `query(name, { payload?, success?, rejections?, reads: (payload) => Key[] })` | `Query<Name, Payload, Success, Specs>` |
37
+ | `command` | `command(name, { payload?, success?, rejections?, invalidates: (payload, result) => Key[] })` | `Command<Name, Payload, Success, Specs>` |
38
+ | `contract` | `contract(name, { queries?, commands? })` | `Contract<Name, Queries, Commands, Rpcs>`, a native `RpcGroup<Rpcs>` |
39
+ | `collection` | `collection(name, idSchema)` | `{ name, list, item(id) }` typed reactivity keys |
40
+ | `fieldRejection` | `fieldRejection(struct, keys?)` | fields `{ field: Literals<keys>, message: String }` |
41
+ | `bind` | `bind(contract, atomRpcService)` | `{ [query]: { query, run }, [command]: { run } }` |
42
+ | `readKeys` / `invalidationKeys` | `(keys: Key[]) => string[]` | native Reactivity keys |
43
+
44
+ Payload accepts struct fields or any schema, as in `Rpc.make`. Omitted payload and success are `Schema.Void`.
45
+
46
+ ### Operations and rejections
47
+
48
+ - Each operation exposes `payload`, `success`, `error` (the rejection union, or `Schema.Never` without rejections), `Rejection.X` (the class) and `reject.X(...)`, an `Effect<never, X>`.
49
+ - `rejections: { Tag: fields }` generates a `Schema.TaggedError` class with that `_tag`. `rejections: { Tag: ExistingClass }` reuses a class; its `_tag` must equal the key. Reuse one class when several operations or a shared service raise the same rejection. Generated classes are distinct per operation.
50
+ - `fieldRejection(OrderDraft)` derives the `field` literal union from the struct's keys, so a form can attach the rejection to one of its own fields. Pass a key list to narrow it.
51
+
52
+ ### Contract
53
+
54
+ - RPC tags are `` `${contract}.${operation}` ``. Implement handlers with native `Orders.toLayer(Effect.gen(...))` and `Orders.of({ "orders.get": ... })`; `of` rejects wrong payloads, successes and undeclared failures.
55
+ - Middleware is the native `.middleware(M)`. On a contract it returns a contract, keeping `declaration` (`{ name, queries, commands }`). Other native group combinators (`add`, `merge`, `prefix`, `omit`) return plain groups.
56
+ - Operation names in one contract must all differ, across and within queries and commands. The compiler rejects duplicates in literal arrays; `contract` throws for duplicates it cannot see.
57
+ - Operations can be declared inline in `contract(...)` or beforehand; both keep the same types and defaults.
58
+
59
+ ### Reactivity keys
60
+
61
+ `collection("orders", Order.fields.id)` gives `orders.list` and `orders.item(id)`; ids must be strings or numbers. The binding applies the one rule that is easy to get wrong with native keys:
62
+
63
+ | Declared | Query registers | Command invalidates |
64
+ | --- | --- | --- |
65
+ | `orders.item(id)` | `` `orders:${id}` `` | `` `orders:${id}` `` and `"orders"` |
66
+ | `orders.list` | `"orders"` | `"orders"` |
67
+
68
+ An item change therefore refreshes that item and every list, but not other items. Invalidating `orders.list` does not refresh item queries. The strings match native Reactivity's hashing of `{ orders: [id] }`.
69
+
70
+ ### Client binding
71
+
72
+ `bind` is browser-safe and uses no React:
73
+
74
+ ```ts
75
+ class OrdersClient extends AtomRpc.Service<OrdersClient>()("app/OrdersClient", { group: Orders, protocol }) {}
76
+ const api = bind(Orders, OrdersClient);
77
+
78
+ api.get.query({ id }, { timeToLive: "1 minute" }); // native Client.query atom with the declared read keys
79
+ api.get.run({ id }); // one call, no registration
80
+ api.save.run(input); // Effect: call, then invalidate declared keys
81
+ ```
82
+
83
+ - `query` returns the native `AtomRpc` query atom; `headers`, `timeToLive` and `serializationKey` pass through.
84
+ - `run` returns `Effect<Success, Rejection | middleware error | RpcClientError, OrdersClient | Reactivity>` for that single invocation. Run it through the service's atom runtime (for example as a form's submit handler) so invalidation reaches the same registry. Keys, including result-dependent keys, are invalidated only after success.
85
+ - There is no shared mutation atom or second cache.
86
+
87
+ Authorization and transactions stay in handlers and services. For server-side invalidation after a SQL commit, see `transact` in `@shivaedev/effect-sql`.
88
+
89
+ This release targets Effect `4.0.0-rc.112`.
package/dist/bind.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ import type { Duration } from "effect";
2
+ import { Effect } from "effect";
3
+ import type { Headers } from "effect/unstable/http";
4
+ import type * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
5
+ import type * as Atom from "effect/unstable/reactivity/Atom";
6
+ import type * as AtomRpc from "effect/unstable/reactivity/AtomRpc";
7
+ import * as Reactivity from "effect/unstable/reactivity/Reactivity";
8
+ import type { Rpc } from "effect/unstable/rpc";
9
+ import type { RpcClientError } from "effect/unstable/rpc/RpcClientError";
10
+ import type { Contract, Tag } from "./contract.ts";
11
+ import type { CommandShape, QueryShape } from "./operation.ts";
12
+ export type Failure<R extends Rpc.Any> = R extends Rpc.Rpc<infer _Tag, infer _Payload, infer _Success, infer Error, infer Middleware, infer _Requires> ? Error["Type"] | Middleware["error"]["Type"] | RpcClientError : never;
13
+ export type RunFailure<R extends Rpc.Any> = R extends Rpc.Rpc<infer _Tag, infer _Payload, infer _Success, infer _Error, infer Middleware, infer _Requires> ? Failure<R> | Middleware["~ClientError"] : never;
14
+ export interface QueryOptions {
15
+ readonly headers?: Headers.Input;
16
+ readonly serializationKey?: string;
17
+ readonly timeToLive?: Duration.Input;
18
+ }
19
+ export interface BoundQuery<R extends Rpc.Any, Self> {
20
+ readonly query: (payload: Rpc.Payload<R>, options?: QueryOptions) => Atom.Atom<AsyncResult.AsyncResult<Rpc.Success<R>, Failure<R>>>;
21
+ readonly run: (payload: Rpc.Payload<R>) => Effect.Effect<Rpc.Success<R>, RunFailure<R>, Self>;
22
+ }
23
+ export interface BoundCommand<R extends Rpc.Any, Self> {
24
+ readonly run: (payload: Rpc.Payload<R>) => Effect.Effect<Rpc.Success<R>, RunFailure<R>, Self | Reactivity.Reactivity>;
25
+ }
26
+ export type Bound<Name extends string, Queries extends readonly QueryShape[], Commands extends readonly CommandShape[], Rpcs extends Rpc.Any, Self> = {
27
+ readonly [Query in Queries[number] as Query["name"]]: BoundQuery<Rpc.ExtractTag<Rpcs, Tag<Name, Query["name"]>>, Self>;
28
+ } & {
29
+ readonly [Command in Commands[number] as Command["name"]]: BoundCommand<Rpc.ExtractTag<Rpcs, Tag<Name, Command["name"]>>, Self>;
30
+ };
31
+ export declare function bind<Name extends string, Queries extends readonly QueryShape[], Commands extends readonly CommandShape[], Rpcs extends Rpc.Any, Self, Id extends string>(contract: Contract<Name, Queries, Commands, Rpcs>, service: AtomRpc.AtomRpcClient<Self, Id, Rpcs>): Bound<Name, Queries, Commands, Rpcs, Self>;
32
+ //# sourceMappingURL=bind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bind.d.ts","sourceRoot":"","sources":["../src/bind.ts"],"names":[],"mappings":"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"}
package/dist/bind.js ADDED
@@ -0,0 +1,24 @@
1
+ import { Effect } from "effect";
2
+ import * as Reactivity from "effect/unstable/reactivity/Reactivity";
3
+ import { invalidationKeys, readKeys } from "./keys.js";
4
+ const invalidateAfter = Effect.fn("EffectContract.invalidate")(function* (keys) {
5
+ yield* Reactivity.invalidate(invalidationKeys(keys));
6
+ });
7
+ const boundQuery = (service, tag, query) => {
8
+ const reads = (payload) => readKeys(query.reads(payload));
9
+ return {
10
+ query: (payload, options = {}) => service.query(tag, payload, { ...options, reactivityKeys: reads(payload) }),
11
+ run: (payload) => service.use((client) => client(tag, payload)),
12
+ };
13
+ };
14
+ const boundCommand = (service, tag, command) => ({
15
+ run: (payload) => service.use((client) => client(tag, payload)).pipe(Effect.tap((result) => invalidateAfter(command.invalidates(payload, result)))),
16
+ });
17
+ export function bind(contract, service) {
18
+ const { name, queries, commands } = contract.declaration;
19
+ return Object.fromEntries([
20
+ ...queries.map((query) => [query.name, boundQuery(service, `${name}.${query.name}`, query)]),
21
+ ...commands.map((command) => [command.name, boundCommand(service, `${name}.${command.name}`, command)]),
22
+ ]);
23
+ }
24
+ //# sourceMappingURL=bind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bind.js","sourceRoot":"","sources":["../src/bind.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAKhC,OAAO,KAAK,UAAU,MAAM,uCAAuC,CAAC;AAIpE,OAAO,EAAE,gBAAgB,EAAY,QAAQ,EAAE,MAAM,WAAW,CAAC;AA0CjE,MAAM,eAAe,GAAG,MAAM,CAAC,EAAE,CAAC,2BAA2B,CAAC,CAAC,QAAQ,CAAC,EAAE,IAAoB;IAC7F,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC;AACtD,CAAC,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG,CAAC,OAAqB,EAAE,GAAW,EAAE,KAAiB,EAAE,EAAE;IAC5E,MAAM,KAAK,GAAG,CAAC,OAAgB,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IACnE,OAAO;QACN,KAAK,EAAE,CAAC,OAAgB,EAAE,UAAwB,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACpI,GAAG,EAAE,CAAC,OAAgB,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;KACxE,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,OAAqB,EAAE,GAAW,EAAE,OAAqB,EAAE,EAAE,CAAC,CAAC;IACpF,GAAG,EAAE,CAAC,OAAgB,EAAE,EAAE,CACzB,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;CAClI,CAAC,CAAC;AAUH,MAAM,UAAU,IAAI,CACnB,QAAoG,EACpG,OAAqB;IAErB,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC;IACzD,OAAO,MAAM,CAAC,WAAW,CAAC;QACzB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,KAAK,CAAC,CAAU,CAAC;QACrG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAU,CAAC;KAChH,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { Rpc, RpcGroup, type RpcMiddleware } from "effect/unstable/rpc";
2
+ import type { CommandShape, OperationShape, QueryShape } from "./operation.ts";
3
+ export type Tag<Contract extends string, Name extends string> = `${Contract}.${Name}`;
4
+ export type OperationRpc<Contract extends string, Operation> = Operation extends OperationShape ? Rpc.Rpc<Tag<Contract, Operation["name"]>, Operation["payload"], Operation["success"], Operation["error"]> : never;
5
+ export interface Declared<Name extends string, Queries extends readonly QueryShape[], Commands extends readonly CommandShape[]> {
6
+ readonly commands: Commands;
7
+ readonly name: Name;
8
+ readonly queries: Queries;
9
+ }
10
+ export interface Contract<Name extends string, Queries extends readonly QueryShape[], Commands extends readonly CommandShape[], Rpcs extends Rpc.Any = OperationRpc<Name, Queries[number] | Commands[number]>> extends RpcGroup.RpcGroup<Rpcs> {
11
+ readonly declaration: Declared<Name, Queries, Commands>;
12
+ middleware: <M extends RpcMiddleware.AnyService>(middleware: M) => Contract<Name, Queries, Commands, Rpc.AddMiddleware<Rpcs, M>>;
13
+ }
14
+ type Duplicated<Operations extends readonly OperationShape[], Seen extends string = never> = Operations extends readonly [
15
+ infer Head extends OperationShape,
16
+ ...infer Tail extends readonly OperationShape[]
17
+ ] ? (Head["name"] extends Seen ? Head["name"] : never) | Duplicated<Tail, Seen | Head["name"]> : never;
18
+ type Unique<Operations extends readonly OperationShape[]> = [Duplicated<Operations>] extends [never] ? unknown : `Operation names must be unique; duplicated: ${Duplicated<Operations>}`;
19
+ export declare function contract<const Name extends string, const Queries extends readonly QueryShape[] = readonly [], const Commands extends readonly CommandShape[] = readonly []>(name: Name, operations: {
20
+ readonly queries?: Queries & Unique<Queries>;
21
+ readonly commands?: Commands & Unique<[...Queries, ...Commands]>;
22
+ }): Contract<Name, Queries, Commands>;
23
+ export {};
24
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { Rpc, RpcGroup } from "effect/unstable/rpc";
2
+ const assertUnique = (operations) => {
3
+ const duplicated = operations.map(({ name }) => name).filter((name, index, names) => names.indexOf(name) !== index);
4
+ if (duplicated.length > 0) {
5
+ throw new Error(`Operation names must be unique; duplicated: ${[...new Set(duplicated)].join(", ")}`);
6
+ }
7
+ };
8
+ const withDeclaration = (group, declaration) => {
9
+ const nativeMiddleware = group.middleware.bind(group);
10
+ return Object.assign(group, {
11
+ declaration,
12
+ middleware: (middleware) => withDeclaration(nativeMiddleware(middleware), declaration),
13
+ });
14
+ };
15
+ export function contract(name, operations) {
16
+ const { queries = [], commands = [] } = operations;
17
+ assertUnique([...queries, ...commands]);
18
+ const rpcs = [...queries, ...commands].map((operation) => Rpc.make(`${name}.${operation.name}`, { error: operation.error, payload: operation.payload, success: operation.success }));
19
+ return withDeclaration(RpcGroup.make(...rpcs), { commands, name, queries });
20
+ }
21
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAsB,MAAM,qBAAqB,CAAC;AAoCxE,MAAM,YAAY,GAAG,CAAC,UAAqC,EAAE,EAAE;IAC9D,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,CAAC;AAYF,MAAM,eAAe,GAAG,CAAC,KAAY,EAAE,WAAwB,EAAiB,EAAE;IACjF,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,CAAC;AAUF,MAAM,UAAU,QAAQ,CACvB,IAAY,EACZ,UAAqG;IAErG,MAAM,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,EAAE,EAAE,GAAG,UAAU,CAAC;IACnD,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,CAAC,GAAG,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CACxD,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,CAAC,CACzH,CAAC;IACF,OAAO,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AAC7E,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { type Bound, type BoundCommand, type BoundQuery, bind, type Failure, type QueryOptions, type RunFailure } from "./bind.ts";
2
+ export { type Contract, contract, type Declared, type OperationRpc, type Tag } from "./contract.ts";
3
+ export { type Collection, collection, type Identity, type ItemKey, invalidationKeys, type Key, type ListKey, readKeys } from "./keys.ts";
4
+ export { type Command, type CommandShape, command, type OperationShape, type PayloadSchema, type Query, type QueryShape, query, } from "./operation.ts";
5
+ export { type FieldRejection, fieldRejection, type MatchingTags, type Reject, type RejectedBy, type RejectionClass, type RejectionSpecs, type Rejections, type RejectionUnion, type RejectionValue, type TaggedRejection, } from "./rejection.ts";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,IAAI,EAAE,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AACnI,OAAO,EAAE,KAAK,QAAQ,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,KAAK,GAAG,EAAE,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,KAAK,UAAU,EAAE,UAAU,EAAE,KAAK,QAAQ,EAAE,KAAK,OAAO,EAAE,gBAAgB,EAAE,KAAK,GAAG,EAAE,KAAK,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACzI,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,YAAY,EACjB,OAAO,EACP,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,KAAK,EACV,KAAK,UAAU,EACf,KAAK,GACL,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,KAAK,cAAc,EACnB,cAAc,EACd,KAAK,YAAY,EACjB,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,eAAe,GACpB,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { bind } from "./bind.js";
2
+ export { contract } from "./contract.js";
3
+ export { collection, invalidationKeys, readKeys } from "./keys.js";
4
+ export { command, query, } from "./operation.js";
5
+ export { fieldRejection, } from "./rejection.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkD,IAAI,EAAoD,MAAM,WAAW,CAAC;AACnI,OAAO,EAAiB,QAAQ,EAA8C,MAAM,eAAe,CAAC;AACpG,OAAO,EAAmB,UAAU,EAA+B,gBAAgB,EAA0B,QAAQ,EAAE,MAAM,WAAW,CAAC;AACzI,OAAO,EAGN,OAAO,EAKP,KAAK,GACL,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEN,cAAc,GAUd,MAAM,gBAAgB,CAAC"}
package/dist/keys.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type { Schema } from "effect";
2
+ export type Identity = string | number;
3
+ export interface ListKey<Name extends string = string> {
4
+ readonly _tag: "List";
5
+ readonly collection: Name;
6
+ }
7
+ export interface ItemKey<Name extends string = string> {
8
+ readonly _tag: "Item";
9
+ readonly collection: Name;
10
+ readonly id: Identity;
11
+ }
12
+ export type Key = ListKey | ItemKey;
13
+ export interface Collection<Name extends string, Id extends Identity> {
14
+ readonly item: (id: Id) => ItemKey<Name>;
15
+ readonly list: ListKey<Name>;
16
+ readonly name: Name;
17
+ }
18
+ export declare const collection: <const Name extends string, Id extends Schema.Top & {
19
+ readonly Type: Identity;
20
+ }>(name: Name, _id: Id) => Collection<Name, Id["Type"]>;
21
+ export declare const readKeys: (keys: readonly Key[]) => readonly string[];
22
+ export declare const invalidationKeys: (keys: readonly Key[]) => readonly string[];
23
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErC,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,MAAM,WAAW,OAAO,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,OAAO,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;CACtB;AAED,MAAM,MAAM,GAAG,GAAG,OAAO,GAAG,OAAO,CAAC;AAEpC,MAAM,WAAW,UAAU,CAAC,IAAI,SAAS,MAAM,EAAE,EAAE,SAAS,QAAQ;IACnE,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;CACpB;AAED,eAAO,MAAM,UAAU,GAAI,KAAK,CAAC,IAAI,SAAS,MAAM,EAAE,EAAE,SAAS,MAAM,CAAC,GAAG,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,EACxG,MAAM,IAAI,EACV,KAAK,EAAE,KACL,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,CAI5B,CAAC;AAIH,eAAO,MAAM,QAAQ,GAAI,MAAM,SAAS,GAAG,EAAE,KAAG,SAAS,MAAM,EAAiC,CAAC;AAEjG,eAAO,MAAM,gBAAgB,GAAI,MAAM,SAAS,GAAG,EAAE,KAAG,SAAS,MAAM,EAEtE,CAAC"}
package/dist/keys.js ADDED
@@ -0,0 +1,11 @@
1
+ export const collection = (name, _id) => ({
2
+ item: (id) => ({ _tag: "Item", collection: name, id }),
3
+ list: { _tag: "List", collection: name },
4
+ name,
5
+ });
6
+ const own = (key) => (key._tag === "List" ? key.collection : `${key.collection}:${key.id}`);
7
+ export const readKeys = (keys) => [...new Set(keys.map(own))];
8
+ export const invalidationKeys = (keys) => [
9
+ ...new Set(keys.flatMap((key) => (key._tag === "List" ? [key.collection] : [own(key), key.collection]))),
10
+ ];
11
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAuBA,MAAM,CAAC,MAAM,UAAU,GAAG,CACzB,IAAU,EACV,GAAO,EACwB,EAAE,CAAC,CAAC;IACnC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IACtD,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE;IACxC,IAAI;CACJ,CAAC,CAAC;AAEH,MAAM,GAAG,GAAG,CAAC,GAAQ,EAAU,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;AAEzG,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAoB,EAAqB,EAAE,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAEjG,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,IAAoB,EAAqB,EAAE,CAAC;IAC5E,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CACxG,CAAC"}
@@ -0,0 +1,54 @@
1
+ import { Schema } from "effect";
2
+ import type { Bivariant } from "@shivaedev/types";
3
+ import type { Key } from "./keys.ts";
4
+ import { type MatchingTags, type Reject, type RejectionSpecs, type Rejections, type RejectionUnion } from "./rejection.ts";
5
+ export type PayloadSchema<Payload extends Schema.Top | Schema.Struct.Fields> = Payload extends Schema.Struct.Fields ? Schema.Struct<Payload> : Payload;
6
+ type NoRejections = Record<never, never>;
7
+ export interface OperationShape {
8
+ readonly error: Schema.Top;
9
+ readonly kind: "query" | "command";
10
+ readonly name: string;
11
+ readonly payload: Schema.Top;
12
+ readonly Rejection: object;
13
+ readonly reject: object;
14
+ readonly rejections: RejectionSpecs;
15
+ readonly success: Schema.Top;
16
+ }
17
+ export interface QueryShape extends OperationShape {
18
+ readonly kind: "query";
19
+ reads: Bivariant<(payload: unknown) => readonly Key[]>;
20
+ }
21
+ export interface CommandShape extends OperationShape {
22
+ invalidates: Bivariant<(payload: unknown, result: unknown) => readonly Key[]>;
23
+ readonly kind: "command";
24
+ }
25
+ interface Operation<Name extends string, Payload extends Schema.Top, Success extends Schema.Top, Specs extends RejectionSpecs> extends OperationShape {
26
+ readonly error: RejectionUnion<Specs>;
27
+ readonly name: Name;
28
+ readonly payload: Payload;
29
+ readonly Rejection: Rejections<Specs>;
30
+ readonly reject: Reject<Specs>;
31
+ readonly rejections: Specs;
32
+ readonly success: Success;
33
+ }
34
+ export interface Query<Name extends string, Payload extends Schema.Top, Success extends Schema.Top, Specs extends RejectionSpecs> extends Operation<Name, Payload, Success, Specs> {
35
+ readonly kind: "query";
36
+ reads: (payload: Payload["Type"]) => readonly Key[];
37
+ }
38
+ export interface Command<Name extends string, Payload extends Schema.Top, Success extends Schema.Top, Specs extends RejectionSpecs> extends Operation<Name, Payload, Success, Specs> {
39
+ invalidates: (payload: Payload["Type"], result: Success["Type"]) => readonly Key[];
40
+ readonly kind: "command";
41
+ }
42
+ interface Declaration<Payload extends Schema.Top | Schema.Struct.Fields, Success extends Schema.Top, Specs extends RejectionSpecs> {
43
+ readonly payload?: Payload;
44
+ readonly rejections?: Specs & MatchingTags<Specs>;
45
+ readonly success?: Success;
46
+ }
47
+ export declare function query<const Name extends string, Payload extends Schema.Top | Schema.Struct.Fields = Schema.Void, Success extends Schema.Top = Schema.Void, const Specs extends RejectionSpecs = NoRejections>(name: Name, declaration: Declaration<Payload, Success, Specs> & {
48
+ readonly reads: (payload: PayloadSchema<Payload>["Type"]) => readonly Key[];
49
+ }): NoInfer<Query<Name, PayloadSchema<Payload>, Success, Specs>>;
50
+ export declare function command<const Name extends string, Payload extends Schema.Top | Schema.Struct.Fields = Schema.Void, Success extends Schema.Top = Schema.Void, const Specs extends RejectionSpecs = NoRejections>(name: Name, declaration: Declaration<Payload, Success, Specs> & {
51
+ readonly invalidates: (payload: PayloadSchema<Payload>["Type"], result: Success["Type"]) => readonly Key[];
52
+ }): NoInfer<Command<Name, PayloadSchema<Payload>, Success, Specs>>;
53
+ export {};
54
+ //# sourceMappingURL=operation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operation.d.ts","sourceRoot":"","sources":["../src/operation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { Schema } from "effect";
2
+ import { rejectionSet } from "./rejection.js";
3
+ const payloadSchema = (payload) => {
4
+ if (payload === undefined) {
5
+ return Schema.Void;
6
+ }
7
+ return Schema.isSchema(payload) ? payload : Schema.Struct(payload);
8
+ };
9
+ const operation = (kind, name, declaration) => {
10
+ const { payload, success, rejections = {} } = declaration;
11
+ return {
12
+ kind,
13
+ name,
14
+ payload: payloadSchema(payload),
15
+ rejections,
16
+ success: success ?? Schema.Void,
17
+ ...rejectionSet(rejections),
18
+ };
19
+ };
20
+ export function query(name, declaration) {
21
+ return { ...operation("query", name, declaration), reads: declaration.reads };
22
+ }
23
+ export function command(name, declaration) {
24
+ return { ...operation("command", name, declaration), invalidates: declaration.invalidates };
25
+ }
26
+ //# sourceMappingURL=operation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operation.js","sourceRoot":"","sources":["../src/operation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,OAAO,EAA6F,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAgEzI,MAAM,aAAa,GAAG,CAAC,OAAyB,EAAc,EAAE;IAC/D,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,MAAM,CAAC,IAAI,CAAC;IACpB,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACpE,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,CAAsC,IAAU,EAAE,IAAY,EAAE,WAAkB,EAAE,EAAE;IACvG,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,GAAG,EAAE,EAAE,GAAG,WAAW,CAAC;IAC1D,OAAO;QACN,IAAI;QACJ,IAAI;QACJ,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC;QAC/B,UAAU;QACV,OAAO,EAAE,OAAO,IAAI,MAAM,CAAC,IAAI;QAC/B,GAAG,YAAY,CAAC,UAAU,CAAC;KAC3B,CAAC;AACH,CAAC,CAAC;AAaF,MAAM,UAAU,KAAK,CAAC,IAAY,EAAE,WAA6E;IAChH,OAAO,EAAE,GAAG,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,KAAK,EAAE,CAAC;AAC/E,CAAC;AAaD,MAAM,UAAU,OAAO,CACtB,IAAY,EACZ,WAAoG;IAEpG,OAAO,EAAE,GAAG,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,WAAW,CAAC,EAAE,WAAW,EAAE,WAAW,CAAC,WAAW,EAAE,CAAC;AAC7F,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { type Cause, Effect, Schema } from "effect";
2
+ export type RejectionValue<Tag extends string, Fields extends Schema.Struct.Fields> = Schema.TaggedStruct<Tag, Fields>["Type"] & Cause.YieldableError;
3
+ export type RejectionClass<Tag extends string, Fields extends Schema.Struct.Fields> = Schema.Class<RejectionValue<Tag, Fields>, Schema.TaggedStruct<Tag, Fields>, Cause.YieldableError>;
4
+ export type TaggedRejection = Schema.Top & (new (...args: never) => {
5
+ readonly _tag: string;
6
+ });
7
+ export interface RejectionSpecs {
8
+ readonly [tag: string]: Schema.Struct.Fields | TaggedRejection;
9
+ }
10
+ export type Rejections<Specs extends RejectionSpecs> = {
11
+ readonly [Tag in keyof Specs & string]: Specs[Tag] extends TaggedRejection ? Specs[Tag] : Specs[Tag] extends Schema.Struct.Fields ? RejectionClass<Tag, Specs[Tag]> : never;
12
+ };
13
+ export type RejectedBy<Specs extends RejectionSpecs> = {
14
+ [Tag in keyof Specs & string]: Rejections<Specs>[Tag]["Type"];
15
+ }[keyof Specs & string];
16
+ export type Reject<Specs extends RejectionSpecs> = {
17
+ readonly [Tag in keyof Specs & string]: Rejections<Specs>[Tag] extends new (...args: infer Arguments) => infer Value ? (...args: Arguments) => Effect.Effect<never, Value> : never;
18
+ };
19
+ export type RejectionUnion<Specs extends RejectionSpecs> = [keyof Specs & string] extends [never] ? Schema.Never : Schema.Union<readonly Rejections<Specs>[keyof Specs & string][]>;
20
+ export type MatchingTags<Specs extends RejectionSpecs> = {
21
+ readonly [Tag in keyof Specs]: Specs[Tag] extends TaggedRejection ? Specs[Tag]["Type"] extends {
22
+ readonly _tag: Tag;
23
+ } ? Specs[Tag] : `The rejection class under "${Tag & string}" must have that _tag` : Specs[Tag];
24
+ };
25
+ export interface RejectionSet<Specs extends RejectionSpecs> {
26
+ readonly error: RejectionUnion<Specs>;
27
+ readonly Rejection: Rejections<Specs>;
28
+ readonly reject: Reject<Specs>;
29
+ }
30
+ export declare function rejectionSet<const Specs extends RejectionSpecs>(specs: Specs): RejectionSet<Specs>;
31
+ export type FieldRejection<Field extends string> = Readonly<{
32
+ field: Schema.Literals<readonly Field[]>;
33
+ message: Schema.String;
34
+ }>;
35
+ export declare function fieldRejection<const Fields extends Schema.Struct.Fields>(struct: {
36
+ readonly fields: Fields;
37
+ }): FieldRejection<keyof Fields & string>;
38
+ export declare function fieldRejection<const Fields extends Schema.Struct.Fields, const Field extends keyof Fields & string>(struct: {
39
+ readonly fields: Fields;
40
+ }, fields: readonly Field[]): FieldRejection<Field>;
41
+ //# sourceMappingURL=rejection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rejection.d.ts","sourceRoot":"","sources":["../src/rejection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { Effect, Schema } from "effect";
2
+ export function rejectionSet(specs) {
3
+ const classes = Object.entries(specs).map(([tag, spec]) => [
4
+ tag,
5
+ Schema.isSchema(spec) ? spec : Schema.TaggedError()(tag, spec),
6
+ ]);
7
+ return {
8
+ error: classes.length === 0 ? Schema.Never : Schema.Union(classes.map(([, schema]) => schema)),
9
+ Rejection: Object.fromEntries(classes),
10
+ reject: Object.fromEntries(classes.map(([tag, Rejection]) => [tag, (...args) => Effect.fail(Reflect.construct(Rejection, args))])),
11
+ };
12
+ }
13
+ export function fieldRejection(struct, fields) {
14
+ return { field: Schema.Literals(fields ?? Object.keys(struct.fields)), message: Schema.String };
15
+ }
16
+ //# sourceMappingURL=rejection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rejection.js","sourceRoot":"","sources":["../src/rejection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AA6DpD,MAAM,UAAU,YAAY,CAAC,KAAqB;IACjD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAsC,EAAE,CAAC;QAC9F,GAAG;QACH,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,EAAwB,CAAC,GAAG,EAAE,IAAI,CAAC;KACpF,CAAC,CAAC;IACH,OAAO;QACN,KAAK,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;QAC9F,SAAS,EAAE,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC;QACtC,MAAM,EAAE,MAAM,CAAC,WAAW,CACzB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,IAAwB,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAC1H;KACD,CAAC;AACH,CAAC;AAYD,MAAM,UAAU,cAAc,CAAC,MAAiD,EAAE,MAA0B;IAC3G,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;AACjG,CAAC"}
package/package.json CHANGED
@@ -1,14 +1,60 @@
1
1
  {
2
2
  "name": "@shivaedev/effect-contract",
3
- "version": "0.0.0",
4
- "description": "Name reservation. The first release is published from https://github.com/ShivaeDev/platform.",
5
- "license": "MIT",
3
+ "version": "0.2.0",
4
+ "description": "Typed query and command contracts over native Effect RPC and reactivity",
5
+ "homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/effect-contract#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/ShivaeDev/platform/issues"
8
+ },
6
9
  "repository": {
7
10
  "type": "git",
8
11
  "url": "git+https://github.com/ShivaeDev/platform.git",
9
12
  "directory": "packages/effect-contract"
10
13
  },
14
+ "license": "MIT",
15
+ "sideEffects": false,
16
+ "type": "module",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "source": "./src/index.ts",
21
+ "import": "./dist/index.js",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "./package.json": "./package.json"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "src",
29
+ "CHANGELOG.md",
30
+ "README.md"
31
+ ],
32
+ "dependencies": {
33
+ "@shivaedev/types": "0.1.0"
34
+ },
35
+ "devDependencies": {
36
+ "@effect/vitest": "4.0.0-rc.112",
37
+ "@shivaedev/quality": "0.7.0",
38
+ "@types/node": "24.10.1",
39
+ "@typescript/native": "npm:typescript@7.0.2",
40
+ "effect": "4.0.0-rc.112",
41
+ "typescript": "npm:@typescript/typescript6@6.0.2",
42
+ "vitest": "4.1.9"
43
+ },
44
+ "peerDependencies": {
45
+ "effect": "4.0.0-rc.112"
46
+ },
47
+ "engines": {
48
+ "node": ">=24"
49
+ },
11
50
  "publishConfig": {
12
- "access": "public"
51
+ "access": "public",
52
+ "provenance": true
53
+ },
54
+ "scripts": {
55
+ "build": "node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist', { force: true, recursive: true }))\" && tsc6 --project tsconfig.build.json",
56
+ "ready": "pnpm typecheck && pnpm test && pnpm build",
57
+ "test": "vitest run",
58
+ "typecheck": "tsc --noEmit"
13
59
  }
14
- }
60
+ }
package/src/bind.ts ADDED
@@ -0,0 +1,87 @@
1
+ import type { Duration, Schema } from "effect";
2
+ import { Effect } from "effect";
3
+ import type { Headers } from "effect/unstable/http";
4
+ import type * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
5
+ import type * as Atom from "effect/unstable/reactivity/Atom";
6
+ import type * as AtomRpc from "effect/unstable/reactivity/AtomRpc";
7
+ import * as Reactivity from "effect/unstable/reactivity/Reactivity";
8
+ import type { Rpc } from "effect/unstable/rpc";
9
+ import type { RpcClientError } from "effect/unstable/rpc/RpcClientError";
10
+ import type { Contract, Declared, Tag } from "./contract.ts";
11
+ import { invalidationKeys, type Key, readKeys } from "./keys.ts";
12
+ import type { CommandShape, QueryShape } from "./operation.ts";
13
+
14
+ export type Failure<R extends Rpc.Any> =
15
+ R extends Rpc.Rpc<infer _Tag, infer _Payload, infer _Success, infer Error, infer Middleware, infer _Requires>
16
+ ? Error["Type"] | Middleware["error"]["Type"] | RpcClientError
17
+ : never;
18
+
19
+ export type RunFailure<R extends Rpc.Any> =
20
+ R extends Rpc.Rpc<infer _Tag, infer _Payload, infer _Success, infer _Error, infer Middleware, infer _Requires>
21
+ ? Failure<R> | Middleware["~ClientError"]
22
+ : never;
23
+
24
+ export interface QueryOptions {
25
+ readonly headers?: Headers.Input;
26
+ readonly serializationKey?: string;
27
+ readonly timeToLive?: Duration.Input;
28
+ }
29
+
30
+ export interface BoundQuery<R extends Rpc.Any, Self> {
31
+ readonly query: (payload: Rpc.Payload<R>, options?: QueryOptions) => Atom.Atom<AsyncResult.AsyncResult<Rpc.Success<R>, Failure<R>>>;
32
+ readonly run: (payload: Rpc.Payload<R>) => Effect.Effect<Rpc.Success<R>, RunFailure<R>, Self>;
33
+ }
34
+
35
+ export interface BoundCommand<R extends Rpc.Any, Self> {
36
+ readonly run: (payload: Rpc.Payload<R>) => Effect.Effect<Rpc.Success<R>, RunFailure<R>, Self | Reactivity.Reactivity>;
37
+ }
38
+
39
+ export type Bound<
40
+ Name extends string,
41
+ Queries extends readonly QueryShape[],
42
+ Commands extends readonly CommandShape[],
43
+ Rpcs extends Rpc.Any,
44
+ Self,
45
+ > = {
46
+ readonly [Query in Queries[number] as Query["name"]]: BoundQuery<Rpc.ExtractTag<Rpcs, Tag<Name, Query["name"]>>, Self>;
47
+ } & {
48
+ readonly [Command in Commands[number] as Command["name"]]: BoundCommand<Rpc.ExtractTag<Rpcs, Tag<Name, Command["name"]>>, Self>;
49
+ };
50
+
51
+ type ErasedClient = AtomRpc.AtomRpcClient<unknown, string, Rpc.Rpc<string, Schema.Top, Schema.Top, Schema.Top>>;
52
+
53
+ const invalidateAfter = Effect.fn("EffectContract.invalidate")(function* (keys: readonly Key[]) {
54
+ yield* Reactivity.invalidate(invalidationKeys(keys));
55
+ });
56
+
57
+ const boundQuery = (service: ErasedClient, tag: string, query: QueryShape) => {
58
+ const reads = (payload: unknown) => readKeys(query.reads(payload));
59
+ return {
60
+ query: (payload: unknown, options: QueryOptions = {}) => service.query(tag, payload, { ...options, reactivityKeys: reads(payload) }),
61
+ run: (payload: unknown) => service.use((client) => client(tag, payload)),
62
+ };
63
+ };
64
+
65
+ const boundCommand = (service: ErasedClient, tag: string, command: CommandShape) => ({
66
+ run: (payload: unknown) =>
67
+ service.use((client) => client(tag, payload)).pipe(Effect.tap((result) => invalidateAfter(command.invalidates(payload, result)))),
68
+ });
69
+
70
+ export function bind<
71
+ Name extends string,
72
+ Queries extends readonly QueryShape[],
73
+ Commands extends readonly CommandShape[],
74
+ Rpcs extends Rpc.Any,
75
+ Self,
76
+ Id extends string,
77
+ >(contract: Contract<Name, Queries, Commands, Rpcs>, service: AtomRpc.AtomRpcClient<Self, Id, Rpcs>): Bound<Name, Queries, Commands, Rpcs, Self>;
78
+ export function bind(
79
+ contract: { readonly declaration: Declared<string, readonly QueryShape[], readonly CommandShape[]> },
80
+ service: ErasedClient,
81
+ ): unknown {
82
+ const { name, queries, commands } = contract.declaration;
83
+ return Object.fromEntries([
84
+ ...queries.map((query) => [query.name, boundQuery(service, `${name}.${query.name}`, query)] as const),
85
+ ...commands.map((command) => [command.name, boundCommand(service, `${name}.${command.name}`, command)] as const),
86
+ ]);
87
+ }
@@ -0,0 +1,80 @@
1
+ import { Rpc, RpcGroup, type RpcMiddleware } from "effect/unstable/rpc";
2
+ import type { CommandShape, OperationShape, QueryShape } from "./operation.ts";
3
+
4
+ export type Tag<Contract extends string, Name extends string> = `${Contract}.${Name}`;
5
+
6
+ export type OperationRpc<Contract extends string, Operation> = Operation extends OperationShape
7
+ ? Rpc.Rpc<Tag<Contract, Operation["name"]>, Operation["payload"], Operation["success"], Operation["error"]>
8
+ : never;
9
+
10
+ export interface Declared<Name extends string, Queries extends readonly QueryShape[], Commands extends readonly CommandShape[]> {
11
+ readonly commands: Commands;
12
+ readonly name: Name;
13
+ readonly queries: Queries;
14
+ }
15
+
16
+ export interface Contract<
17
+ Name extends string,
18
+ Queries extends readonly QueryShape[],
19
+ Commands extends readonly CommandShape[],
20
+ Rpcs extends Rpc.Any = OperationRpc<Name, Queries[number] | Commands[number]>,
21
+ > extends RpcGroup.RpcGroup<Rpcs> {
22
+ readonly declaration: Declared<Name, Queries, Commands>;
23
+ middleware: <M extends RpcMiddleware.AnyService>(middleware: M) => Contract<Name, Queries, Commands, Rpc.AddMiddleware<Rpcs, M>>;
24
+ }
25
+
26
+ type Duplicated<Operations extends readonly OperationShape[], Seen extends string = never> = Operations extends readonly [
27
+ infer Head extends OperationShape,
28
+ ...infer Tail extends readonly OperationShape[],
29
+ ]
30
+ ? (Head["name"] extends Seen ? Head["name"] : never) | Duplicated<Tail, Seen | Head["name"]>
31
+ : never;
32
+
33
+ type Unique<Operations extends readonly OperationShape[]> = [Duplicated<Operations>] extends [never]
34
+ ? unknown
35
+ : `Operation names must be unique; duplicated: ${Duplicated<Operations>}`;
36
+
37
+ const assertUnique = (operations: readonly OperationShape[]) => {
38
+ const duplicated = operations.map(({ name }) => name).filter((name, index, names) => names.indexOf(name) !== index);
39
+ if (duplicated.length > 0) {
40
+ throw new Error(`Operation names must be unique; duplicated: ${[...new Set(duplicated)].join(", ")}`);
41
+ }
42
+ };
43
+
44
+ type AnyDeclared = Declared<string, readonly QueryShape[], readonly CommandShape[]>;
45
+
46
+ interface Group {
47
+ middleware: (middleware: RpcMiddleware.AnyService) => Group;
48
+ }
49
+
50
+ interface DeclaredGroup extends Group {
51
+ readonly declaration: AnyDeclared;
52
+ }
53
+
54
+ const withDeclaration = (group: Group, declaration: AnyDeclared): DeclaredGroup => {
55
+ const nativeMiddleware = group.middleware.bind(group);
56
+ return Object.assign(group, {
57
+ declaration,
58
+ middleware: (middleware: RpcMiddleware.AnyService) => withDeclaration(nativeMiddleware(middleware), declaration),
59
+ });
60
+ };
61
+
62
+ export function contract<
63
+ const Name extends string,
64
+ const Queries extends readonly QueryShape[] = readonly [],
65
+ const Commands extends readonly CommandShape[] = readonly [],
66
+ >(
67
+ name: Name,
68
+ operations: { readonly queries?: Queries & Unique<Queries>; readonly commands?: Commands & Unique<[...Queries, ...Commands]> },
69
+ ): Contract<Name, Queries, Commands>;
70
+ export function contract(
71
+ name: string,
72
+ operations: { readonly queries?: readonly QueryShape[]; readonly commands?: readonly CommandShape[] },
73
+ ): DeclaredGroup {
74
+ const { queries = [], commands = [] } = operations;
75
+ assertUnique([...queries, ...commands]);
76
+ const rpcs = [...queries, ...commands].map((operation) =>
77
+ Rpc.make(`${name}.${operation.name}`, { error: operation.error, payload: operation.payload, success: operation.success }),
78
+ );
79
+ return withDeclaration(RpcGroup.make(...rpcs), { commands, name, queries });
80
+ }
package/src/index.ts ADDED
@@ -0,0 +1,26 @@
1
+ export { type Bound, type BoundCommand, type BoundQuery, bind, type Failure, type QueryOptions, type RunFailure } from "./bind.ts";
2
+ export { type Contract, contract, type Declared, type OperationRpc, type Tag } from "./contract.ts";
3
+ export { type Collection, collection, type Identity, type ItemKey, invalidationKeys, type Key, type ListKey, readKeys } from "./keys.ts";
4
+ export {
5
+ type Command,
6
+ type CommandShape,
7
+ command,
8
+ type OperationShape,
9
+ type PayloadSchema,
10
+ type Query,
11
+ type QueryShape,
12
+ query,
13
+ } from "./operation.ts";
14
+ export {
15
+ type FieldRejection,
16
+ fieldRejection,
17
+ type MatchingTags,
18
+ type Reject,
19
+ type RejectedBy,
20
+ type RejectionClass,
21
+ type RejectionSpecs,
22
+ type Rejections,
23
+ type RejectionUnion,
24
+ type RejectionValue,
25
+ type TaggedRejection,
26
+ } from "./rejection.ts";
package/src/keys.ts ADDED
@@ -0,0 +1,39 @@
1
+ import type { Schema } from "effect";
2
+
3
+ export type Identity = string | number;
4
+
5
+ export interface ListKey<Name extends string = string> {
6
+ readonly _tag: "List";
7
+ readonly collection: Name;
8
+ }
9
+
10
+ export interface ItemKey<Name extends string = string> {
11
+ readonly _tag: "Item";
12
+ readonly collection: Name;
13
+ readonly id: Identity;
14
+ }
15
+
16
+ export type Key = ListKey | ItemKey;
17
+
18
+ export interface Collection<Name extends string, Id extends Identity> {
19
+ readonly item: (id: Id) => ItemKey<Name>;
20
+ readonly list: ListKey<Name>;
21
+ readonly name: Name;
22
+ }
23
+
24
+ export const collection = <const Name extends string, Id extends Schema.Top & { readonly Type: Identity }>(
25
+ name: Name,
26
+ _id: Id,
27
+ ): Collection<Name, Id["Type"]> => ({
28
+ item: (id) => ({ _tag: "Item", collection: name, id }),
29
+ list: { _tag: "List", collection: name },
30
+ name,
31
+ });
32
+
33
+ const own = (key: Key): string => (key._tag === "List" ? key.collection : `${key.collection}:${key.id}`);
34
+
35
+ export const readKeys = (keys: readonly Key[]): readonly string[] => [...new Set(keys.map(own))];
36
+
37
+ export const invalidationKeys = (keys: readonly Key[]): readonly string[] => [
38
+ ...new Set(keys.flatMap((key) => (key._tag === "List" ? [key.collection] : [own(key), key.collection]))),
39
+ ];
@@ -0,0 +1,118 @@
1
+ import { Schema } from "effect";
2
+ import type { Bivariant } from "@shivaedev/types";
3
+ import type { Key } from "./keys.ts";
4
+ import { type MatchingTags, type Reject, type RejectionSpecs, type Rejections, type RejectionUnion, rejectionSet } from "./rejection.ts";
5
+
6
+ export type PayloadSchema<Payload extends Schema.Top | Schema.Struct.Fields> = Payload extends Schema.Struct.Fields
7
+ ? Schema.Struct<Payload>
8
+ : Payload;
9
+
10
+ type NoRejections = Record<never, never>;
11
+
12
+ export interface OperationShape {
13
+ readonly error: Schema.Top;
14
+ readonly kind: "query" | "command";
15
+ readonly name: string;
16
+ readonly payload: Schema.Top;
17
+ readonly Rejection: object;
18
+ readonly reject: object;
19
+ readonly rejections: RejectionSpecs;
20
+ readonly success: Schema.Top;
21
+ }
22
+
23
+ export interface QueryShape extends OperationShape {
24
+ readonly kind: "query";
25
+ reads: Bivariant<(payload: unknown) => readonly Key[]>;
26
+ }
27
+
28
+ export interface CommandShape extends OperationShape {
29
+ invalidates: Bivariant<(payload: unknown, result: unknown) => readonly Key[]>;
30
+ readonly kind: "command";
31
+ }
32
+
33
+ interface Operation<Name extends string, Payload extends Schema.Top, Success extends Schema.Top, Specs extends RejectionSpecs>
34
+ extends OperationShape {
35
+ readonly error: RejectionUnion<Specs>;
36
+ readonly name: Name;
37
+ readonly payload: Payload;
38
+ readonly Rejection: Rejections<Specs>;
39
+ readonly reject: Reject<Specs>;
40
+ readonly rejections: Specs;
41
+ readonly success: Success;
42
+ }
43
+
44
+ export interface Query<Name extends string, Payload extends Schema.Top, Success extends Schema.Top, Specs extends RejectionSpecs>
45
+ extends Operation<Name, Payload, Success, Specs> {
46
+ readonly kind: "query";
47
+ reads: (payload: Payload["Type"]) => readonly Key[];
48
+ }
49
+
50
+ export interface Command<Name extends string, Payload extends Schema.Top, Success extends Schema.Top, Specs extends RejectionSpecs>
51
+ extends Operation<Name, Payload, Success, Specs> {
52
+ invalidates: (payload: Payload["Type"], result: Success["Type"]) => readonly Key[];
53
+ readonly kind: "command";
54
+ }
55
+
56
+ interface Declaration<Payload extends Schema.Top | Schema.Struct.Fields, Success extends Schema.Top, Specs extends RejectionSpecs> {
57
+ readonly payload?: Payload;
58
+ readonly rejections?: Specs & MatchingTags<Specs>;
59
+ readonly success?: Success;
60
+ }
61
+
62
+ interface Loose {
63
+ readonly payload?: Schema.Top | Schema.Struct.Fields;
64
+ readonly rejections?: RejectionSpecs;
65
+ readonly success?: Schema.Top;
66
+ }
67
+
68
+ const payloadSchema = (payload: Loose["payload"]): Schema.Top => {
69
+ if (payload === undefined) {
70
+ return Schema.Void;
71
+ }
72
+ return Schema.isSchema(payload) ? payload : Schema.Struct(payload);
73
+ };
74
+
75
+ const operation = <Kind extends OperationShape["kind"]>(kind: Kind, name: string, declaration: Loose) => {
76
+ const { payload, success, rejections = {} } = declaration;
77
+ return {
78
+ kind,
79
+ name,
80
+ payload: payloadSchema(payload),
81
+ rejections,
82
+ success: success ?? Schema.Void,
83
+ ...rejectionSet(rejections),
84
+ };
85
+ };
86
+
87
+ export function query<
88
+ const Name extends string,
89
+ Payload extends Schema.Top | Schema.Struct.Fields = Schema.Void,
90
+ Success extends Schema.Top = Schema.Void,
91
+ const Specs extends RejectionSpecs = NoRejections,
92
+ >(
93
+ name: Name,
94
+ declaration: Declaration<Payload, Success, Specs> & {
95
+ readonly reads: (payload: PayloadSchema<Payload>["Type"]) => readonly Key[];
96
+ },
97
+ ): NoInfer<Query<Name, PayloadSchema<Payload>, Success, Specs>>;
98
+ export function query(name: string, declaration: Loose & { readonly reads: (payload: unknown) => readonly Key[] }): QueryShape {
99
+ return { ...operation("query", name, declaration), reads: declaration.reads };
100
+ }
101
+
102
+ export function command<
103
+ const Name extends string,
104
+ Payload extends Schema.Top | Schema.Struct.Fields = Schema.Void,
105
+ Success extends Schema.Top = Schema.Void,
106
+ const Specs extends RejectionSpecs = NoRejections,
107
+ >(
108
+ name: Name,
109
+ declaration: Declaration<Payload, Success, Specs> & {
110
+ readonly invalidates: (payload: PayloadSchema<Payload>["Type"], result: Success["Type"]) => readonly Key[];
111
+ },
112
+ ): NoInfer<Command<Name, PayloadSchema<Payload>, Success, Specs>>;
113
+ export function command(
114
+ name: string,
115
+ declaration: Loose & { readonly invalidates: (payload: unknown, result: unknown) => readonly Key[] },
116
+ ): CommandShape {
117
+ return { ...operation("command", name, declaration), invalidates: declaration.invalidates };
118
+ }
@@ -0,0 +1,88 @@
1
+ import { type Cause, Effect, Schema } from "effect";
2
+
3
+ export type RejectionValue<Tag extends string, Fields extends Schema.Struct.Fields> = Schema.TaggedStruct<Tag, Fields>["Type"] & Cause.YieldableError;
4
+
5
+ export type RejectionClass<Tag extends string, Fields extends Schema.Struct.Fields> = Schema.Class<
6
+ RejectionValue<Tag, Fields>,
7
+ Schema.TaggedStruct<Tag, Fields>,
8
+ Cause.YieldableError
9
+ >;
10
+
11
+ export type TaggedRejection = Schema.Top & (new (...args: never) => { readonly _tag: string });
12
+
13
+ export interface RejectionSpecs {
14
+ readonly [tag: string]: Schema.Struct.Fields | TaggedRejection;
15
+ }
16
+
17
+ export type Rejections<Specs extends RejectionSpecs> = {
18
+ readonly [Tag in keyof Specs & string]: Specs[Tag] extends TaggedRejection
19
+ ? Specs[Tag]
20
+ : Specs[Tag] extends Schema.Struct.Fields
21
+ ? RejectionClass<Tag, Specs[Tag]>
22
+ : never;
23
+ };
24
+
25
+ export type RejectedBy<Specs extends RejectionSpecs> = {
26
+ [Tag in keyof Specs & string]: Rejections<Specs>[Tag]["Type"];
27
+ }[keyof Specs & string];
28
+
29
+ export type Reject<Specs extends RejectionSpecs> = {
30
+ readonly [Tag in keyof Specs & string]: Rejections<Specs>[Tag] extends new (
31
+ ...args: infer Arguments
32
+ ) => infer Value
33
+ ? (...args: Arguments) => Effect.Effect<never, Value>
34
+ : never;
35
+ };
36
+
37
+ export type RejectionUnion<Specs extends RejectionSpecs> = [keyof Specs & string] extends [never]
38
+ ? Schema.Never
39
+ : Schema.Union<readonly Rejections<Specs>[keyof Specs & string][]>;
40
+
41
+ export type MatchingTags<Specs extends RejectionSpecs> = {
42
+ readonly [Tag in keyof Specs]: Specs[Tag] extends TaggedRejection
43
+ ? Specs[Tag]["Type"] extends { readonly _tag: Tag }
44
+ ? Specs[Tag]
45
+ : `The rejection class under "${Tag & string}" must have that _tag`
46
+ : Specs[Tag];
47
+ };
48
+
49
+ export interface RejectionSet<Specs extends RejectionSpecs> {
50
+ readonly error: RejectionUnion<Specs>;
51
+ readonly Rejection: Rejections<Specs>;
52
+ readonly reject: Reject<Specs>;
53
+ }
54
+
55
+ interface LooseRejectionSet {
56
+ readonly error: Schema.Top;
57
+ readonly Rejection: { readonly [tag: string]: TaggedRejection };
58
+ readonly reject: { readonly [tag: string]: (...args: readonly unknown[]) => Effect.Effect<never, unknown> };
59
+ }
60
+
61
+ export function rejectionSet<const Specs extends RejectionSpecs>(specs: Specs): RejectionSet<Specs>;
62
+ export function rejectionSet(specs: RejectionSpecs): LooseRejectionSet {
63
+ const classes = Object.entries(specs).map(([tag, spec]): readonly [string, TaggedRejection] => [
64
+ tag,
65
+ Schema.isSchema(spec) ? spec : Schema.TaggedError<Cause.YieldableError>()(tag, spec),
66
+ ]);
67
+ return {
68
+ error: classes.length === 0 ? Schema.Never : Schema.Union(classes.map(([, schema]) => schema)),
69
+ Rejection: Object.fromEntries(classes),
70
+ reject: Object.fromEntries(
71
+ classes.map(([tag, Rejection]) => [tag, (...args: readonly unknown[]) => Effect.fail(Reflect.construct(Rejection, args))]),
72
+ ),
73
+ };
74
+ }
75
+
76
+ export type FieldRejection<Field extends string> = Readonly<{
77
+ field: Schema.Literals<readonly Field[]>;
78
+ message: Schema.String;
79
+ }>;
80
+
81
+ export function fieldRejection<const Fields extends Schema.Struct.Fields>(struct: { readonly fields: Fields }): FieldRejection<keyof Fields & string>;
82
+ export function fieldRejection<const Fields extends Schema.Struct.Fields, const Field extends keyof Fields & string>(
83
+ struct: { readonly fields: Fields },
84
+ fields: readonly Field[],
85
+ ): FieldRejection<Field>;
86
+ export function fieldRejection(struct: { readonly fields: Schema.Struct.Fields }, fields?: readonly string[]): FieldRejection<string> {
87
+ return { field: Schema.Literals(fields ?? Object.keys(struct.fields)), message: Schema.String };
88
+ }