@shivaedev/contract 0.18.0 → 0.19.1

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 +33 -0
  2. package/README.md +168 -178
  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 +28 -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 +32 -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
package/src/operation.ts CHANGED
@@ -1,7 +1,9 @@
1
- import { Schema } from "effect";
1
+ import type { Option, Schema } from "effect";
2
+ import type { Key } from "@shivaedev/changes/keys.ts";
2
3
  import type { Bivariant } from "@shivaedev/types/Bivariant.ts";
3
- import type { Key } from "./keys.ts";
4
- import { type MatchingTags, type Reject, type RejectionSpecs, type Rejections, type RejectionUnion, rejectionSet } from "./rejection.ts";
4
+ import { type Loose, operation } from "#internal/operationDeclaration.ts";
5
+ import type { QueryReadPolicy, Viewer } from "#readPolicy.ts";
6
+ import type { MatchingTags, Reject, RejectionSpecs, Rejections, RejectionUnion } from "./rejection.ts";
5
7
 
6
8
  export type PayloadSchema<TPayload extends Schema.Top | Schema.Struct.Fields> = TPayload extends Schema.Struct.Fields
7
9
  ? Schema.Struct<TPayload>
@@ -22,7 +24,8 @@ export interface OperationShape {
22
24
 
23
25
  export interface QueryShape extends OperationShape {
24
26
  readonly kind: "query";
25
- reads: Bivariant<(payload: unknown) => readonly Key[]>;
27
+ readonly readPolicy?: Bivariant<QueryReadPolicy<unknown>>;
28
+ reads: Bivariant<(payload: unknown, viewer: Option.Option<Viewer>) => readonly Key[]>;
26
29
  }
27
30
 
28
31
  export interface StreamShape extends OperationShape {
@@ -31,7 +34,7 @@ export interface StreamShape extends OperationShape {
31
34
  }
32
35
 
33
36
  export interface CommandShape extends OperationShape {
34
- invalidates: Bivariant<(payload: unknown, result: unknown) => readonly Key[]>;
37
+ invalidates: Bivariant<(payload: unknown, result: unknown, viewer?: Option.Option<Viewer>) => readonly Key[]>;
35
38
  readonly kind: "command";
36
39
  }
37
40
 
@@ -49,7 +52,8 @@ interface Operation<TName extends string, TPayload extends Schema.Top, TSuccess
49
52
  export interface Query<TName extends string, TPayload extends Schema.Top, TSuccess extends Schema.Top, TSpecs extends RejectionSpecs>
50
53
  extends Operation<TName, TPayload, TSuccess, TSpecs> {
51
54
  readonly kind: "query";
52
- reads: (payload: TPayload["Type"]) => readonly Key[];
55
+ readonly readPolicy?: QueryReadPolicy<TPayload["Type"]>;
56
+ reads: (payload: TPayload["Type"], viewer: Option.Option<Viewer>) => readonly Key[];
53
57
  }
54
58
 
55
59
  export interface Streaming<TName extends string, TPayload extends Schema.Top, TSuccess extends Schema.Top, TSpecs extends RejectionSpecs>
@@ -60,7 +64,7 @@ export interface Streaming<TName extends string, TPayload extends Schema.Top, TS
60
64
 
61
65
  export interface Command<TName extends string, TPayload extends Schema.Top, TSuccess extends Schema.Top, TSpecs extends RejectionSpecs>
62
66
  extends Operation<TName, TPayload, TSuccess, TSpecs> {
63
- invalidates: (payload: TPayload["Type"], result: TSuccess["Type"]) => readonly Key[];
67
+ invalidates: (payload: TPayload["Type"], result: TSuccess["Type"], viewer?: Option.Option<Viewer>) => readonly Key[];
64
68
  readonly kind: "command";
65
69
  }
66
70
 
@@ -70,31 +74,6 @@ interface Declaration<TPayload extends Schema.Top | Schema.Struct.Fields, TSucce
70
74
  readonly success?: TSuccess;
71
75
  }
72
76
 
73
- interface Loose {
74
- readonly payload?: Schema.Top | Schema.Struct.Fields;
75
- readonly rejections?: RejectionSpecs;
76
- readonly success?: Schema.Top;
77
- }
78
-
79
- function payloadSchema(payload: Loose["payload"]): Schema.Top {
80
- if (payload === undefined) {
81
- return Schema.Void;
82
- }
83
- return Schema.isSchema(payload) ? payload : Schema.Struct(payload);
84
- }
85
-
86
- function operation<TKind extends OperationShape["kind"]>(kind: TKind, name: string, declaration: Loose) {
87
- const { payload, success, rejections = {} } = declaration;
88
- return {
89
- kind,
90
- name,
91
- payload: payloadSchema(payload),
92
- rejections,
93
- success: success ?? Schema.Void,
94
- ...rejectionSet(rejections),
95
- };
96
- }
97
-
98
77
  export function query<
99
78
  const TName extends string,
100
79
  TPayload extends Schema.Top | Schema.Struct.Fields = Schema.Void,
@@ -103,11 +82,22 @@ export function query<
103
82
  >(
104
83
  name: TName,
105
84
  declaration: Declaration<TPayload, TSuccess, TSpecs> & {
106
- readonly reads: (payload: PayloadSchema<TPayload>["Type"]) => readonly Key[];
85
+ readonly reads: (payload: PayloadSchema<TPayload>["Type"], viewer: Option.Option<Viewer>) => readonly Key[];
86
+ readonly readPolicy?: QueryReadPolicy<PayloadSchema<TPayload>["Type"]>;
107
87
  },
108
88
  ): NoInfer<Query<TName, PayloadSchema<TPayload>, TSuccess, TSpecs>>;
109
- export function query(name: string, declaration: Loose & { readonly reads: (payload: unknown) => readonly Key[] }): QueryShape {
110
- return { ...operation("query", name, declaration), reads: declaration.reads };
89
+ export function query(
90
+ name: string,
91
+ declaration: Loose & {
92
+ readonly reads: (payload: unknown, viewer: Option.Option<Viewer>) => readonly Key[];
93
+ readonly readPolicy?: QueryReadPolicy<unknown>;
94
+ },
95
+ ): QueryShape {
96
+ return {
97
+ ...operation("query", name, declaration),
98
+ reads: declaration.reads,
99
+ ...(declaration.readPolicy === undefined ? {} : { readPolicy: declaration.readPolicy }),
100
+ };
111
101
  }
112
102
 
113
103
  export function command<
@@ -118,12 +108,12 @@ export function command<
118
108
  >(
119
109
  name: TName,
120
110
  declaration: Declaration<TPayload, TSuccess, TSpecs> & {
121
- readonly invalidates: (payload: PayloadSchema<TPayload>["Type"], result: TSuccess["Type"]) => readonly Key[];
111
+ readonly invalidates: (payload: PayloadSchema<TPayload>["Type"], result: TSuccess["Type"], viewer?: Option.Option<Viewer>) => readonly Key[];
122
112
  },
123
113
  ): NoInfer<Command<TName, PayloadSchema<TPayload>, TSuccess, TSpecs>>;
124
114
  export function command(
125
115
  name: string,
126
- declaration: Loose & { readonly invalidates: (payload: unknown, result: unknown) => readonly Key[] },
116
+ declaration: Loose & { readonly invalidates: (payload: unknown, result: unknown, viewer?: Option.Option<Viewer>) => readonly Key[] },
127
117
  ): CommandShape {
128
118
  return { ...operation("command", name, declaration), invalidates: declaration.invalidates };
129
119
  }
@@ -1,9 +1,9 @@
1
- import type { Schema } from "effect";
1
+ import { Effect, type Schema } from "effect";
2
2
  import { contract } from "#contract.ts";
3
3
  import { query } from "#operation.ts";
4
4
 
5
5
  export function publicConfig<T, E>(schema: Schema.Codec<T, E>) {
6
6
  return contract("platform.publicConfig", {
7
- queries: [query("get", { reads: () => [], success: schema })],
7
+ queries: [query("get", { readPolicy: () => Effect.succeed(true), reads: () => [], success: schema })],
8
8
  });
9
9
  }
@@ -0,0 +1,21 @@
1
+ import { Context, Effect, type Option } from "effect";
2
+ import type { Key } from "@shivaedev/changes/keys.ts";
3
+ import { checkQuery, type Viewer } from "#readPolicy.ts";
4
+ import type { QueryRequest } from "#serverMetadata.ts";
5
+
6
+ export interface QuerySubscription {
7
+ readonly authorized: Effect.Effect<boolean>;
8
+ readonly keys: readonly Key[];
9
+ readonly scopes: readonly (readonly Key[])[];
10
+ }
11
+
12
+ export const querySubscription = Effect.fnUntraced(function* (queries: readonly QueryRequest[], viewer: Option.Option<Viewer>) {
13
+ yield* Effect.forEach(queries, ({ query, payload }) => checkQuery(query, payload, viewer));
14
+ const context = Context.makeUnsafe<unknown>((yield* Effect.context<never>()).mapUnsafe);
15
+ const scopes = queries.map(({ query, payload }) => query.reads(payload, viewer));
16
+ const authorized = Effect.map(
17
+ Effect.forEach(queries, ({ query, payload }) => Effect.provideContext(query.readPolicy?.(payload, viewer) ?? Effect.succeed(false), context)),
18
+ (permissions) => permissions.every(Boolean),
19
+ );
20
+ return { authorized, keys: scopes.flat(), scopes } satisfies QuerySubscription;
21
+ });
package/src/readPolicy.ts CHANGED
@@ -1,33 +1,33 @@
1
- import { Context, type Duration, Effect, Option } 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
- import { QueryReads } from "./serverMetadata.ts";
3
+ import type { QueryShape } from "#operation.ts";
4
+ import { MissingReadPolicy, QueryReadDenied } from "#readPolicyErrors.ts";
5
+ import { QueryDefinition } from "#serverMetadata.ts";
5
6
 
6
- export const ReadPolicy = Context.Reference<(key: Key) => Effect.Effect<boolean, unknown>>("@shivaedev/contract/ReadPolicy", {
7
- defaultValue: () => () => Effect.succeed(true),
8
- });
7
+ export interface Viewer {
8
+ readonly id: string;
9
+ }
10
+ export type QueryReadPolicy<TPayload> = (payload: TPayload, viewer: Option.Option<Viewer>) => Effect.Effect<boolean, never, unknown>;
9
11
 
10
- export const ReadPolicyMemoWindow = Context.Reference<Duration.Input>("@shivaedev/contract/ReadPolicyMemoWindow", {
11
- defaultValue: () => "1 second",
12
+ export const CurrentViewer = Context.Reference<Option.Option<Viewer>>("@shivaedev/contract/CurrentViewer", { defaultValue: Option.none });
13
+ export const checkQuery = Effect.fnUntraced(function* (query: QueryShape, payload: unknown, viewer: Option.Option<Viewer>) {
14
+ if (query.readPolicy === undefined) {
15
+ return yield* new MissingReadPolicy({ message: `Declare a readPolicy on query ${query.name} before running it live or subscribing` });
16
+ }
17
+ const context = Context.makeUnsafe<unknown>((yield* Effect.context<never>()).mapUnsafe);
18
+ if (!(yield* Effect.provideContext(query.readPolicy(payload, viewer), context))) {
19
+ return yield* new QueryReadDenied({ message: `Read not allowed for query ${query.name}` });
20
+ }
12
21
  });
13
22
 
14
- export const authorizeReads = Effect.fnUntraced(function* <A, E, R, TPolicyServices, TDenied, TPolicyError>(
23
+ export const authorizeReads = Effect.fnUntraced(function* <A, E, R>(
15
24
  effect: Effect.Effect<A, E, R>,
16
25
  request: { readonly rpc: Rpc.AnyWithProps; readonly payload: unknown },
17
- policy: (key: Key) => Effect.Effect<boolean, TPolicyError, TPolicyServices>,
18
- denied: Effect.Effect<never, TDenied>,
26
+ viewer: Option.Option<Viewer>,
19
27
  ) {
20
- const context = yield* Effect.context<TPolicyServices>();
21
- function canRead(key: Key) {
22
- return Effect.provideContext(policy(key), context);
23
- }
24
- const reads = Context.getOption(request.rpc.annotations, QueryReads);
25
- if (Option.isSome(reads)) {
26
- for (const key of reads.value(request.payload)) {
27
- if (!(yield* canRead(key))) {
28
- return yield* denied;
29
- }
30
- }
28
+ const definition = Context.getOption(request.rpc.annotations, QueryDefinition);
29
+ if (Option.isSome(definition) && definition.value.definition.readPolicy !== undefined) {
30
+ yield* checkQuery(definition.value.definition, request.payload, viewer);
31
31
  }
32
- return yield* Effect.provideService(effect, ReadPolicy, canRead);
32
+ return yield* Effect.provideService(effect, CurrentViewer, viewer);
33
33
  });
@@ -0,0 +1,4 @@
1
+ import { Schema } from "effect";
2
+
3
+ export class MissingReadPolicy extends Schema.TaggedError<MissingReadPolicy>()("MissingReadPolicy", { message: Schema.String }) {}
4
+ export class QueryReadDenied extends Schema.TaggedError<QueryReadDenied>()("QueryReadDenied", { message: Schema.String }) {}
@@ -1,12 +1,24 @@
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";
3
+ import type { Key } from "@shivaedev/changes/keys.ts";
4
4
  import { LiveHint } from "#live.ts";
5
5
  import type { CommandShape, QueryShape } from "#operation.ts";
6
+ import type { Viewer } from "#readPolicy.ts";
7
+ import { MissingReadPolicy, QueryReadDenied } from "#readPolicyErrors.ts";
6
8
 
7
- export class QueryReads extends Context.Service<QueryReads, QueryShape["reads"]>()("@shivaedev/contract/QueryReads") {}
9
+ export class QueryDefinition extends Context.Service<QueryDefinition, { readonly definition: QueryShape }>()("@shivaedev/contract/QueryDefinition") {}
10
+ export interface QueryRequest {
11
+ readonly payload: unknown;
12
+ readonly query: QueryShape;
13
+ }
14
+ export class ChangesQueries extends Context.Service<
15
+ ChangesQueries,
16
+ {
17
+ readonly resolve: (references: readonly QueryReference[]) => Effect.Effect<readonly QueryRequest[]>;
18
+ }
19
+ >()("@shivaedev/contract/ChangesQueries") {}
8
20
 
9
- export class CommandInvalidation extends Context.Service<CommandInvalidation, CommandShape["invalidates"]>()(
21
+ export class CommandInvalidation extends Context.Service<CommandInvalidation, { readonly invalidates: CommandShape["invalidates"] }>()(
10
22
  "@shivaedev/contract/CommandInvalidation",
11
23
  ) {}
12
24
 
@@ -21,20 +33,22 @@ type ReferenceSchema<TQuery extends QueryShape> = TQuery extends QueryShape
21
33
  ? Schema.Struct<{ query: Schema.Literal<TQuery["name"]>; payload: TQuery["payload"] }>
22
34
  : never;
23
35
 
24
- export class ChangesReads extends Context.Service<ChangesReads, (references: readonly QueryReference[]) => readonly Key[]>()(
25
- "@shivaedev/contract/ChangesReads",
26
- ) {}
27
-
28
- export function queryKeys(queries: readonly QueryShape[], requested: readonly QueryReference[]): readonly Key[] {
29
- return requested.flatMap((reference) => {
30
- const declared = queries.find((candidate) => candidate.name === reference.query);
31
- if (declared === undefined) {
32
- throw new Error(`Unknown query: ${reference.query}`);
33
- }
34
- return declared.reads(reference.payload);
36
+ function resolveQueries(queries: readonly QueryShape[], requested: readonly QueryReference[]) {
37
+ return Effect.forEach(requested, (reference) => {
38
+ const query = queries.find((candidate) => candidate.name === reference.query);
39
+ return query === undefined ? Effect.die(new Error(`Unknown query: ${reference.query}`)) : Effect.succeed({ payload: reference.payload, query });
35
40
  });
36
41
  }
37
42
 
43
+ export const queryKeys = Effect.fnUntraced(function* (
44
+ queries: readonly QueryShape[],
45
+ requested: readonly QueryReference[],
46
+ viewer: Option.Option<Viewer> = Option.none(),
47
+ ): Effect.fn.Return<readonly Key[]> {
48
+ const resolved = yield* resolveQueries(queries, requested);
49
+ return resolved.flatMap(({ query, payload }) => query.reads(payload, viewer));
50
+ });
51
+
38
52
  function referenceSchemas<const TQueries extends readonly QueryShape[]>(
39
53
  queries: TQueries,
40
54
  ): Schema.$Array<Schema.Union<readonly ReferenceSchema<TQueries[number]>[]>>;
@@ -43,8 +57,10 @@ function referenceSchemas(queries: readonly QueryShape[]) {
43
57
  }
44
58
 
45
59
  export function changesRpc<const TName extends string, const TQueries extends readonly QueryShape[]>(name: TName, queries: TQueries) {
46
- return Rpc.make(`${name}.$changes`, { payload: { queries: referenceSchemas(queries) }, stream: true, success: LiveHint }).annotate(
47
- ChangesReads,
48
- (requested) => queryKeys(queries, requested),
49
- );
60
+ return Rpc.make(`${name}.$changes`, {
61
+ error: Schema.Union([MissingReadPolicy, QueryReadDenied]),
62
+ payload: { queries: referenceSchemas(queries) },
63
+ stream: true,
64
+ success: LiveHint,
65
+ }).annotate(ChangesQueries, { resolve: (references) => resolveQueries(queries, references) });
50
66
  }
package/src/testing.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Deferred, type Duration, Effect, Fiber, 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
 
5
5
  export interface HintViewer<E> {
@@ -37,15 +37,5 @@ export const heard = Effect.fn("Live.heard")(function* <E>(
37
37
  });
38
38
 
39
39
  function namesKeys(hint: LiveHint, keys: readonly Key[]): boolean {
40
- return (
41
- hint._tag === "Changed"
42
- && keys.every((key) =>
43
- hint.keys.some(
44
- (actual) =>
45
- actual._tag === key._tag
46
- && actual.collection === key.collection
47
- && (key._tag === "List" || (actual._tag === "Item" && actual.id === key.id)),
48
- ),
49
- )
50
- );
40
+ return hint._tag === "Changed" && keys.every((key) => hint.keys.some((actual) => JSON.stringify(actual) === JSON.stringify(key)));
51
41
  }
@@ -1,4 +0,0 @@
1
- import { Effect } from "effect";
2
- import type { Key } from "#keys.ts";
3
- export declare const makeHintPolicy: (capacity: number, retry: (keys: readonly Key[]) => Effect.Effect<void>) => Effect.Effect<(keys: readonly Key[]) => Effect.Effect<Key[], never, never>, never, import("effect/Scope").Scope>;
4
- //# sourceMappingURL=makeHintPolicy.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"makeHintPolicy.d.ts","sourceRoot":"","sources":["../../src/internal/makeHintPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,MAAM,EAAe,MAAM,QAAQ,CAAC;AAC1D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAGpC,eAAO,MAAM,cAAc,mCAAgE,SAAS,GAAG,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,0BAqBlH,SAAS,GAAG,EAAE,6EAc3B,CAAC"}
@@ -1,25 +0,0 @@
1
- import { Cache, Data, Effect, Exit, Queue } from "effect";
2
- import { ReadPolicy, ReadPolicyMemoWindow } from "#readPolicy.ts";
3
- export const makeHintPolicy = Effect.fnUntraced(function* (capacity, retry) {
4
- const policy = yield* ReadPolicy;
5
- const window = yield* ReadPolicyMemoWindow;
6
- const cache = yield* Cache.makeWith(policy, {
7
- capacity: Math.max(1, capacity),
8
- timeToLive: (exit) => (Exit.isSuccess(exit) ? window : 0),
9
- });
10
- const failed = new Map();
11
- const wake = yield* Queue.unbounded();
12
- yield* Queue.take(wake).pipe(Effect.andThen(Effect.sleep("1 second")), Effect.andThen(Effect.suspend(() => {
13
- const keys = [...failed.values()];
14
- failed.clear();
15
- return retry(keys);
16
- })), Effect.forever, Effect.forkScoped);
17
- return (keys) => Effect.filter(keys, (key) => Cache.get(cache, new Data.Class(key)).pipe(Effect.catch(() => Effect.gen(function* () {
18
- if (failed.size === 0) {
19
- yield* Queue.offer(wake, undefined);
20
- }
21
- failed.set(JSON.stringify(key), key);
22
- return false;
23
- }))));
24
- });
25
- //# sourceMappingURL=makeHintPolicy.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"makeHintPolicy.js","sourceRoot":"","sources":["../../src/internal/makeHintPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE1D,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAElE,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,QAAgB,EAAE,KAAoD;IAChI,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC;IACjC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,oBAAoB,CAAC;IAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE;QAC3C,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC;QAC/B,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;KACzD,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,IAAI,GAAG,EAAe,CAAC;IACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,SAAS,EAAQ,CAAC;IAC5C,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAC3B,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,EACxC,MAAM,CAAC,OAAO,CACb,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;QACnB,MAAM,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAClC,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC,CAAC,CACF,EACD,MAAM,CAAC,OAAO,EACd,MAAM,CAAC,UAAU,CACjB,CAAC;IACF,OAAO,CAAC,IAAoB,EAAE,EAAE,CAC/B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CAC3B,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,IAAI,CAAC,KAAK,CAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAC9C,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CACjB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACrC,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;QACrC,OAAO,KAAK,CAAC;IACd,CAAC,CAAC,CACF,CACD,CACD,CAAC;AACJ,CAAC,CAAC,CAAC"}
package/dist/keys.d.ts DELETED
@@ -1,34 +0,0 @@
1
- import { Schema } from "effect";
2
- export type Identity = string | number;
3
- export interface ListKey<TName extends string = string> {
4
- readonly _tag: "List";
5
- readonly collection: TName;
6
- }
7
- export interface ItemKey<TName extends string = string> {
8
- readonly _tag: "Item";
9
- readonly collection: TName;
10
- readonly id: Identity;
11
- }
12
- export type Key = ListKey | ItemKey;
13
- export declare const Key: Schema.Union<readonly [Schema.Struct<{
14
- readonly _tag: Schema.Literal<"List">;
15
- readonly collection: Schema.String;
16
- }>, Schema.Struct<{
17
- readonly _tag: Schema.Literal<"Item">;
18
- readonly collection: Schema.String;
19
- readonly id: Schema.Union<readonly [Schema.String, Schema.Number]>;
20
- }>]>;
21
- export interface Collection<TName extends string, TId extends Identity> {
22
- readonly item: (id: TId) => ItemKey<TName>;
23
- readonly list: ListKey<TName>;
24
- readonly name: TName;
25
- }
26
- export declare function collection<const TName extends string, TId extends Schema.Top & {
27
- readonly Type: Identity;
28
- }>(name: TName, _id: TId): Collection<TName, TId["Type"]>;
29
- export interface KeyContext {
30
- readonly scope: string;
31
- }
32
- export declare function readKeys(keys: readonly Key[], context?: KeyContext): readonly string[];
33
- export declare function invalidationKeys(keys: readonly Key[], context?: KeyContext): readonly string[];
34
- //# sourceMappingURL=keys.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,MAAM,WAAW,OAAO,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC;CAC3B;AAED,MAAM,WAAW,OAAO,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC;IAC3B,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;CACtB;AAED,MAAM,MAAM,GAAG,GAAG,OAAO,GAAG,OAAO,CAAC;AAEpC,eAAO,MAAM,GAAG;;;;;;;IAGd,CAAC;AAEH,MAAM,WAAW,UAAU,CAAC,KAAK,SAAS,MAAM,EAAE,GAAG,SAAS,QAAQ;IACrE,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;IAC3C,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;CACrB;AAED,wBAAgB,UAAU,CAAC,KAAK,CAAC,KAAK,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,CAAC,GAAG,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,EAC1G,IAAI,EAAE,KAAK,EACX,GAAG,EAAE,GAAG,GACN,UAAU,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAMhC;AAED,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAUD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,EAAE,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,SAAS,MAAM,EAAE,CAItF;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,GAAG,EAAE,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,SAAS,MAAM,EAAE,CAI9F"}
package/dist/keys.js DELETED
@@ -1,25 +0,0 @@
1
- import { Schema } from "effect";
2
- export const Key = Schema.Union([
3
- Schema.Struct({ _tag: Schema.Literal("List"), collection: Schema.String }),
4
- Schema.Struct({ _tag: Schema.Literal("Item"), collection: Schema.String, id: Schema.Union([Schema.String, Schema.Number]) }),
5
- ]);
6
- export function collection(name, _id) {
7
- return {
8
- item: (id) => ({ _tag: "Item", collection: name, id }),
9
- list: { _tag: "List", collection: name },
10
- name,
11
- };
12
- }
13
- function contextual(key, context) {
14
- return context === undefined ? key : `${JSON.stringify(["session", context.scope])}:${key}`;
15
- }
16
- function own(key) {
17
- return key._tag === "List" ? key.collection : `${key.collection}:${key.id}`;
18
- }
19
- export function readKeys(keys, context) {
20
- return [...new Set(keys.flatMap((key) => (key._tag === "Item" ? [own(key), `${key.collection}:*`] : [own(key)])))].map((key) => contextual(key, context));
21
- }
22
- export function invalidationKeys(keys, context) {
23
- return [...new Set(keys.flatMap((key) => (key._tag === "List" ? [key.collection] : [own(key), key.collection])))].map((key) => contextual(key, context));
24
- }
25
- //# sourceMappingURL=keys.js.map
package/dist/keys.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAiBhC,MAAM,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC;IAC/B,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IAC1E,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;CAC5H,CAAC,CAAC;AAQH,MAAM,UAAU,UAAU,CACzB,IAAW,EACX,GAAQ;IAER,OAAO;QACN,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QACtD,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE;QACxC,IAAI;KACJ,CAAC;AACH,CAAC;AAMD,SAAS,UAAU,CAAC,GAAW,EAAE,OAAoB;IACpD,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC;AAC7F,CAAC;AAED,SAAS,GAAG,CAAC,GAAQ;IACpB,OAAO,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAoB,EAAE,OAAoB;IAClE,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAC9H,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CACxB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAoB,EAAE,OAAoB;IAC1E,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAC7H,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CACxB,CAAC;AACH,CAAC"}
@@ -1,40 +0,0 @@
1
- import { Cache, Data, Effect, Exit, Queue } from "effect";
2
- import type { Key } from "#keys.ts";
3
- import { ReadPolicy, ReadPolicyMemoWindow } from "#readPolicy.ts";
4
-
5
- export const makeHintPolicy = Effect.fnUntraced(function* (capacity: number, retry: (keys: readonly Key[]) => Effect.Effect<void>) {
6
- const policy = yield* ReadPolicy;
7
- const window = yield* ReadPolicyMemoWindow;
8
- const cache = yield* Cache.makeWith(policy, {
9
- capacity: Math.max(1, capacity),
10
- timeToLive: (exit) => (Exit.isSuccess(exit) ? window : 0),
11
- });
12
- const failed = new Map<string, Key>();
13
- const wake = yield* Queue.unbounded<void>();
14
- yield* Queue.take(wake).pipe(
15
- Effect.andThen(Effect.sleep("1 second")),
16
- Effect.andThen(
17
- Effect.suspend(() => {
18
- const keys = [...failed.values()];
19
- failed.clear();
20
- return retry(keys);
21
- }),
22
- ),
23
- Effect.forever,
24
- Effect.forkScoped,
25
- );
26
- return (keys: readonly Key[]) =>
27
- Effect.filter(keys, (key) =>
28
- Cache.get(cache, new Data.Class<Key>(key)).pipe(
29
- Effect.catch(() =>
30
- Effect.gen(function* () {
31
- if (failed.size === 0) {
32
- yield* Queue.offer(wake, undefined);
33
- }
34
- failed.set(JSON.stringify(key), key);
35
- return false;
36
- }),
37
- ),
38
- ),
39
- );
40
- });
package/src/keys.ts DELETED
@@ -1,62 +0,0 @@
1
- import { Schema } from "effect";
2
-
3
- export type Identity = string | number;
4
-
5
- export interface ListKey<TName extends string = string> {
6
- readonly _tag: "List";
7
- readonly collection: TName;
8
- }
9
-
10
- export interface ItemKey<TName extends string = string> {
11
- readonly _tag: "Item";
12
- readonly collection: TName;
13
- readonly id: Identity;
14
- }
15
-
16
- export type Key = ListKey | ItemKey;
17
-
18
- export const Key = Schema.Union([
19
- Schema.Struct({ _tag: Schema.Literal("List"), collection: Schema.String }),
20
- Schema.Struct({ _tag: Schema.Literal("Item"), collection: Schema.String, id: Schema.Union([Schema.String, Schema.Number]) }),
21
- ]);
22
-
23
- export interface Collection<TName extends string, TId extends Identity> {
24
- readonly item: (id: TId) => ItemKey<TName>;
25
- readonly list: ListKey<TName>;
26
- readonly name: TName;
27
- }
28
-
29
- export function collection<const TName extends string, TId extends Schema.Top & { readonly Type: Identity }>(
30
- name: TName,
31
- _id: TId,
32
- ): Collection<TName, TId["Type"]> {
33
- return {
34
- item: (id) => ({ _tag: "Item", collection: name, id }),
35
- list: { _tag: "List", collection: name },
36
- name,
37
- };
38
- }
39
-
40
- export interface KeyContext {
41
- readonly scope: string;
42
- }
43
-
44
- function contextual(key: string, context?: KeyContext): string {
45
- return context === undefined ? key : `${JSON.stringify(["session", context.scope])}:${key}`;
46
- }
47
-
48
- function own(key: Key): string {
49
- return key._tag === "List" ? key.collection : `${key.collection}:${key.id}`;
50
- }
51
-
52
- export function readKeys(keys: readonly Key[], context?: KeyContext): readonly string[] {
53
- return [...new Set(keys.flatMap((key) => (key._tag === "Item" ? [own(key), `${key.collection}:*`] : [own(key)])))].map((key) =>
54
- contextual(key, context),
55
- );
56
- }
57
-
58
- export function invalidationKeys(keys: readonly Key[], context?: KeyContext): readonly string[] {
59
- return [...new Set(keys.flatMap((key) => (key._tag === "List" ? [key.collection] : [own(key), key.collection])))].map((key) =>
60
- contextual(key, context),
61
- );
62
- }