@shivaedev/effect-contract 0.0.0 → 0.1.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,10 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - 2026-09-26
4
+
5
+ ### Added
6
+
7
+ - Declare queries and commands with typed rejections and reactivity keys; group
8
+ them into a native `RpcGroup` with namespaced tags.
9
+ - Bind a contract to a native `AtomRpc` service: query atoms register declared
10
+ 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 Meal extends Schema.Class<Meal>("Meal")({ id: Schema.Number, name: Schema.String, calories: Schema.Number }) {}
10
+ class MealNotFound extends Schema.TaggedError<MealNotFound>()("MealNotFound", {}) {}
11
+ const MealDraft = Schema.Struct({ name: Meal.fields.name, calories: Meal.fields.calories });
12
+
13
+ export const meals = collection("meals", Meal.fields.id);
14
+
15
+ export const GetMeal = query("get", {
16
+ payload: { id: Schema.Number },
17
+ success: Meal,
18
+ rejections: { MealNotFound },
19
+ reads: ({ id }) => [meals.item(id)],
20
+ });
21
+ export const ListMeals = query("list", { success: Schema.Array(Meal), reads: () => [meals.list] });
22
+ export const SaveMeal = command("save", {
23
+ payload: { id: Schema.Number, ...MealDraft.fields },
24
+ success: Meal,
25
+ rejections: { MealNotFound, MealValidation: fieldRejection(MealDraft) },
26
+ invalidates: ({ id }) => [meals.item(id)],
27
+ });
28
+
29
+ export const Meals = contract("meals", { queries: [GetMeal, ListMeals], commands: [SaveMeal] }).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(MealDraft)` 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 `Meals.toLayer(Effect.gen(...))` and `Meals.of({ "meals.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("meals", Meal.fields.id)` gives `meals.list` and `meals.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
+ | `meals.item(id)` | `` `meals:${id}` `` | `` `meals:${id}` `` and `"meals"` |
66
+ | `meals.list` | `"meals"` | `"meals"` |
67
+
68
+ An item change therefore refreshes that item and every list, but not other items. Invalidating `meals.list` does not refresh item queries. The strings match native Reactivity's hashing of `{ meals: [id] }`.
69
+
70
+ ### Client binding
71
+
72
+ `bind` is browser-safe and uses no React:
73
+
74
+ ```ts
75
+ class MealsClient extends AtomRpc.Service<MealsClient>()("app/MealsClient", { group: Meals, protocol }) {}
76
+ const api = bind(Meals, MealsClient);
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, MealsClient | 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 timeToLive?: Duration.Input;
17
+ readonly serializationKey?: string;
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 ReadonlyArray<QueryShape>, Commands extends ReadonlyArray<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 ReadonlyArray<QueryShape>, Commands extends ReadonlyArray<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,IAAwB;IACjG,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,QAA4G,EAC5G,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 ReadonlyArray<QueryShape>, Commands extends ReadonlyArray<CommandShape>> {
6
+ readonly name: Name;
7
+ readonly queries: Queries;
8
+ readonly commands: Commands;
9
+ }
10
+ export interface Contract<Name extends string, Queries extends ReadonlyArray<QueryShape>, Commands extends ReadonlyArray<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 ReadonlyArray<OperationShape>, Seen extends string = never> = Operations extends readonly [
15
+ infer Head extends OperationShape,
16
+ ...infer Tail extends ReadonlyArray<OperationShape>
17
+ ] ? (Head["name"] extends Seen ? Head["name"] : never) | Duplicated<Tail, Seen | Head["name"]> : never;
18
+ type Unique<Operations extends ReadonlyArray<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 ReadonlyArray<QueryShape> = readonly [], const Commands extends ReadonlyArray<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,20 @@
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
+ const withDeclaration = (group, declaration) => {
8
+ const nativeMiddleware = group.middleware.bind(group);
9
+ return Object.assign(group, {
10
+ declaration,
11
+ middleware: (middleware) => withDeclaration(nativeMiddleware(middleware), declaration),
12
+ });
13
+ };
14
+ export function contract(name, operations) {
15
+ const { queries = [], commands = [] } = operations;
16
+ assertUnique([...queries, ...commands]);
17
+ const rpcs = [...queries, ...commands].map((operation) => Rpc.make(`${name}.${operation.name}`, { payload: operation.payload, success: operation.success, error: operation.error }));
18
+ return withDeclaration(RpcGroup.make(...rpcs), { name, queries, commands });
19
+ }
20
+ //# 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,UAAyC,EAAE,EAAE;IAClE,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;QAAE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAClI,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,UAA6G;IAE7G,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,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,CAAC,CACzH,CAAC;IACF,OAAO,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,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 name: Name;
15
+ readonly list: ListKey<Name>;
16
+ readonly item: (id: Id) => ItemKey<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: ReadonlyArray<Key>) => ReadonlyArray<string>;
22
+ export declare const invalidationKeys: (keys: ReadonlyArray<Key>) => ReadonlyArray<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,IAAI,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC;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,aAAa,CAAC,GAAG,CAAC,KAAG,aAAa,CAAC,MAAM,CAAgC,CAAC;AAEzG,eAAO,MAAM,gBAAgB,GAAI,MAAM,aAAa,CAAC,GAAG,CAAC,KAAG,aAAa,CAAC,MAAM,CAE/E,CAAC"}
package/dist/keys.js ADDED
@@ -0,0 +1,11 @@
1
+ export const collection = (name, _id) => ({
2
+ name,
3
+ list: { _tag: "List", collection: name },
4
+ item: (id) => ({ _tag: "Item", collection: name, id }),
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;IACJ,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE;IACxC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;CACtD,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,IAAwB,EAAyB,EAAE,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAEzG,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,IAAwB,EAAyB,EAAE,CAAC;IACpF,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,53 @@
1
+ import { Schema } from "effect";
2
+ import type { Key } from "./keys.ts";
3
+ import { type MatchingTags, type Reject, type RejectionSpecs, type Rejections, type RejectionUnion } from "./rejection.ts";
4
+ export type PayloadSchema<Payload extends Schema.Top | Schema.Struct.Fields> = Payload extends Schema.Struct.Fields ? Schema.Struct<Payload> : Payload;
5
+ type NoRejections = Record<never, never>;
6
+ export interface OperationShape {
7
+ readonly kind: "query" | "command";
8
+ readonly name: string;
9
+ readonly payload: Schema.Top;
10
+ readonly success: Schema.Top;
11
+ readonly error: Schema.Top;
12
+ readonly rejections: RejectionSpecs;
13
+ readonly Rejection: object;
14
+ readonly reject: object;
15
+ }
16
+ export interface QueryShape extends OperationShape {
17
+ readonly kind: "query";
18
+ reads(payload: unknown): ReadonlyArray<Key>;
19
+ }
20
+ export interface CommandShape extends OperationShape {
21
+ readonly kind: "command";
22
+ invalidates(payload: unknown, result: unknown): ReadonlyArray<Key>;
23
+ }
24
+ interface Operation<Name extends string, Payload extends Schema.Top, Success extends Schema.Top, Specs extends RejectionSpecs> extends OperationShape {
25
+ readonly name: Name;
26
+ readonly payload: Payload;
27
+ readonly success: Success;
28
+ readonly error: RejectionUnion<Specs>;
29
+ readonly rejections: Specs;
30
+ readonly Rejection: Rejections<Specs>;
31
+ readonly reject: Reject<Specs>;
32
+ }
33
+ export interface Query<Name extends string, Payload extends Schema.Top, Success extends Schema.Top, Specs extends RejectionSpecs> extends Operation<Name, Payload, Success, Specs> {
34
+ readonly kind: "query";
35
+ reads(payload: Payload["Type"]): ReadonlyArray<Key>;
36
+ }
37
+ export interface Command<Name extends string, Payload extends Schema.Top, Success extends Schema.Top, Specs extends RejectionSpecs> extends Operation<Name, Payload, Success, Specs> {
38
+ readonly kind: "command";
39
+ invalidates(payload: Payload["Type"], result: Success["Type"]): ReadonlyArray<Key>;
40
+ }
41
+ interface Declaration<Payload extends Schema.Top | Schema.Struct.Fields, Success extends Schema.Top, Specs extends RejectionSpecs> {
42
+ readonly payload?: Payload;
43
+ readonly success?: Success;
44
+ readonly rejections?: Specs & MatchingTags<Specs>;
45
+ }
46
+ 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> & {
47
+ readonly reads: (payload: PayloadSchema<Payload>["Type"]) => ReadonlyArray<Key>;
48
+ }): NoInfer<Query<Name, PayloadSchema<Payload>, Success, Specs>>;
49
+ 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> & {
50
+ readonly invalidates: (payload: PayloadSchema<Payload>["Type"], result: Success["Type"]) => ReadonlyArray<Key>;
51
+ }): NoInfer<Command<Name, PayloadSchema<Payload>, Success, Specs>>;
52
+ export {};
53
+ //# 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,25 @@
1
+ import { Schema } from "effect";
2
+ import { rejectionSet } from "./rejection.js";
3
+ const payloadSchema = (payload) => {
4
+ if (payload === undefined)
5
+ return Schema.Void;
6
+ return Schema.isSchema(payload) ? payload : Schema.Struct(payload);
7
+ };
8
+ const operation = (kind, name, declaration) => {
9
+ const { payload, success, rejections = {} } = declaration;
10
+ return {
11
+ kind,
12
+ name,
13
+ payload: payloadSchema(payload),
14
+ success: success ?? Schema.Void,
15
+ rejections,
16
+ ...rejectionSet(rejections),
17
+ };
18
+ };
19
+ export function query(name, declaration) {
20
+ return { ...operation("query", name, declaration), reads: declaration.reads };
21
+ }
22
+ export function command(name, declaration) {
23
+ return { ...operation("command", name, declaration), invalidates: declaration.invalidates };
24
+ }
25
+ //# 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;AAEhC,OAAO,EAA6F,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAgEzI,MAAM,aAAa,GAAG,CAAC,OAAyB,EAAc,EAAE;IAC/D,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC;IAC9C,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,OAAO,EAAE,OAAO,IAAI,MAAM,CAAC,IAAI;QAC/B,UAAU;QACV,GAAG,YAAY,CAAC,UAAU,CAAC;KAC3B,CAAC;AACH,CAAC,CAAC;AAaF,MAAM,UAAU,KAAK,CAAC,IAAY,EAAE,WAAiF;IACpH,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,WAAwG;IAExG,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 type 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<ReadonlyArray<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 Rejection: Rejections<Specs>;
27
+ readonly reject: Reject<Specs>;
28
+ readonly error: RejectionUnion<Specs>;
29
+ }
30
+ export declare function rejectionSet<const Specs extends RejectionSpecs>(specs: Specs): RejectionSet<Specs>;
31
+ export type FieldRejection<Field extends string> = {
32
+ readonly field: Schema.Literals<ReadonlyArray<Field>>;
33
+ readonly 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: ReadonlyArray<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":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEpD,MAAM,MAAM,cAAc,CAAC,GAAG,SAAS,MAAM,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,cAAc,CAAC;AAEtJ,MAAM,MAAM,cAAc,CAAC,GAAG,SAAS,MAAM,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,CACjG,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,EAC3B,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,EAChC,KAAK,CAAC,cAAc,CACpB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,KAAK;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE/F,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,eAAe,CAAA;CAAE,CAAC;AAEhG,MAAM,MAAM,UAAU,CAAC,KAAK,SAAS,cAAc,IAAI;IACtD,QAAQ,EAAE,GAAG,IAAI,MAAM,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,eAAe,GACvE,KAAK,CAAC,GAAG,CAAC,GACV,KAAK,CAAC,GAAG,CAAC,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,GACtC,cAAc,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,GAC/B,KAAK;CACT,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,KAAK,SAAS,cAAc,IAAI;KACrD,GAAG,IAAI,MAAM,KAAK,GAAG,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;CAC7D,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC;AAExB,MAAM,MAAM,MAAM,CAAC,KAAK,SAAS,cAAc,IAAI;IAClD,QAAQ,EAAE,GAAG,IAAI,MAAM,KAAK,GAAG,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,SAAS,KACtE,GAAG,IAAI,EAAE,MAAM,SAAS,KACpB,MAAM,KAAK,GACb,CAAC,GAAG,IAAI,EAAE,SAAS,KAAK,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,GACnD,KAAK;CACR,CAAC;AAEF,MAAM,MAAM,cAAc,CAAC,KAAK,SAAS,cAAc,IAAI,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,GAC9F,MAAM,CAAC,KAAK,GACZ,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;AAExE,MAAM,MAAM,YAAY,CAAC,KAAK,SAAS,cAAc,IAAI;IACxD,QAAQ,EAAE,GAAG,IAAI,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,eAAe,GAC9D,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAA;KAAE,GAChD,KAAK,CAAC,GAAG,CAAC,GACV,8BAA8B,GAAG,GAAG,MAAM,uBAAuB,GAClE,KAAK,CAAC,GAAG,CAAC;CACb,CAAC;AAEF,MAAM,WAAW,YAAY,CAAC,KAAK,SAAS,cAAc;IACzD,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;CACtC;AAQD,wBAAgB,YAAY,CAAC,KAAK,CAAC,KAAK,SAAS,cAAc,EAAE,KAAK,EAAE,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;AAepG,MAAM,MAAM,cAAc,CAAC,KAAK,SAAS,MAAM,IAAI;IAClD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;IACtD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC;CAChC,CAAC;AAEF,wBAAgB,cAAc,CAAC,KAAK,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,cAAc,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC;AACtJ,wBAAgB,cAAc,CAAC,KAAK,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,SAAS,MAAM,MAAM,GAAG,MAAM,EAClH,MAAM,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACnC,MAAM,EAAE,aAAa,CAAC,KAAK,CAAC,GAC1B,cAAc,CAAC,KAAK,CAAC,CAAC"}
@@ -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
+ Rejection: Object.fromEntries(classes),
9
+ reject: Object.fromEntries(classes.map(([tag, Rejection]) => [tag, (...args) => Effect.fail(Reflect.construct(Rejection, args))])),
10
+ error: classes.length === 0 ? Schema.Never : Schema.Union(classes.map(([, schema]) => schema)),
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;AA2DpD,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,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,IAA4B,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAC9H;QACD,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;KAC9F,CAAC;AACH,CAAC;AAYD,MAAM,UAAU,cAAc,CAAC,MAAiD,EAAE,MAA8B;IAC/G,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,59 @@
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.",
3
+ "version": "0.1.0",
4
+ "description": "Typed query and command contracts over native Effect RPC and reactivity",
5
+ "type": "module",
5
6
  "license": "MIT",
6
7
  "repository": {
7
8
  "type": "git",
8
9
  "url": "git+https://github.com/ShivaeDev/platform.git",
9
10
  "directory": "packages/effect-contract"
10
11
  },
12
+ "homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/effect-contract#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/ShivaeDev/platform/issues"
15
+ },
16
+ "engines": {
17
+ "node": ">=24"
18
+ },
19
+ "sideEffects": false,
20
+ "files": [
21
+ "dist",
22
+ "src",
23
+ "CHANGELOG.md",
24
+ "README.md"
25
+ ],
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "source": "./src/index.ts",
30
+ "import": "./dist/index.js",
31
+ "default": "./dist/index.js"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
11
35
  "publishConfig": {
12
- "access": "public"
36
+ "access": "public",
37
+ "provenance": true
38
+ },
39
+ "peerDependencies": {
40
+ "effect": "4.0.0-rc.112"
41
+ },
42
+ "devDependencies": {
43
+ "@effect/vitest": "4.0.0-rc.112",
44
+ "@types/node": "24.10.1",
45
+ "@typescript/native": "npm:typescript@7.0.2",
46
+ "effect": "4.0.0-rc.112",
47
+ "typescript": "npm:@typescript/typescript6@6.0.2",
48
+ "vitest": "4.1.9"
49
+ },
50
+ "scripts": {
51
+ "build": "node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist', { force: true, recursive: true }))\" && tsc6 --project tsconfig.build.json",
52
+ "check": "biome check .",
53
+ "test": "vitest run",
54
+ "test:package": "node scripts/test-package.mjs",
55
+ "typecheck": "tsc --noEmit",
56
+ "typecheck:compat": "tsc6 --noEmit",
57
+ "ready": "pnpm check && pnpm typecheck && pnpm typecheck:compat && pnpm test && pnpm build && pnpm test:package"
13
58
  }
14
- }
59
+ }
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 timeToLive?: Duration.Input;
27
+ readonly serializationKey?: string;
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 ReadonlyArray<QueryShape>,
42
+ Commands extends ReadonlyArray<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: ReadonlyArray<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 ReadonlyArray<QueryShape>,
73
+ Commands extends ReadonlyArray<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, ReadonlyArray<QueryShape>, ReadonlyArray<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,78 @@
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 ReadonlyArray<QueryShape>, Commands extends ReadonlyArray<CommandShape>> {
11
+ readonly name: Name;
12
+ readonly queries: Queries;
13
+ readonly commands: Commands;
14
+ }
15
+
16
+ export interface Contract<
17
+ Name extends string,
18
+ Queries extends ReadonlyArray<QueryShape>,
19
+ Commands extends ReadonlyArray<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 ReadonlyArray<OperationShape>, Seen extends string = never> = Operations extends readonly [
27
+ infer Head extends OperationShape,
28
+ ...infer Tail extends ReadonlyArray<OperationShape>,
29
+ ]
30
+ ? (Head["name"] extends Seen ? Head["name"] : never) | Duplicated<Tail, Seen | Head["name"]>
31
+ : never;
32
+
33
+ type Unique<Operations extends ReadonlyArray<OperationShape>> = [Duplicated<Operations>] extends [never]
34
+ ? unknown
35
+ : `Operation names must be unique; duplicated: ${Duplicated<Operations>}`;
36
+
37
+ const assertUnique = (operations: ReadonlyArray<OperationShape>) => {
38
+ const duplicated = operations.map(({ name }) => name).filter((name, index, names) => names.indexOf(name) !== index);
39
+ if (duplicated.length > 0) throw new Error(`Operation names must be unique; duplicated: ${[...new Set(duplicated)].join(", ")}`);
40
+ };
41
+
42
+ type AnyDeclared = Declared<string, ReadonlyArray<QueryShape>, ReadonlyArray<CommandShape>>;
43
+
44
+ interface Group {
45
+ middleware(middleware: RpcMiddleware.AnyService): Group;
46
+ }
47
+
48
+ interface DeclaredGroup extends Group {
49
+ readonly declaration: AnyDeclared;
50
+ }
51
+
52
+ const withDeclaration = (group: Group, declaration: AnyDeclared): DeclaredGroup => {
53
+ const nativeMiddleware = group.middleware.bind(group);
54
+ return Object.assign(group, {
55
+ declaration,
56
+ middleware: (middleware: RpcMiddleware.AnyService) => withDeclaration(nativeMiddleware(middleware), declaration),
57
+ });
58
+ };
59
+
60
+ export function contract<
61
+ const Name extends string,
62
+ const Queries extends ReadonlyArray<QueryShape> = readonly [],
63
+ const Commands extends ReadonlyArray<CommandShape> = readonly [],
64
+ >(
65
+ name: Name,
66
+ operations: { readonly queries?: Queries & Unique<Queries>; readonly commands?: Commands & Unique<[...Queries, ...Commands]> },
67
+ ): Contract<Name, Queries, Commands>;
68
+ export function contract(
69
+ name: string,
70
+ operations: { readonly queries?: ReadonlyArray<QueryShape>; readonly commands?: ReadonlyArray<CommandShape> },
71
+ ): DeclaredGroup {
72
+ const { queries = [], commands = [] } = operations;
73
+ assertUnique([...queries, ...commands]);
74
+ const rpcs = [...queries, ...commands].map((operation) =>
75
+ Rpc.make(`${name}.${operation.name}`, { payload: operation.payload, success: operation.success, error: operation.error }),
76
+ );
77
+ return withDeclaration(RpcGroup.make(...rpcs), { name, queries, commands });
78
+ }
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 name: Name;
20
+ readonly list: ListKey<Name>;
21
+ readonly item: (id: Id) => ItemKey<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
+ name,
29
+ list: { _tag: "List", collection: name },
30
+ item: (id) => ({ _tag: "Item", collection: name, id }),
31
+ });
32
+
33
+ const own = (key: Key): string => (key._tag === "List" ? key.collection : `${key.collection}:${key.id}`);
34
+
35
+ export const readKeys = (keys: ReadonlyArray<Key>): ReadonlyArray<string> => [...new Set(keys.map(own))];
36
+
37
+ export const invalidationKeys = (keys: ReadonlyArray<Key>): ReadonlyArray<string> => [
38
+ ...new Set(keys.flatMap((key) => (key._tag === "List" ? [key.collection] : [own(key), key.collection]))),
39
+ ];
@@ -0,0 +1,115 @@
1
+ import { Schema } from "effect";
2
+ import type { Key } from "./keys.ts";
3
+ import { type MatchingTags, type Reject, type RejectionSpecs, type Rejections, type RejectionUnion, rejectionSet } from "./rejection.ts";
4
+
5
+ export type PayloadSchema<Payload extends Schema.Top | Schema.Struct.Fields> = Payload extends Schema.Struct.Fields
6
+ ? Schema.Struct<Payload>
7
+ : Payload;
8
+
9
+ type NoRejections = Record<never, never>;
10
+
11
+ export interface OperationShape {
12
+ readonly kind: "query" | "command";
13
+ readonly name: string;
14
+ readonly payload: Schema.Top;
15
+ readonly success: Schema.Top;
16
+ readonly error: Schema.Top;
17
+ readonly rejections: RejectionSpecs;
18
+ readonly Rejection: object;
19
+ readonly reject: object;
20
+ }
21
+
22
+ export interface QueryShape extends OperationShape {
23
+ readonly kind: "query";
24
+ reads(payload: unknown): ReadonlyArray<Key>;
25
+ }
26
+
27
+ export interface CommandShape extends OperationShape {
28
+ readonly kind: "command";
29
+ invalidates(payload: unknown, result: unknown): ReadonlyArray<Key>;
30
+ }
31
+
32
+ interface Operation<Name extends string, Payload extends Schema.Top, Success extends Schema.Top, Specs extends RejectionSpecs>
33
+ extends OperationShape {
34
+ readonly name: Name;
35
+ readonly payload: Payload;
36
+ readonly success: Success;
37
+ readonly error: RejectionUnion<Specs>;
38
+ readonly rejections: Specs;
39
+ readonly Rejection: Rejections<Specs>;
40
+ readonly reject: Reject<Specs>;
41
+ }
42
+
43
+ export interface Query<Name extends string, Payload extends Schema.Top, Success extends Schema.Top, Specs extends RejectionSpecs>
44
+ extends Operation<Name, Payload, Success, Specs> {
45
+ readonly kind: "query";
46
+ reads(payload: Payload["Type"]): ReadonlyArray<Key>;
47
+ }
48
+
49
+ export interface Command<Name extends string, Payload extends Schema.Top, Success extends Schema.Top, Specs extends RejectionSpecs>
50
+ extends Operation<Name, Payload, Success, Specs> {
51
+ readonly kind: "command";
52
+ invalidates(payload: Payload["Type"], result: Success["Type"]): ReadonlyArray<Key>;
53
+ }
54
+
55
+ interface Declaration<Payload extends Schema.Top | Schema.Struct.Fields, Success extends Schema.Top, Specs extends RejectionSpecs> {
56
+ readonly payload?: Payload;
57
+ readonly success?: Success;
58
+ readonly rejections?: Specs & MatchingTags<Specs>;
59
+ }
60
+
61
+ interface Loose {
62
+ readonly payload?: Schema.Top | Schema.Struct.Fields;
63
+ readonly success?: Schema.Top;
64
+ readonly rejections?: RejectionSpecs;
65
+ }
66
+
67
+ const payloadSchema = (payload: Loose["payload"]): Schema.Top => {
68
+ if (payload === undefined) return Schema.Void;
69
+ return Schema.isSchema(payload) ? payload : Schema.Struct(payload);
70
+ };
71
+
72
+ const operation = <Kind extends OperationShape["kind"]>(kind: Kind, name: string, declaration: Loose) => {
73
+ const { payload, success, rejections = {} } = declaration;
74
+ return {
75
+ kind,
76
+ name,
77
+ payload: payloadSchema(payload),
78
+ success: success ?? Schema.Void,
79
+ rejections,
80
+ ...rejectionSet(rejections),
81
+ };
82
+ };
83
+
84
+ export function query<
85
+ const Name extends string,
86
+ Payload extends Schema.Top | Schema.Struct.Fields = Schema.Void,
87
+ Success extends Schema.Top = Schema.Void,
88
+ const Specs extends RejectionSpecs = NoRejections,
89
+ >(
90
+ name: Name,
91
+ declaration: Declaration<Payload, Success, Specs> & {
92
+ readonly reads: (payload: PayloadSchema<Payload>["Type"]) => ReadonlyArray<Key>;
93
+ },
94
+ ): NoInfer<Query<Name, PayloadSchema<Payload>, Success, Specs>>;
95
+ export function query(name: string, declaration: Loose & { readonly reads: (payload: unknown) => ReadonlyArray<Key> }): QueryShape {
96
+ return { ...operation("query", name, declaration), reads: declaration.reads };
97
+ }
98
+
99
+ export function command<
100
+ const Name extends string,
101
+ Payload extends Schema.Top | Schema.Struct.Fields = Schema.Void,
102
+ Success extends Schema.Top = Schema.Void,
103
+ const Specs extends RejectionSpecs = NoRejections,
104
+ >(
105
+ name: Name,
106
+ declaration: Declaration<Payload, Success, Specs> & {
107
+ readonly invalidates: (payload: PayloadSchema<Payload>["Type"], result: Success["Type"]) => ReadonlyArray<Key>;
108
+ },
109
+ ): NoInfer<Command<Name, PayloadSchema<Payload>, Success, Specs>>;
110
+ export function command(
111
+ name: string,
112
+ declaration: Loose & { readonly invalidates: (payload: unknown, result: unknown) => ReadonlyArray<Key> },
113
+ ): CommandShape {
114
+ return { ...operation("command", name, declaration), invalidates: declaration.invalidates };
115
+ }
@@ -0,0 +1,86 @@
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 type RejectionSpecs = { readonly [tag: string]: Schema.Struct.Fields | TaggedRejection };
14
+
15
+ export type Rejections<Specs extends RejectionSpecs> = {
16
+ readonly [Tag in keyof Specs & string]: Specs[Tag] extends TaggedRejection
17
+ ? Specs[Tag]
18
+ : Specs[Tag] extends Schema.Struct.Fields
19
+ ? RejectionClass<Tag, Specs[Tag]>
20
+ : never;
21
+ };
22
+
23
+ export type RejectedBy<Specs extends RejectionSpecs> = {
24
+ [Tag in keyof Specs & string]: Rejections<Specs>[Tag]["Type"];
25
+ }[keyof Specs & string];
26
+
27
+ export type Reject<Specs extends RejectionSpecs> = {
28
+ readonly [Tag in keyof Specs & string]: Rejections<Specs>[Tag] extends new (
29
+ ...args: infer Arguments
30
+ ) => infer Value
31
+ ? (...args: Arguments) => Effect.Effect<never, Value>
32
+ : never;
33
+ };
34
+
35
+ export type RejectionUnion<Specs extends RejectionSpecs> = [keyof Specs & string] extends [never]
36
+ ? Schema.Never
37
+ : Schema.Union<ReadonlyArray<Rejections<Specs>[keyof Specs & string]>>;
38
+
39
+ export type MatchingTags<Specs extends RejectionSpecs> = {
40
+ readonly [Tag in keyof Specs]: Specs[Tag] extends TaggedRejection
41
+ ? Specs[Tag]["Type"] extends { readonly _tag: Tag }
42
+ ? Specs[Tag]
43
+ : `The rejection class under "${Tag & string}" must have that _tag`
44
+ : Specs[Tag];
45
+ };
46
+
47
+ export interface RejectionSet<Specs extends RejectionSpecs> {
48
+ readonly Rejection: Rejections<Specs>;
49
+ readonly reject: Reject<Specs>;
50
+ readonly error: RejectionUnion<Specs>;
51
+ }
52
+
53
+ interface LooseRejectionSet {
54
+ readonly Rejection: { readonly [tag: string]: TaggedRejection };
55
+ readonly reject: { readonly [tag: string]: (...args: ReadonlyArray<unknown>) => Effect.Effect<never, unknown> };
56
+ readonly error: Schema.Top;
57
+ }
58
+
59
+ export function rejectionSet<const Specs extends RejectionSpecs>(specs: Specs): RejectionSet<Specs>;
60
+ export function rejectionSet(specs: RejectionSpecs): LooseRejectionSet {
61
+ const classes = Object.entries(specs).map(([tag, spec]): readonly [string, TaggedRejection] => [
62
+ tag,
63
+ Schema.isSchema(spec) ? spec : Schema.TaggedError<Cause.YieldableError>()(tag, spec),
64
+ ]);
65
+ return {
66
+ Rejection: Object.fromEntries(classes),
67
+ reject: Object.fromEntries(
68
+ classes.map(([tag, Rejection]) => [tag, (...args: ReadonlyArray<unknown>) => Effect.fail(Reflect.construct(Rejection, args))]),
69
+ ),
70
+ error: classes.length === 0 ? Schema.Never : Schema.Union(classes.map(([, schema]) => schema)),
71
+ };
72
+ }
73
+
74
+ export type FieldRejection<Field extends string> = {
75
+ readonly field: Schema.Literals<ReadonlyArray<Field>>;
76
+ readonly message: Schema.String;
77
+ };
78
+
79
+ export function fieldRejection<const Fields extends Schema.Struct.Fields>(struct: { readonly fields: Fields }): FieldRejection<keyof Fields & string>;
80
+ export function fieldRejection<const Fields extends Schema.Struct.Fields, const Field extends keyof Fields & string>(
81
+ struct: { readonly fields: Fields },
82
+ fields: ReadonlyArray<Field>,
83
+ ): FieldRejection<Field>;
84
+ export function fieldRejection(struct: { readonly fields: Schema.Struct.Fields }, fields?: ReadonlyArray<string>): FieldRejection<string> {
85
+ return { field: Schema.Literals(fields ?? Object.keys(struct.fields)), message: Schema.String };
86
+ }