@shivaedev/contract 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +57 -36
  3. package/dist/automaticLive.d.ts +2 -2
  4. package/dist/automaticLive.d.ts.map +1 -1
  5. package/dist/automaticLive.js +4 -3
  6. package/dist/automaticLive.js.map +1 -1
  7. package/dist/bind.d.ts +11 -2
  8. package/dist/bind.d.ts.map +1 -1
  9. package/dist/bind.js +9 -0
  10. package/dist/bind.js.map +1 -1
  11. package/dist/clientOptions.d.ts +23 -0
  12. package/dist/clientOptions.d.ts.map +1 -0
  13. package/dist/clientOptions.js +2 -0
  14. package/dist/clientOptions.js.map +1 -0
  15. package/dist/contract.d.ts +4 -2
  16. package/dist/contract.d.ts.map +1 -1
  17. package/dist/contract.js +6 -5
  18. package/dist/contract.js.map +1 -1
  19. package/dist/internal/bindingOwner.d.ts +1 -1
  20. package/dist/internal/bindingOwner.d.ts.map +1 -1
  21. package/dist/internal/clientOperations.d.ts.map +1 -1
  22. package/dist/internal/clientOperations.js +26 -7
  23. package/dist/internal/clientOperations.js.map +1 -1
  24. package/dist/internal/httpClient.d.ts +21 -0
  25. package/dist/internal/httpClient.d.ts.map +1 -0
  26. package/dist/internal/httpClient.js +13 -0
  27. package/dist/internal/httpClient.js.map +1 -0
  28. package/dist/internal/operationDeclaration.d.ts +19 -0
  29. package/dist/internal/operationDeclaration.d.ts.map +1 -0
  30. package/dist/internal/operationDeclaration.js +20 -0
  31. package/dist/internal/operationDeclaration.js.map +1 -0
  32. package/dist/internal/queryHints.d.ts +8 -0
  33. package/dist/internal/queryHints.d.ts.map +1 -0
  34. package/dist/internal/queryHints.js +34 -0
  35. package/dist/internal/queryHints.js.map +1 -0
  36. package/dist/live.d.ts +7 -1
  37. package/dist/live.d.ts.map +1 -1
  38. package/dist/live.js +2 -2
  39. package/dist/live.js.map +1 -1
  40. package/dist/makeLiveServer.d.ts +27 -1
  41. package/dist/makeLiveServer.d.ts.map +1 -1
  42. package/dist/makeLiveServer.js +15 -8
  43. package/dist/makeLiveServer.js.map +1 -1
  44. package/dist/operation.d.ts +13 -9
  45. package/dist/operation.d.ts.map +1 -1
  46. package/dist/operation.js +5 -19
  47. package/dist/operation.js.map +1 -1
  48. package/dist/publicConfig.d.ts +9 -3
  49. package/dist/publicConfig.d.ts.map +1 -1
  50. package/dist/publicConfig.js +2 -1
  51. package/dist/publicConfig.js.map +1 -1
  52. package/dist/querySubscription.d.ts +15 -0
  53. package/dist/querySubscription.d.ts.map +1 -0
  54. package/dist/querySubscription.js +10 -0
  55. package/dist/querySubscription.js.map +1 -0
  56. package/dist/readPolicy.d.ts +11 -6
  57. package/dist/readPolicy.d.ts.map +1 -1
  58. package/dist/readPolicy.js +16 -19
  59. package/dist/readPolicy.js.map +1 -1
  60. package/dist/readPolicyErrors.d.ts +13 -0
  61. package/dist/readPolicyErrors.d.ts.map +1 -0
  62. package/dist/readPolicyErrors.js +6 -0
  63. package/dist/readPolicyErrors.js.map +1 -0
  64. package/dist/serverMetadata.d.ts +29 -11
  65. package/dist/serverMetadata.d.ts.map +1 -1
  66. package/dist/serverMetadata.js +19 -12
  67. package/dist/serverMetadata.js.map +1 -1
  68. package/dist/testing.d.ts +13 -1
  69. package/dist/testing.d.ts.map +1 -1
  70. package/dist/testing.js +1 -4
  71. package/dist/testing.js.map +1 -1
  72. package/package.json +6 -5
  73. package/src/automaticLive.ts +8 -3
  74. package/src/bind.ts +45 -3
  75. package/src/clientOptions.ts +35 -0
  76. package/src/contract.ts +11 -6
  77. package/src/internal/bindingOwner.ts +1 -1
  78. package/src/internal/clientOperations.ts +30 -11
  79. package/src/internal/httpClient.ts +21 -0
  80. package/src/internal/operationDeclaration.ts +28 -0
  81. package/src/internal/queryHints.ts +38 -0
  82. package/src/live.ts +2 -2
  83. package/src/makeLiveServer.ts +18 -12
  84. package/src/operation.ts +27 -37
  85. package/src/publicConfig.ts +2 -2
  86. package/src/querySubscription.ts +21 -0
  87. package/src/readPolicy.ts +23 -23
  88. package/src/readPolicyErrors.ts +4 -0
  89. package/src/serverMetadata.ts +35 -19
  90. package/src/testing.ts +2 -12
  91. package/dist/internal/makeHintPolicy.d.ts +0 -4
  92. package/dist/internal/makeHintPolicy.d.ts.map +0 -1
  93. package/dist/internal/makeHintPolicy.js +0 -25
  94. package/dist/internal/makeHintPolicy.js.map +0 -1
  95. package/dist/keys.d.ts +0 -34
  96. package/dist/keys.d.ts.map +0 -1
  97. package/dist/keys.js +0 -25
  98. package/dist/keys.js.map +0 -1
  99. package/src/internal/makeHintPolicy.ts +0 -40
  100. package/src/keys.ts +0 -62
@@ -1 +1 @@
1
- {"version":3,"file":"publicConfig.js","sourceRoot":"","sources":["../src/publicConfig.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEtC,MAAM,UAAU,YAAY,CAAO,MAA0B;IAC5D,OAAO,QAAQ,CAAC,uBAAuB,EAAE;QACxC,OAAO,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;KAC7D,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"publicConfig.js","sourceRoot":"","sources":["../src/publicConfig.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAe,MAAM,QAAQ,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEtC,MAAM,UAAU,YAAY,CAAO,MAA0B;IAC5D,OAAO,QAAQ,CAAC,uBAAuB,EAAE;QACxC,OAAO,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;KACrG,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { Effect, type Option } from "effect";
2
+ import type { Key } from "@shivaedev/changes/keys.ts";
3
+ import { type Viewer } from "#readPolicy.ts";
4
+ import type { QueryRequest } from "#serverMetadata.ts";
5
+ export interface QuerySubscription {
6
+ readonly authorized: Effect.Effect<boolean>;
7
+ readonly keys: readonly Key[];
8
+ readonly scopes: readonly (readonly Key[])[];
9
+ }
10
+ export declare const querySubscription: (queries: readonly QueryRequest[], viewer: Option.Option<Viewer>) => Effect.Effect<{
11
+ authorized: Effect.Effect<boolean, never, never>;
12
+ keys: Key[];
13
+ scopes: (readonly Key[])[];
14
+ }, import("./readPolicyErrors.ts").MissingReadPolicy | import("./readPolicyErrors.ts").QueryReadDenied, never>;
15
+ //# sourceMappingURL=querySubscription.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"querySubscription.d.ts","sourceRoot":"","sources":["../src/querySubscription.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AACtD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5C,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,EAAE,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,SAAS,GAAG,EAAE,CAAC,EAAE,CAAC;CAC7C;AAED,eAAO,MAAM,iBAAiB;;;;8GAS5B,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { Context, Effect } from "effect";
2
+ import { checkQuery } from "#readPolicy.ts";
3
+ export const querySubscription = Effect.fnUntraced(function* (queries, viewer) {
4
+ yield* Effect.forEach(queries, ({ query, payload }) => checkQuery(query, payload, viewer));
5
+ const context = Context.makeUnsafe((yield* Effect.context()).mapUnsafe);
6
+ const scopes = queries.map(({ query, payload }) => query.reads(payload, viewer));
7
+ const authorized = Effect.map(Effect.forEach(queries, ({ query, payload }) => Effect.provideContext(query.readPolicy?.(payload, viewer) ?? Effect.succeed(false), context)), (permissions) => permissions.every(Boolean));
8
+ return { authorized, keys: scopes.flat(), scopes };
9
+ });
10
+ //# sourceMappingURL=querySubscription.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"querySubscription.js","sourceRoot":"","sources":["../src/querySubscription.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAe,MAAM,QAAQ,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAe,MAAM,gBAAgB,CAAC;AASzD,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,OAAgC,EAAE,MAA6B;IAC5H,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3F,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,EAAS,CAAC,CAAC,SAAS,CAAC,CAAC;IACxF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IACjF,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAC5B,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,EAC7I,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAC3C,CAAC;IACF,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,MAAM,EAA8B,CAAC;AAChF,CAAC,CAAC,CAAC"}
@@ -1,10 +1,15 @@
1
- import { Context, type Duration, Effect } from "effect";
1
+ import { Context, Effect, Option } from "effect";
2
2
  import type { Rpc } from "effect/rpc";
3
- import type { Key } from "./keys.ts";
4
- export declare const ReadPolicy: Context.Reference<(key: Key) => Effect.Effect<boolean, unknown>>;
5
- export declare const ReadPolicyMemoWindow: Context.Reference<Duration.Input>;
6
- export declare const authorizeReads: <A, E, R, TPolicyServices, TDenied, TPolicyError>(effect: Effect.Effect<A, E, R>, request: {
3
+ import type { QueryShape } from "#operation.ts";
4
+ import { MissingReadPolicy, QueryReadDenied } from "#readPolicyErrors.ts";
5
+ export interface Viewer {
6
+ readonly id: string;
7
+ }
8
+ export type QueryReadPolicy<TPayload> = (payload: TPayload, viewer: Option.Option<Viewer>) => Effect.Effect<boolean, never, unknown>;
9
+ export declare const CurrentViewer: Context.Reference<Option.Option<Viewer>>;
10
+ export declare const checkQuery: (query: QueryShape, payload: unknown, viewer: Option.Option<Viewer>) => Effect.Effect<undefined, MissingReadPolicy | QueryReadDenied, never>;
11
+ export declare const authorizeReads: <A, E, R>(effect: Effect.Effect<A, E, R>, request: {
7
12
  readonly rpc: Rpc.AnyWithProps;
8
13
  readonly payload: unknown;
9
- }, policy: (key: Key) => Effect.Effect<boolean, TPolicyError, TPolicyServices>, denied: Effect.Effect<never, TDenied, never>) => Effect.Effect<A, E | TDenied | TPolicyError, TPolicyServices | Exclude<R, never>>;
14
+ }, viewer: Option.Option<Viewer>) => Effect.Effect<A, E | MissingReadPolicy | QueryReadDenied, Exclude<R, never>>;
10
15
  //# sourceMappingURL=readPolicy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"readPolicy.d.ts","sourceRoot":"","sources":["../src/readPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AAChE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAGrC,eAAO,MAAM,UAAU,0BAA2B,GAAG,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAEvF,CAAC;AAEH,eAAO,MAAM,oBAAoB,mCAE/B,CAAC;AAEH,eAAO,MAAM,cAAc,GAAgC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,eAAe,EAAE,OAAO,EAAE,YAAY;kBAEhF,GAAG,CAAC,YAAY;sBAAoB,OAAO;iBACtD,GAAG,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,eAAe,CAAC,oIAgB1E,CAAC"}
1
+ {"version":3,"file":"readPolicy.d.ts","sourceRoot":"","sources":["../src/readPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG1E,MAAM,WAAW,MAAM;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACpB;AACD,MAAM,MAAM,eAAe,CAAC,QAAQ,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;AAErI,eAAO,MAAM,aAAa,0CAA+G,CAAC;AAC1I,eAAO,MAAM,UAAU,8IAQrB,CAAC;AAEH,eAAO,MAAM,cAAc,GAAgC,CAAC,EAAE,CAAC,EAAE,CAAC;kBAExC,GAAG,CAAC,YAAY;sBAAoB,OAAO;iHAQnE,CAAC"}
@@ -1,24 +1,21 @@
1
1
  import { Context, Effect, Option } from "effect";
2
- import { QueryReads } from "./serverMetadata.js";
3
- export const ReadPolicy = Context.Reference("@shivaedev/contract/ReadPolicy", {
4
- defaultValue: () => () => Effect.succeed(true),
5
- });
6
- export const ReadPolicyMemoWindow = Context.Reference("@shivaedev/contract/ReadPolicyMemoWindow", {
7
- defaultValue: () => "1 second",
8
- });
9
- export const authorizeReads = Effect.fnUntraced(function* (effect, request, policy, denied) {
10
- const context = yield* Effect.context();
11
- function canRead(key) {
12
- return Effect.provideContext(policy(key), context);
2
+ import { MissingReadPolicy, QueryReadDenied } from "#readPolicyErrors.ts";
3
+ import { QueryDefinition } from "#serverMetadata.ts";
4
+ export const CurrentViewer = Context.Reference("@shivaedev/contract/CurrentViewer", { defaultValue: Option.none });
5
+ export const checkQuery = Effect.fnUntraced(function* (query, payload, viewer) {
6
+ if (query.readPolicy === undefined) {
7
+ return yield* new MissingReadPolicy({ message: `Declare a readPolicy on query ${query.name} before running it live or subscribing` });
13
8
  }
14
- const reads = Context.getOption(request.rpc.annotations, QueryReads);
15
- if (Option.isSome(reads)) {
16
- for (const key of reads.value(request.payload)) {
17
- if (!(yield* canRead(key))) {
18
- return yield* denied;
19
- }
20
- }
9
+ const context = Context.makeUnsafe((yield* Effect.context()).mapUnsafe);
10
+ if (!(yield* Effect.provideContext(query.readPolicy(payload, viewer), context))) {
11
+ return yield* new QueryReadDenied({ message: `Read not allowed for query ${query.name}` });
12
+ }
13
+ });
14
+ export const authorizeReads = Effect.fnUntraced(function* (effect, request, viewer) {
15
+ const definition = Context.getOption(request.rpc.annotations, QueryDefinition);
16
+ if (Option.isSome(definition) && definition.value.definition.readPolicy !== undefined) {
17
+ yield* checkQuery(definition.value.definition, request.payload, viewer);
21
18
  }
22
- return yield* Effect.provideService(effect, ReadPolicy, canRead);
19
+ return yield* Effect.provideService(effect, CurrentViewer, viewer);
23
20
  });
24
21
  //# sourceMappingURL=readPolicy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"readPolicy.js","sourceRoot":"","sources":["../src/readPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAiB,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhE,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,CAAgD,gCAAgC,EAAE;IAC5H,YAAY,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;CAC9C,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,OAAO,CAAC,SAAS,CAAiB,0CAA0C,EAAE;IACjH,YAAY,EAAE,GAAG,EAAE,CAAC,UAAU;CAC9B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EACxD,MAA8B,EAC9B,OAAsE,EACtE,MAA2E,EAC3E,MAAqC;IAErC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,EAAmB,CAAC;IACzD,SAAS,OAAO,CAAC,GAAQ;QACxB,OAAO,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IACrE,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAChD,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBAC5B,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC;YACtB,CAAC;QACF,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"readPolicy.js","sourceRoot":"","sources":["../src/readPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGjD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAOrD,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC,SAAS,CAAwB,mCAAmC,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;AAC1I,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,KAAiB,EAAE,OAAgB,EAAE,MAA6B;IACxH,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACpC,OAAO,KAAK,CAAC,CAAC,IAAI,iBAAiB,CAAC,EAAE,OAAO,EAAE,iCAAiC,KAAK,CAAC,IAAI,wCAAwC,EAAE,CAAC,CAAC;IACvI,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,EAAS,CAAC,CAAC,SAAS,CAAC,CAAC;IACxF,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;QACjF,OAAO,KAAK,CAAC,CAAC,IAAI,eAAe,CAAC,EAAE,OAAO,EAAE,8BAA8B,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC5F,CAAC;AACF,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EACxD,MAA8B,EAC9B,OAAsE,EACtE,MAA6B;IAE7B,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;IAC/E,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACvF,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC;AACpE,CAAC,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { Schema } from "effect";
2
+ declare const MissingReadPolicy_base: Schema.Class<MissingReadPolicy, Schema.TaggedStruct<"MissingReadPolicy", {
3
+ readonly message: Schema.String;
4
+ }>, import("effect/Cause").YieldableError>;
5
+ export declare class MissingReadPolicy extends MissingReadPolicy_base {
6
+ }
7
+ declare const QueryReadDenied_base: Schema.Class<QueryReadDenied, Schema.TaggedStruct<"QueryReadDenied", {
8
+ readonly message: Schema.String;
9
+ }>, import("effect/Cause").YieldableError>;
10
+ export declare class QueryReadDenied extends QueryReadDenied_base {
11
+ }
12
+ export {};
13
+ //# sourceMappingURL=readPolicyErrors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readPolicyErrors.d.ts","sourceRoot":"","sources":["../src/readPolicyErrors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;;;;AAEhC,qBAAa,iBAAkB,SAAQ,sBAAwF;CAAG;;;;AAClI,qBAAa,eAAgB,SAAQ,oBAAoF;CAAG"}
@@ -0,0 +1,6 @@
1
+ import { Schema } from "effect";
2
+ export class MissingReadPolicy extends Schema.TaggedError()("MissingReadPolicy", { message: Schema.String }) {
3
+ }
4
+ export class QueryReadDenied extends Schema.TaggedError()("QueryReadDenied", { message: Schema.String }) {
5
+ }
6
+ //# sourceMappingURL=readPolicyErrors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readPolicyErrors.js","sourceRoot":"","sources":["../src/readPolicyErrors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,OAAO,iBAAkB,SAAQ,MAAM,CAAC,WAAW,EAAqB,CAAC,mBAAmB,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;CAAG;AAClI,MAAM,OAAO,eAAgB,SAAQ,MAAM,CAAC,WAAW,EAAmB,CAAC,iBAAiB,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;CAAG"}
@@ -1,11 +1,26 @@
1
- import { Context, Schema } from "effect";
1
+ import { Context, Effect, Option, Schema } from "effect";
2
2
  import { Rpc } from "effect/rpc";
3
- import type { Key } from "#keys.ts";
4
- import type { QueryShape } from "#operation.ts";
5
- declare const QueryReads_base: Context.ServiceClass<QueryReads, "@shivaedev/contract/QueryReads", import("@shivaedev/types/Bivariant.ts").Bivariant<(payload: unknown) => readonly Key[]>>;
6
- export declare class QueryReads extends QueryReads_base {
3
+ import type { Key } from "@shivaedev/changes/keys.ts";
4
+ import type { CommandShape, QueryShape } from "#operation.ts";
5
+ import type { Viewer } from "#readPolicy.ts";
6
+ import { MissingReadPolicy, QueryReadDenied } from "#readPolicyErrors.ts";
7
+ declare const QueryDefinition_base: Context.ServiceClass<QueryDefinition, "@shivaedev/contract/QueryDefinition", {
8
+ readonly definition: QueryShape;
9
+ }>;
10
+ export declare class QueryDefinition extends QueryDefinition_base {
7
11
  }
8
- declare const CommandInvalidation_base: Context.ServiceClass<CommandInvalidation, "@shivaedev/contract/CommandInvalidation", import("@shivaedev/types/Bivariant.ts").Bivariant<(payload: unknown, result: unknown) => readonly Key[]>>;
12
+ export interface QueryRequest {
13
+ readonly payload: unknown;
14
+ readonly query: QueryShape;
15
+ }
16
+ declare const ChangesQueries_base: Context.ServiceClass<ChangesQueries, "@shivaedev/contract/ChangesQueries", {
17
+ readonly resolve: (references: readonly QueryReference[]) => Effect.Effect<readonly QueryRequest[]>;
18
+ }>;
19
+ export declare class ChangesQueries extends ChangesQueries_base {
20
+ }
21
+ declare const CommandInvalidation_base: Context.ServiceClass<CommandInvalidation, "@shivaedev/contract/CommandInvalidation", {
22
+ readonly invalidates: CommandShape["invalidates"];
23
+ }>;
9
24
  export declare class CommandInvalidation extends CommandInvalidation_base {
10
25
  }
11
26
  declare const KeyScope_base: Context.ServiceClass<KeyScope, "@shivaedev/contract/KeyScope", "session">;
@@ -19,15 +34,17 @@ type ReferenceSchema<TQuery extends QueryShape> = TQuery extends QueryShape ? Sc
19
34
  query: Schema.Literal<TQuery["name"]>;
20
35
  payload: TQuery["payload"];
21
36
  }> : never;
22
- declare const ChangesReads_base: Context.ServiceClass<ChangesReads, "@shivaedev/contract/ChangesReads", (references: readonly QueryReference[]) => readonly Key[]>;
23
- export declare class ChangesReads extends ChangesReads_base {
24
- }
25
- export declare function queryKeys(queries: readonly QueryShape[], requested: readonly QueryReference[]): readonly Key[];
37
+ export declare const queryKeys: (queries: readonly QueryShape[], requested: readonly QueryReference[], viewer?: Option.Option<Viewer> | undefined) => Effect.Effect<readonly Key[], never, never>;
26
38
  export declare function changesRpc<const TName extends string, const TQueries extends readonly QueryShape[]>(name: TName, queries: TQueries): Rpc.Rpc<`${TName}.$changes`, Schema.Struct<{
27
39
  queries: Schema.$Array<Schema.Union<readonly ReferenceSchema<TQueries[number]>[]>>;
28
40
  }>, import("effect/rpc/RpcSchema").Stream<Schema.Union<readonly [Schema.Struct<{
29
41
  readonly _tag: Schema.Literal<"Changed">;
30
42
  readonly keys: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
43
+ readonly _tag: Schema.Literal<"Bucket">;
44
+ readonly collection: Schema.String;
45
+ readonly field: Schema.String;
46
+ readonly value: Schema.Union<readonly [Schema.String, Schema.Number]>;
47
+ }>, Schema.Struct<{
31
48
  readonly _tag: Schema.Literal<"List">;
32
49
  readonly collection: Schema.String;
33
50
  }>, Schema.Struct<{
@@ -35,8 +52,9 @@ export declare function changesRpc<const TName extends string, const TQueries ex
35
52
  readonly collection: Schema.String;
36
53
  readonly id: Schema.Union<readonly [Schema.String, Schema.Number]>;
37
54
  }>]>>;
55
+ readonly queryIndexes: Schema.optional<Schema.$Array<Schema.Number>>;
38
56
  }>, Schema.Struct<{
39
57
  readonly _tag: Schema.Literal<"Resync">;
40
- }>]>, Schema.Never>, Schema.Never, never, never>;
58
+ }>]>, Schema.Union<readonly [typeof MissingReadPolicy, typeof QueryReadDenied]>>, Schema.Never, never, never>;
41
59
  export {};
42
60
  //# sourceMappingURL=serverMetadata.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serverMetadata.d.ts","sourceRoot":"","sources":["../src/serverMetadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,OAAO,KAAK,EAAgB,UAAU,EAAE,MAAM,eAAe,CAAC;;AAE9D,qBAAa,UAAW,SAAQ,eAAoF;CAAG;;AAEvH,qBAAa,mBAAoB,SAAQ,wBAExC;CAAG;;AAEJ,qBAAa,QAAS,SAAQ,aAAsE;CAAG;AAEvG,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,KAAK,eAAe,CAAC,MAAM,SAAS,UAAU,IAAI,MAAM,SAAS,UAAU,GACxE,MAAM,CAAC,MAAM,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,CAAA;CAAE,CAAC,GACpF,KAAK,CAAC;qHAEoE,SAAS,cAAc,EAAE,KAAK,SAAS,GAAG,EAAE;AAAzH,qBAAa,YAAa,SAAQ,iBAEjC;CAAG;AAEJ,wBAAgB,SAAS,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,EAAE,SAAS,EAAE,SAAS,cAAc,EAAE,GAAG,SAAS,GAAG,EAAE,CAQ9G;AASD,wBAAgB,UAAU,CAAC,KAAK,CAAC,KAAK,SAAS,MAAM,EAAE,KAAK,CAAC,QAAQ,SAAS,SAAS,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ;;;;;;;;;;;;;;iDAKlI"}
1
+ {"version":3,"file":"serverMetadata.d.ts","sourceRoot":"","sources":["../src/serverMetadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,4BAA4B,CAAC;AAEtD,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;;yBAEmB,UAAU;;AAAvG,qBAAa,eAAgB,SAAQ,oBAA8G;CAAG;AACtJ,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC3B;;sBAImB,CAAC,UAAU,EAAE,SAAS,cAAc,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,SAAS,YAAY,EAAE,CAAC;;AAHrG,qBAAa,cAAe,SAAQ,mBAKK;CAAG;;0BAE0D,YAAY,CAAC,aAAa,CAAC;;AAAjI,qBAAa,mBAAoB,SAAQ,wBAExC;CAAG;;AAEJ,qBAAa,QAAS,SAAQ,aAAsE;CAAG;AAEvG,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,KAAK,eAAe,CAAC,MAAM,SAAS,UAAU,IAAI,MAAM,SAAS,UAAU,GACxE,MAAM,CAAC,MAAM,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,CAAA;CAAE,CAAC,GACpF,KAAK,CAAC;AAST,eAAO,MAAM,SAAS,mKAOpB,CAAC;AASH,wBAAgB,UAAU,CAAC,KAAK,CAAC,KAAK,SAAS,MAAM,EAAE,KAAK,CAAC,QAAQ,SAAS,SAAS,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ;;;;;;;;;;;;;;;;;;;;8GAOlI"}
@@ -1,27 +1,34 @@
1
- import { Context, Schema } from "effect";
1
+ import { Context, Effect, Option, Schema } from "effect";
2
2
  import { Rpc } from "effect/rpc";
3
3
  import { LiveHint } from "#live.ts";
4
- export class QueryReads extends Context.Service()("@shivaedev/contract/QueryReads") {
4
+ import { MissingReadPolicy, QueryReadDenied } from "#readPolicyErrors.ts";
5
+ export class QueryDefinition extends Context.Service()("@shivaedev/contract/QueryDefinition") {
6
+ }
7
+ export class ChangesQueries extends Context.Service()("@shivaedev/contract/ChangesQueries") {
5
8
  }
6
9
  export class CommandInvalidation extends Context.Service()("@shivaedev/contract/CommandInvalidation") {
7
10
  }
8
11
  export class KeyScope extends Context.Service()("@shivaedev/contract/KeyScope") {
9
12
  }
10
- export class ChangesReads extends Context.Service()("@shivaedev/contract/ChangesReads") {
11
- }
12
- export function queryKeys(queries, requested) {
13
- return requested.flatMap((reference) => {
14
- const declared = queries.find((candidate) => candidate.name === reference.query);
15
- if (declared === undefined) {
16
- throw new Error(`Unknown query: ${reference.query}`);
17
- }
18
- return declared.reads(reference.payload);
13
+ function resolveQueries(queries, requested) {
14
+ return Effect.forEach(requested, (reference) => {
15
+ const query = queries.find((candidate) => candidate.name === reference.query);
16
+ return query === undefined ? Effect.die(new Error(`Unknown query: ${reference.query}`)) : Effect.succeed({ payload: reference.payload, query });
19
17
  });
20
18
  }
19
+ export const queryKeys = Effect.fnUntraced(function* (queries, requested, viewer = Option.none()) {
20
+ const resolved = yield* resolveQueries(queries, requested);
21
+ return resolved.flatMap(({ query, payload }) => query.reads(payload, viewer));
22
+ });
21
23
  function referenceSchemas(queries) {
22
24
  return Schema.Array(Schema.Union(queries.map((query) => Schema.Struct({ payload: query.payload, query: Schema.Literal(query.name) }))));
23
25
  }
24
26
  export function changesRpc(name, queries) {
25
- return Rpc.make(`${name}.$changes`, { payload: { queries: referenceSchemas(queries) }, stream: true, success: LiveHint }).annotate(ChangesReads, (requested) => queryKeys(queries, requested));
27
+ return Rpc.make(`${name}.$changes`, {
28
+ error: Schema.Union([MissingReadPolicy, QueryReadDenied]),
29
+ payload: { queries: referenceSchemas(queries) },
30
+ stream: true,
31
+ success: LiveHint,
32
+ }).annotate(ChangesQueries, { resolve: (references) => resolveQueries(queries, references) });
26
33
  }
27
34
  //# sourceMappingURL=serverMetadata.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"serverMetadata.js","sourceRoot":"","sources":["../src/serverMetadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGpC,MAAM,OAAO,UAAW,SAAQ,OAAO,CAAC,OAAO,EAAmC,CAAC,gCAAgC,CAAC;CAAG;AAEvH,MAAM,OAAO,mBAAoB,SAAQ,OAAO,CAAC,OAAO,EAAoD,CAC3G,yCAAyC,CACzC;CAAG;AAEJ,MAAM,OAAO,QAAS,SAAQ,OAAO,CAAC,OAAO,EAAuB,CAAC,8BAA8B,CAAC;CAAG;AAWvG,MAAM,OAAO,YAAa,SAAQ,OAAO,CAAC,OAAO,EAA2E,CAC3H,kCAAkC,CAClC;CAAG;AAEJ,MAAM,UAAU,SAAS,CAAC,OAA8B,EAAE,SAAoC;IAC7F,OAAO,SAAS,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE;QACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,SAAS,CAAC,KAAK,CAAC,CAAC;QACjF,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,kBAAkB,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACJ,CAAC;AAKD,SAAS,gBAAgB,CAAC,OAA8B;IACvD,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AACzI,CAAC;AAED,MAAM,UAAU,UAAU,CAA2E,IAAW,EAAE,OAAiB;IAClI,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,WAAW,EAAE,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,CACjI,YAAY,EACZ,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,CAC5C,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"serverMetadata.js","sourceRoot":"","sources":["../src/serverMetadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGpC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE1E,MAAM,OAAO,eAAgB,SAAQ,OAAO,CAAC,OAAO,EAAwD,CAAC,qCAAqC,CAAC;CAAG;AAKtJ,MAAM,OAAO,cAAe,SAAQ,OAAO,CAAC,OAAO,EAKhD,CAAC,oCAAoC,CAAC;CAAG;AAE5C,MAAM,OAAO,mBAAoB,SAAQ,OAAO,CAAC,OAAO,EAA8E,CACrI,yCAAyC,CACzC;CAAG;AAEJ,MAAM,OAAO,QAAS,SAAQ,OAAO,CAAC,OAAO,EAAuB,CAAC,8BAA8B,CAAC;CAAG;AAWvG,SAAS,cAAc,CAAC,OAA8B,EAAE,SAAoC;IAC3F,OAAO,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,SAAS,EAAE,EAAE;QAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,SAAS,CAAC,KAAK,CAAC,CAAC;QAC9E,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,kBAAkB,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IACjJ,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EACnD,OAA8B,EAC9B,SAAoC,EACpC,MAAM,GAA0B,MAAM,CAAC,IAAI,EAAE;IAE7C,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC3D,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AAC/E,CAAC,CAAC,CAAC;AAKH,SAAS,gBAAgB,CAAC,OAA8B;IACvD,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AACzI,CAAC;AAED,MAAM,UAAU,UAAU,CAA2E,IAAW,EAAE,OAAiB;IAClI,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,WAAW,EAAE;QACnC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC;QACzD,OAAO,EAAE,EAAE,OAAO,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAAE;QAC/C,MAAM,EAAE,IAAI;QACZ,OAAO,EAAE,QAAQ;KACjB,CAAC,CAAC,QAAQ,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC;AAC/F,CAAC"}
package/dist/testing.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type Duration, Effect, Stream, SubscriptionRef } from "effect";
2
- import type { Key } from "./keys.ts";
2
+ import type { Key } from "@shivaedev/changes/keys.ts";
3
3
  import type { LiveHint } from "./live.ts";
4
4
  export interface HintViewer<E> {
5
5
  readonly finished: Effect.Effect<void, E>;
@@ -8,6 +8,11 @@ export interface HintViewer<E> {
8
8
  export declare const watchHints: <E, R>(stream: Stream.Stream<{
9
9
  readonly _tag: "Changed";
10
10
  readonly keys: readonly ({
11
+ readonly _tag: "Bucket";
12
+ readonly collection: string;
13
+ readonly field: string;
14
+ readonly value: string | number;
15
+ } | {
11
16
  readonly _tag: "List";
12
17
  readonly collection: string;
13
18
  } | {
@@ -15,6 +20,7 @@ export declare const watchHints: <E, R>(stream: Stream.Stream<{
15
20
  readonly collection: string;
16
21
  readonly id: string | number;
17
22
  })[];
23
+ readonly queryIndexes?: readonly number[] | undefined;
18
24
  } | {
19
25
  readonly _tag: "Resync";
20
26
  }, E, R>) => Effect.Effect<{
@@ -22,6 +28,11 @@ export declare const watchHints: <E, R>(stream: Stream.Stream<{
22
28
  hints: SubscriptionRef.SubscriptionRef<readonly ({
23
29
  readonly _tag: "Changed";
24
30
  readonly keys: readonly ({
31
+ readonly _tag: "Bucket";
32
+ readonly collection: string;
33
+ readonly field: string;
34
+ readonly value: string | number;
35
+ } | {
25
36
  readonly _tag: "List";
26
37
  readonly collection: string;
27
38
  } | {
@@ -29,6 +40,7 @@ export declare const watchHints: <E, R>(stream: Stream.Stream<{
29
40
  readonly collection: string;
30
41
  readonly id: string | number;
31
42
  })[];
43
+ readonly queryIndexes?: readonly number[] | undefined;
32
44
  } | {
33
45
  readonly _tag: "Resync";
34
46
  })[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,EAAS,MAAM,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AACzF,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,WAAW,UAAU,CAAC,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC1C,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC,eAAe,CAAC,SAAS,QAAQ,EAAE,CAAC,CAAC;CACrE;AAED,eAAO,MAAM,UAAU,GAA2C,CAAC,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;uCAYpE,CAAC;AAEH,eAAO,MAAM,KAAK,GAAsC,CAAC;sBAG3B,QAAQ,CAAC,KAAK;kDAU1C,CAAC"}
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,EAAS,MAAM,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AACzF,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,WAAW,UAAU,CAAC,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC1C,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC,eAAe,CAAC,SAAS,QAAQ,EAAE,CAAC,CAAC;CACrE;AAED,eAAO,MAAM,UAAU,GAA2C,CAAC,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uCAYpE,CAAC;AAEH,eAAO,MAAM,KAAK,GAAsC,CAAC;sBAG3B,QAAQ,CAAC,KAAK;kDAU1C,CAAC"}
package/dist/testing.js CHANGED
@@ -12,9 +12,6 @@ export const heard = Effect.fn("Live.heard")(function* (viewer, keys, options =
12
12
  return yield* Effect.raceFirst(matching, Effect.as(viewer.finished, false)).pipe(Effect.timeoutOrElse({ duration: options.within ?? "1 second", orElse: () => Effect.succeed(false) }));
13
13
  });
14
14
  function namesKeys(hint, keys) {
15
- return (hint._tag === "Changed"
16
- && keys.every((key) => hint.keys.some((actual) => actual._tag === key._tag
17
- && actual.collection === key.collection
18
- && (key._tag === "List" || (actual._tag === "Item" && actual.id === key.id)))));
15
+ return hint._tag === "Changed" && keys.every((key) => hint.keys.some((actual) => JSON.stringify(actual) === JSON.stringify(key)));
19
16
  }
20
17
  //# sourceMappingURL=testing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAiB,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AASzF,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,EAAQ,MAAqC;IAC5G,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,eAAe,CAAC,IAAI,CAAsB,EAAE,CAAC,CAAC;IACnE,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAQ,CAAC;IAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAC/B,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,EAAE,CAC1B,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,GAAG,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,CACzH,EACD,MAAM,CAAC,UAAU,CACjB,CAAC;IACF,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnC,KAAK,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC;IACzD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAA0B,CAAC;AACpD,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,EACrD,MAAqB,EACrB,IAAoB,EACpB,OAAO,GAAyC,EAAE;IAElD,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAC1D,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EACrE,MAAM,CAAC,OAAO,EACd,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CACf,CAAC;IACF,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAC/E,MAAM,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,IAAI,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CACrG,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,SAAS,SAAS,CAAC,IAAc,EAAE,IAAoB;IACtD,OAAO,CACN,IAAI,CAAC,IAAI,KAAK,SAAS;WACpB,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CACrB,IAAI,CAAC,IAAI,CAAC,IAAI,CACb,CAAC,MAAM,EAAE,EAAE,CACV,MAAM,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI;eACrB,MAAM,CAAC,UAAU,KAAK,GAAG,CAAC,UAAU;eACpC,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,IAAI,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAC7E,CACD,CACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAiB,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AASzF,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,EAAQ,MAAqC;IAC5G,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,eAAe,CAAC,IAAI,CAAsB,EAAE,CAAC,CAAC;IACnE,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAQ,CAAC;IAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAC/B,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,EAAE,CAC1B,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,GAAG,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,CACzH,EACD,MAAM,CAAC,UAAU,CACjB,CAAC;IACF,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnC,KAAK,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC;IACzD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAA0B,CAAC;AACpD,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,EACrD,MAAqB,EACrB,IAAoB,EACpB,OAAO,GAAyC,EAAE;IAElD,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAC1D,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EACrE,MAAM,CAAC,OAAO,EACd,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CACf,CAAC;IACF,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAC/E,MAAM,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,IAAI,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CACrG,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,SAAS,SAAS,CAAC,IAAc,EAAE,IAAoB;IACtD,OAAO,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACnI,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/contract",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Typed query and command contracts over native Effect RPC and reactivity",
5
5
  "keywords": [
6
6
  "effect"
@@ -44,19 +44,20 @@
44
44
  "README.md"
45
45
  ],
46
46
  "dependencies": {
47
+ "@shivaedev/changes": "0.5.0",
47
48
  "@shivaedev/types": "0.3.0"
48
49
  },
49
50
  "devDependencies": {
50
51
  "@effect/platform-node": "4.0.5",
51
52
  "@effect/vitest": "4.0.5",
52
- "@shivaedev/quality": "0.42.0",
53
+ "@shivaedev/quality": "0.43.0",
53
54
  "@shivaedev/test": "0.10.0",
54
- "@shivaedev/test-sql": "0.10.1",
55
- "@shivaedev/test-story": "0.21.1",
55
+ "@shivaedev/test-sql": "0.10.2",
56
+ "@shivaedev/test-story": "0.22.0",
56
57
  "@types/node": "24.19.2",
57
58
  "effect": "4.0.5",
58
59
  "esbuild": "0.28.2",
59
- "happy-dom": "21.0.3",
60
+ "happy-dom": "21.0.4",
60
61
  "typescript": "7.0.2",
61
62
  "vitest": "5.0.5"
62
63
  },
@@ -2,8 +2,9 @@ import { type Duration, Effect, Stream, SubscriptionRef } from "effect";
2
2
  import * as Atom from "effect/reactivity/Atom";
3
3
  import * as AtomRegistry from "effect/reactivity/AtomRegistry";
4
4
  import type * as AtomRpc from "effect/reactivity/AtomRpc";
5
+ import { type Key, readKeys } from "@shivaedev/changes/keys.ts";
5
6
  import type { BindingOwner } from "#internal/bindingOwner.ts";
6
- import { type Key, readKeys } from "#keys.ts";
7
+ import { localQueryKeys, queryHint } from "#internal/queryHints.ts";
7
8
  import { type LiveStatus, live } from "#live.ts";
8
9
  import { type ResumeSource, type ResumeWindow, resumeSignal } from "#resume.ts";
9
10
  import type { changesRpc, QueryReference } from "#serverMetadata.ts";
@@ -68,13 +69,17 @@ export function automaticLive(service: ChangesClient, name: string, owner?: Bind
68
69
  return Stream.never;
69
70
  }
70
71
  const queries = [...current.values()].map(({ reference }) => reference);
71
- return Stream.unwrap(service.use((client) => Effect.succeed(client(`${name}.$changes`, { queries }))));
72
+ return Stream.unwrap(
73
+ service.use((client) =>
74
+ Effect.succeed(client(`${name}.$changes`, { queries }).pipe(Stream.map((hint) => queryHint(hint, [...current.values()])))),
75
+ ),
76
+ );
72
77
  }
73
78
  const signal = resumeSignal({ ...(typeof window === "undefined" ? {} : { window }), ...(resume === undefined ? {} : { native: resume }) });
74
79
  const subscription = live(service.runtime, {
75
80
  keyContext: owner?.context,
76
81
  resume: signal,
77
- resyncKeys: (registry) => [...Effect.runSync(SubscriptionRef.get(registry.get(tracked))).values()].flatMap(({ keys }) => keys),
82
+ resyncKeys: (registry) => [...Effect.runSync(SubscriptionRef.get(registry.get(tracked))).values()].flatMap(({ keys }) => localQueryKeys(keys)),
78
83
  stream: Stream.unwrap(
79
84
  Effect.map(AtomRegistry.AtomRegistry, (registry) =>
80
85
  Stream.zipLatest(SubscriptionRef.changes(registry.get(tracked)), AtomRegistry.toStream(registry, signal)).pipe(
package/src/bind.ts CHANGED
@@ -1,17 +1,20 @@
1
- import type { Duration, Effect, Stream } from "effect";
1
+ import type { Duration, Effect, Layer, Stream } from "effect";
2
2
  import type { Headers } from "effect/http";
3
3
  import type * as AsyncResult from "effect/reactivity/AsyncResult";
4
4
  import type * as Atom from "effect/reactivity/Atom";
5
5
  import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
6
6
  import type * as AtomRpc from "effect/reactivity/AtomRpc";
7
7
  import type * as Reactivity from "effect/reactivity/Reactivity";
8
- import type { Rpc } from "effect/rpc";
8
+ import type { Rpc, RpcGroup } from "effect/rpc";
9
9
  import type { RpcClientError } from "effect/rpc/RpcClientError";
10
10
  import { automaticLive, type LiveBound, LiveConnection } from "#automaticLive.ts";
11
+ import type { ClientOptions, MadeClient } from "#clientOptions.ts";
11
12
  import type { Contract, Declared, Tag } from "#contract.ts";
12
13
  import { bindingOwner } from "#internal/bindingOwner.ts";
13
14
  import { boundCommand, boundQuery, boundStream } from "#internal/clientOperations.ts";
15
+ import { type HttpGroup, httpClient } from "#internal/httpClient.ts";
14
16
  import type { CommandShape, QueryShape, StreamShape } from "#operation.ts";
17
+ import type { MissingReadPolicy } from "#readPolicyErrors.ts";
15
18
  import type { ResumeSource } from "#resume.ts";
16
19
 
17
20
  export type Failure<R extends Rpc.Any> =
@@ -31,7 +34,10 @@ export interface QueryOptions {
31
34
  }
32
35
 
33
36
  export interface BoundQuery<R extends Rpc.Any, TSelf> {
34
- readonly query: (payload: Rpc.Payload<R>, options?: QueryOptions) => Atom.Atom<AsyncResult.AsyncResult<Rpc.Success<R>, Failure<R>>>;
37
+ readonly query: (
38
+ payload: Rpc.Payload<R>,
39
+ options?: QueryOptions,
40
+ ) => Atom.Atom<AsyncResult.AsyncResult<Rpc.Success<R>, Failure<R> | MissingReadPolicy>>;
35
41
  readonly run: (payload: Rpc.Payload<R>) => Effect.Effect<Rpc.Success<R>, RunFailure<R>, TSelf>;
36
42
  }
37
43
 
@@ -89,6 +95,14 @@ export function bind(
89
95
  service: AtomRpc.AtomRpcClient<unknown, string, Rpc.Any>,
90
96
  options?: BindOptions,
91
97
  ): unknown {
98
+ return bindApi(contract, service, options);
99
+ }
100
+
101
+ function bindApi(
102
+ contract: { readonly declaration: Declared<string, readonly QueryShape[], readonly CommandShape[], readonly StreamShape[], "session" | undefined> },
103
+ service: AtomRpc.AtomRpcClient<unknown, string, Rpc.Any>,
104
+ options?: BindOptions,
105
+ ) {
92
106
  const { name, queries, commands, streams, keyScope } = contract.declaration;
93
107
  const owner = bindingOwner(keyScope, options?.registry);
94
108
  const automatic = automaticLive(service, name, owner, options?.resume);
@@ -101,3 +115,31 @@ export function bind(
101
115
  { [LiveConnection]: automatic.subscription },
102
116
  );
103
117
  }
118
+
119
+ export function makeClient<
120
+ TName extends string,
121
+ TQueries extends readonly QueryShape[],
122
+ TCommands extends readonly CommandShape[],
123
+ TRpcs extends Rpc.Any,
124
+ TStreams extends readonly StreamShape[],
125
+ TScope extends "session" | undefined,
126
+ TExtra extends Rpc.Any = never,
127
+ >(
128
+ contract: Contract<TName, TQueries, TCommands, TRpcs, TStreams, TScope>,
129
+ options: ClientOptions & { readonly group?: RpcGroup.RpcGroup<TRpcs | TExtra> } & ("session" extends TScope
130
+ ? { readonly registry: AtomRegistry.AtomRegistry }
131
+ : { readonly registry?: AtomRegistry.AtomRegistry }),
132
+ ): MadeClient<TName, TQueries, TCommands, TRpcs, TStreams, TExtra>;
133
+ export function makeClient(
134
+ contract: HttpGroup & {
135
+ readonly declaration: Declared<string, readonly QueryShape[], readonly CommandShape[], readonly StreamShape[], "session" | undefined>;
136
+ },
137
+ options: ClientOptions & {
138
+ readonly group?: HttpGroup;
139
+ readonly registry?: AtomRegistry.AtomRegistry;
140
+ },
141
+ ): Layer.Layer<never> {
142
+ const connection = httpClient(contract.declaration.name, options.group ?? contract, options);
143
+ const api = bindApi(contract, connection.Client, options);
144
+ return Object.assign(connection, { api });
145
+ }
@@ -0,0 +1,35 @@
1
+ import type { Context, Effect, Layer, Scope } from "effect";
2
+ import type { HttpClient } from "effect/http/HttpClient";
3
+ import type * as Atom from "effect/reactivity/Atom";
4
+ import type * as AtomRpc from "effect/reactivity/AtomRpc";
5
+ import type { Rpc, RpcClient, RpcSerialization } from "effect/rpc";
6
+ import type { RpcClientError } from "effect/rpc/RpcClientError";
7
+ import type { LiveBound } from "#automaticLive.ts";
8
+ import type { Bound } from "#bind.ts";
9
+ import type { CommandShape, QueryShape, StreamShape } from "#operation.ts";
10
+
11
+ export interface ClientOptions {
12
+ readonly httpClient?: Layer.Layer<HttpClient>;
13
+ readonly serialization?: Layer.Layer<RpcSerialization.RpcSerialization>;
14
+ readonly transformClient?: NonNullable<Parameters<typeof RpcClient.layerProtocolHttp>[0]["transformClient"]>;
15
+ readonly url: string;
16
+ }
17
+
18
+ export type ClientService<TName extends string, TRpcs extends Rpc.Any> = Context.ServiceClass.Shape<
19
+ `${TName}/Client`,
20
+ RpcClient.RpcClient.Flat<TRpcs, RpcClientError>
21
+ >;
22
+
23
+ export type MadeClient<
24
+ TName extends string,
25
+ TQueries extends readonly QueryShape[],
26
+ TCommands extends readonly CommandShape[],
27
+ TRpcs extends Rpc.Any,
28
+ TStreams extends readonly StreamShape[],
29
+ TExtra extends Rpc.Any,
30
+ > = Layer.Layer<ClientService<TName, TRpcs | TExtra>> & {
31
+ readonly api: Bound<TName, TQueries, TCommands, TRpcs, ClientService<TName, TRpcs | TExtra>, TStreams> & LiveBound;
32
+ readonly Client: AtomRpc.AtomRpcClient<ClientService<TName, TRpcs | TExtra>, `${TName}/Client`, TRpcs | TExtra>;
33
+ readonly client: Effect.Effect<RpcClient.RpcClient<TRpcs | TExtra, RpcClientError>, never, Scope.Scope | Rpc.MiddlewareClient<TRpcs | TExtra>>;
34
+ readonly runtime: Atom.AtomRuntime<ClientService<TName, TRpcs | TExtra>>;
35
+ };
package/src/contract.ts CHANGED
@@ -1,14 +1,19 @@
1
- import { Context, type Schema } from "effect";
1
+ import { Context, Schema } from "effect";
2
2
  import { Rpc, RpcGroup, type RpcMiddleware, type RpcSchema } from "effect/rpc";
3
3
  import type { CommandShape, OperationShape, QueryShape, StreamShape } from "#operation.ts";
4
- import { CommandInvalidation, changesRpc, KeyScope, QueryReads } from "#serverMetadata.ts";
4
+ import { QueryReadDenied } from "#readPolicyErrors.ts";
5
+ import { CommandInvalidation, changesRpc, KeyScope, QueryDefinition } from "#serverMetadata.ts";
5
6
 
6
7
  export type Tag<TContract extends string, TName extends string> = `${TContract}.${TName}`;
7
8
 
9
+ type OperationError<TOperation extends OperationShape> = TOperation extends QueryShape
10
+ ? Schema.Union<readonly [TOperation["error"], typeof QueryReadDenied]>
11
+ : TOperation["error"];
12
+
8
13
  export type OperationRpc<TContract extends string, TOperation> = TOperation extends OperationShape
9
14
  ? TOperation extends StreamShape
10
15
  ? Rpc.Rpc<Tag<TContract, TOperation["name"]>, TOperation["payload"], RpcSchema.Stream<TOperation["success"], TOperation["error"]>, Schema.Never>
11
- : Rpc.Rpc<Tag<TContract, TOperation["name"]>, TOperation["payload"], TOperation["success"], TOperation["error"]>
16
+ : Rpc.Rpc<Tag<TContract, TOperation["name"]>, TOperation["payload"], TOperation["success"], OperationError<TOperation>>
12
17
  : never;
13
18
 
14
19
  export interface Declared<
@@ -61,9 +66,9 @@ function assertUnique(operations: readonly OperationShape[]) {
61
66
  function annotations(operation: QueryShape | CommandShape | StreamShape, keyScope: "session" | undefined) {
62
67
  const scope = keyScope === undefined ? Context.empty() : Context.make(KeyScope, keyScope);
63
68
  if (operation.kind === "command") {
64
- return Context.add(scope, CommandInvalidation, operation.invalidates);
69
+ return Context.add(scope, CommandInvalidation, { invalidates: operation.invalidates });
65
70
  }
66
- return operation.kind === "query" ? Context.add(scope, QueryReads, operation.reads) : scope;
71
+ return operation.kind === "query" ? Context.add(scope, QueryDefinition, { definition: operation }) : scope;
67
72
  }
68
73
 
69
74
  type AnyDeclared = Declared<string, readonly QueryShape[], readonly CommandShape[], readonly StreamShape[], "session" | undefined>;
@@ -122,7 +127,7 @@ export function contract(
122
127
  assertUnique([...queries, ...commands, ...streams]);
123
128
  const rpcs = [...queries, ...commands, ...streams].map((operation) =>
124
129
  Rpc.make(`${name}.${operation.name}`, {
125
- error: operation.error,
130
+ error: operation.kind === "query" ? Schema.Union([operation.error, QueryReadDenied]) : operation.error,
126
131
  payload: operation.payload,
127
132
  stream: operation.kind === "stream",
128
133
  success: operation.success,
@@ -1,5 +1,5 @@
1
1
  import type * as AtomRegistry from "effect/reactivity/AtomRegistry";
2
- import type { KeyContext } from "#keys.ts";
2
+ import type { KeyContext } from "@shivaedev/changes/keys.ts";
3
3
 
4
4
  export interface BindingOwner {
5
5
  readonly context: KeyContext;