ocel 0.0.1-alpha.1 → 0.0.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 (271) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +2 -0
  3. package/README.md +98 -0
  4. package/dist/binding/binding.d.ts +22 -0
  5. package/dist/binding/binding.js +60 -0
  6. package/dist/binding/unprovisioned.d.ts +17 -0
  7. package/dist/binding/unprovisioned.js +25 -0
  8. package/dist/bucket/bucket-client.d.ts +4 -0
  9. package/dist/bucket/bucket-client.js +5 -0
  10. package/dist/bucket/bucket-context.d.ts +8 -0
  11. package/dist/bucket/bucket-context.js +10 -0
  12. package/dist/bucket/bucket.d.ts +60 -0
  13. package/dist/bucket/bucket.js +91 -0
  14. package/dist/bucket/client.d.ts +49 -0
  15. package/dist/bucket/client.js +95 -0
  16. package/dist/bucket/errors.d.ts +12 -0
  17. package/dist/bucket/errors.js +20 -0
  18. package/dist/bucket/express.d.ts +10 -0
  19. package/dist/bucket/express.js +24 -0
  20. package/dist/bucket/hono.d.ts +18 -0
  21. package/dist/bucket/hono.js +13 -0
  22. package/dist/bucket/index.d.ts +8 -0
  23. package/dist/bucket/index.js +6 -0
  24. package/dist/bucket/keys.d.ts +5 -0
  25. package/dist/bucket/keys.js +24 -0
  26. package/dist/bucket/metadata.d.ts +6 -0
  27. package/dist/bucket/metadata.js +6 -0
  28. package/dist/bucket/next.d.ts +15 -0
  29. package/dist/bucket/next.js +11 -0
  30. package/dist/bucket/objects.d.ts +130 -0
  31. package/dist/bucket/objects.js +350 -0
  32. package/dist/bucket/route.d.ts +13 -0
  33. package/dist/bucket/route.js +247 -0
  34. package/dist/bucket/server-only.js +5 -0
  35. package/dist/bucket/types.d.ts +63 -0
  36. package/dist/bucket/uploader.d.ts +3 -0
  37. package/dist/bucket/uploader.js +6 -0
  38. package/dist/build-env.d.ts +17 -0
  39. package/dist/build-env.js +41 -0
  40. package/dist/config.d.ts +30 -0
  41. package/dist/config.js +9 -0
  42. package/dist/declaration/callsite.d.ts +15 -0
  43. package/dist/declaration/callsite.js +43 -0
  44. package/dist/declaration/component.d.ts +4 -0
  45. package/dist/declaration/component.js +1 -0
  46. package/dist/declaration/defer.d.ts +5 -0
  47. package/dist/declaration/defer.js +9 -0
  48. package/dist/delivery/duration.d.ts +13 -0
  49. package/dist/delivery/duration.js +36 -0
  50. package/dist/delivery/lane.d.ts +4 -0
  51. package/dist/delivery/lane.js +15 -0
  52. package/dist/delivery/payload.d.ts +6 -0
  53. package/dist/delivery/payload.js +14 -0
  54. package/dist/delivery/retry.d.ts +16 -0
  55. package/dist/delivery/retry.js +10 -0
  56. package/dist/delivery/schema.d.ts +10 -0
  57. package/dist/delivery/schema.js +20 -0
  58. package/dist/dns.d.ts +11 -0
  59. package/dist/dns.js +4 -0
  60. package/dist/edge.d.ts +4 -0
  61. package/dist/edge.js +4 -0
  62. package/dist/env/access.d.ts +22 -0
  63. package/dist/env/access.js +49 -0
  64. package/dist/env/client.d.ts +5 -0
  65. package/dist/env/client.js +10 -0
  66. package/dist/env/declare.d.ts +2 -0
  67. package/dist/env/declare.js +83 -0
  68. package/dist/env/definition.d.ts +46 -0
  69. package/dist/env/definition.js +164 -0
  70. package/dist/env/deployment.d.ts +23 -0
  71. package/dist/env/deployment.js +13 -0
  72. package/dist/env/edge.d.ts +10 -0
  73. package/dist/env/edge.js +30 -0
  74. package/dist/env/errors.d.ts +9 -0
  75. package/dist/env/errors.js +9 -0
  76. package/dist/env/file.d.ts +1 -0
  77. package/dist/env/file.js +27 -0
  78. package/dist/env/index.d.ts +10 -0
  79. package/dist/env/index.js +63 -0
  80. package/dist/env/live.d.ts +3 -0
  81. package/dist/env/live.js +12 -0
  82. package/dist/env/schema.d.ts +27 -0
  83. package/dist/env/schema.js +43 -0
  84. package/dist/env/scope.d.ts +7 -0
  85. package/dist/env/scope.js +45 -0
  86. package/dist/env/standard.d.ts +10 -0
  87. package/dist/env/standard.js +21 -0
  88. package/dist/env/value.d.ts +5 -0
  89. package/dist/env/value.js +26 -0
  90. package/dist/env-source.d.ts +17 -0
  91. package/dist/env-source.js +16 -0
  92. package/dist/gen/proto/app/bucket/v1/bucket_pb.d.ts +911 -0
  93. package/dist/gen/proto/app/bucket/v1/bucket_pb.js +253 -0
  94. package/dist/gen/proto/app/realtime/v1/realtime_pb.d.ts +50 -0
  95. package/dist/gen/proto/app/realtime/v1/realtime_pb.js +23 -0
  96. package/dist/gen/proto/app/resources/v1/resources_pb.d.ts +554 -0
  97. package/dist/gen/proto/app/resources/v1/resources_pb.js +214 -0
  98. package/dist/gen/proto/app/resources/v1/variables_pb.d.ts +231 -0
  99. package/dist/gen/proto/app/resources/v1/variables_pb.js +101 -0
  100. package/dist/gen/proto/app/task/v1/task_pb.d.ts +498 -0
  101. package/dist/gen/proto/app/task/v1/task_pb.js +151 -0
  102. package/dist/gen/proto/app/topic/v1/topic_pb.d.ts +456 -0
  103. package/dist/gen/proto/app/topic/v1/topic_pb.js +125 -0
  104. package/dist/gen/proto/buf/validate/validate_pb.d.ts +4833 -0
  105. package/dist/gen/proto/buf/validate/validate_pb.js +356 -0
  106. package/dist/gen/proto/common/bindings/v1/bindings_pb.d.ts +378 -0
  107. package/dist/gen/proto/common/bindings/v1/bindings_pb.js +140 -0
  108. package/dist/generated/config.d.ts +428 -0
  109. package/dist/generated/config.js +2 -0
  110. package/dist/kv/entries.d.ts +193 -0
  111. package/dist/kv/entries.js +325 -0
  112. package/dist/kv/errors.d.ts +12 -0
  113. package/dist/kv/errors.js +14 -0
  114. package/dist/kv/index.d.ts +6 -0
  115. package/dist/kv/index.js +3 -0
  116. package/dist/kv/ioredis.d.ts +2 -0
  117. package/dist/kv/ioredis.js +20 -0
  118. package/dist/kv/kv.d.ts +84 -0
  119. package/dist/kv/kv.js +177 -0
  120. package/dist/kv/pattern.d.ts +27 -0
  121. package/dist/kv/pattern.js +73 -0
  122. package/dist/kv/ttl.d.ts +22 -0
  123. package/dist/kv/ttl.js +21 -0
  124. package/dist/postgres/index.d.ts +21 -2
  125. package/dist/postgres/index.js +73 -2
  126. package/dist/postgres/pg.d.ts +5 -4
  127. package/dist/postgres/pg.js +16 -12
  128. package/dist/providers/aws/dns.d.ts +11 -0
  129. package/dist/providers/aws/dns.js +4 -0
  130. package/dist/providers/aws/edge.d.ts +6 -0
  131. package/dist/providers/aws/edge.js +8 -0
  132. package/dist/providers/aws/index.d.ts +4 -0
  133. package/dist/providers/aws/index.js +4 -0
  134. package/dist/providers/gcp/edge.d.ts +14 -0
  135. package/dist/providers/gcp/edge.js +14 -0
  136. package/dist/providers/gcp/index.d.ts +9 -0
  137. package/dist/providers/gcp/index.js +9 -0
  138. package/dist/providers/vps/index.d.ts +4 -0
  139. package/dist/providers/vps/index.js +4 -0
  140. package/dist/realtime/client-error.d.ts +30 -0
  141. package/dist/realtime/client-error.js +17 -0
  142. package/dist/realtime/client-event-socket.d.ts +8 -0
  143. package/dist/realtime/client-event-socket.js +169 -0
  144. package/dist/realtime/client-transport.d.ts +29 -0
  145. package/dist/realtime/client-transport.js +13 -0
  146. package/dist/realtime/client.d.ts +150 -0
  147. package/dist/realtime/client.js +345 -0
  148. package/dist/realtime/express.d.ts +18 -0
  149. package/dist/realtime/express.js +83 -0
  150. package/dist/realtime/handler.d.ts +56 -0
  151. package/dist/realtime/handler.js +287 -0
  152. package/dist/realtime/hono.d.ts +19 -0
  153. package/dist/realtime/hono.js +24 -0
  154. package/dist/realtime/index.d.ts +2 -0
  155. package/dist/realtime/index.js +2 -0
  156. package/dist/realtime/next.d.ts +26 -0
  157. package/dist/realtime/next.js +16 -0
  158. package/dist/realtime/realtime.d.ts +181 -0
  159. package/dist/realtime/realtime.js +205 -0
  160. package/dist/realtime/token.d.ts +20 -0
  161. package/dist/realtime/token.js +45 -0
  162. package/dist/realtime/transport.d.ts +11 -0
  163. package/dist/realtime/transport.js +15 -0
  164. package/dist/realtime/wire.d.ts +30 -0
  165. package/dist/realtime/wire.js +104 -0
  166. package/dist/runtime/dev-server.d.ts +8 -0
  167. package/dist/runtime/dev-server.js +8 -0
  168. package/dist/runtime/rpc.d.ts +23 -0
  169. package/dist/runtime/rpc.js +24 -0
  170. package/dist/runtime/transport.d.ts +11 -0
  171. package/dist/runtime/transport.js +35 -0
  172. package/dist/task/errors.d.ts +7 -0
  173. package/dist/task/errors.js +7 -0
  174. package/dist/task/hooks.d.ts +57 -0
  175. package/dist/task/hooks.js +1 -0
  176. package/dist/task/index.d.ts +9 -0
  177. package/dist/task/index.js +4 -0
  178. package/dist/task/runs.d.ts +74 -0
  179. package/dist/task/runs.js +82 -0
  180. package/dist/task/task.d.ts +117 -0
  181. package/dist/task/task.js +109 -0
  182. package/dist/topic/dead-letters.d.ts +53 -0
  183. package/dist/topic/dead-letters.js +62 -0
  184. package/dist/topic/index.d.ts +8 -0
  185. package/dist/topic/index.js +4 -0
  186. package/dist/topic/topic.d.ts +85 -0
  187. package/dist/topic/topic.js +117 -0
  188. package/dist/version.d.ts +2 -0
  189. package/dist/version.js +2 -0
  190. package/dist/worker/context.d.ts +36 -0
  191. package/dist/worker/context.js +1 -0
  192. package/dist/worker/deliver.d.ts +13 -0
  193. package/dist/worker/deliver.js +258 -0
  194. package/dist/worker/index.d.ts +4 -0
  195. package/dist/worker/index.js +2 -0
  196. package/dist/worker/registry.d.ts +28 -0
  197. package/dist/worker/registry.js +22 -0
  198. package/dist/worker/semaphore.d.ts +7 -0
  199. package/dist/worker/semaphore.js +36 -0
  200. package/dist/worker/worker.d.ts +21 -0
  201. package/dist/worker/worker.js +32 -0
  202. package/package.json +158 -57
  203. package/bin/run.js +0 -39
  204. package/dist/blob/bucket.d.ts +0 -55
  205. package/dist/blob/bucket.js +0 -65
  206. package/dist/blob/index.d.ts +0 -8
  207. package/dist/blob/index.js +0 -13
  208. package/dist/blob/integrations/client.d.ts +0 -25
  209. package/dist/blob/integrations/client.js +0 -71
  210. package/dist/blob/integrations/hono.d.ts +0 -31
  211. package/dist/blob/integrations/hono.js +0 -11
  212. package/dist/blob/mime-types.d.ts +0 -1
  213. package/dist/blob/parse.d.ts +0 -16
  214. package/dist/blob/parse.js +0 -69
  215. package/dist/blob/s3-listener.d.ts +0 -7
  216. package/dist/blob/s3-listener.js +0 -35
  217. package/dist/blob/types.d.ts +0 -128
  218. package/dist/blob/types.js +0 -1
  219. package/dist/blob/upload-error.d.ts +0 -35
  220. package/dist/blob/upload-error.js +0 -54
  221. package/dist/blob/uploader.d.ts +0 -67
  222. package/dist/blob/uploader.js +0 -323
  223. package/dist/internal/db/index.d.ts +0 -68
  224. package/dist/internal/db/index.js +0 -73
  225. package/dist/lambda/index.d.ts +0 -2
  226. package/dist/lambda/index.js +0 -4
  227. package/dist/lambda/integrations/express.d.ts +0 -3
  228. package/dist/lambda/integrations/express.js +0 -13
  229. package/dist/lambda/integrations/hono.d.ts +0 -3
  230. package/dist/lambda/integrations/hono.js +0 -13
  231. package/dist/lambda/lambda.d.ts +0 -43
  232. package/dist/lambda/lambda.js +0 -30
  233. package/dist/utils/component.d.ts +0 -3
  234. package/dist/utils/constants.d.ts +0 -2
  235. package/dist/utils/constants.js +0 -2
  236. package/dist/utils/nanoid.d.ts +0 -1
  237. package/dist/utils/nanoid.js +0 -6
  238. package/dist/utils/rpc.d.ts +0 -11
  239. package/dist/utils/rpc.js +0 -17
  240. package/dist/utils/stack.d.ts +0 -4
  241. package/dist/utils/stack.js +0 -23
  242. package/dist/workflow/index.d.ts +0 -3
  243. package/dist/workflow/index.js +0 -4
  244. package/dist/workflow/workflow.d.ts +0 -34
  245. package/dist/workflow/workflow.js +0 -146
  246. package/src/blob/bucket.ts +0 -92
  247. package/src/blob/index.ts +0 -34
  248. package/src/blob/integrations/client.ts +0 -127
  249. package/src/blob/integrations/hono.ts +0 -16
  250. package/src/blob/mime-types.ts +0 -68
  251. package/src/blob/parse.ts +0 -93
  252. package/src/blob/s3-listener.ts +0 -48
  253. package/src/blob/types.ts +0 -142
  254. package/src/blob/upload-error.ts +0 -67
  255. package/src/blob/uploader.ts +0 -426
  256. package/src/internal/db/index.ts +0 -78
  257. package/src/lambda/index.ts +0 -8
  258. package/src/lambda/integrations/express.ts +0 -25
  259. package/src/lambda/integrations/hono.ts +0 -25
  260. package/src/lambda/lambda.ts +0 -94
  261. package/src/postgres/index.ts +0 -5
  262. package/src/postgres/pg.ts +0 -33
  263. package/src/utils/component.ts +0 -3
  264. package/src/utils/constants.ts +0 -2
  265. package/src/utils/nanoid.ts +0 -9
  266. package/src/utils/rpc.ts +0 -28
  267. package/src/utils/stack.ts +0 -26
  268. package/src/workflow/index.ts +0 -9
  269. package/src/workflow/workflow.ts +0 -235
  270. /package/dist/{blob/mime-types.js → bucket/server-only.d.ts} +0 -0
  271. /package/dist/{utils/component.js → bucket/types.js} +0 -0
@@ -0,0 +1,325 @@
1
+ import { declarationSite } from "../declaration/callsite.js";
2
+ import { describeIssues } from "../env/standard.js";
3
+ import { KvShape } from "../gen/proto/app/resources/v1/resources_pb.js";
4
+ import { InvalidKVValueError } from "./errors.js";
5
+ import { buildKey, hasParameters, parsePattern, } from "./pattern.js";
6
+ import { parseTTL, resolveTTL } from "./ttl.js";
7
+ export const shapes = {
8
+ text: KvShape.TEXT,
9
+ counter: KvShape.COUNTER,
10
+ json: KvShape.JSON,
11
+ list: KvShape.LIST,
12
+ set: KvShape.SET,
13
+ };
14
+ export function declareEntry(shape, pattern, options) {
15
+ const declaration = { shape, pattern };
16
+ declared.set(declaration, {
17
+ shape,
18
+ pattern,
19
+ parsed: parsePattern(pattern),
20
+ ttl: options.ttl === undefined ? undefined : parseTTL(options.ttl),
21
+ schema: options.schema,
22
+ missOnInvalid: options.onInvalid === "miss",
23
+ source: declarationSite(),
24
+ });
25
+ return declaration;
26
+ }
27
+ const declared = new WeakMap();
28
+ export function readDeclaredEntry(declaration) {
29
+ const found = declared.get(declaration);
30
+ if (!found) {
31
+ throw new TypeError("an entry of a store is declared with kv.text, kv.counter, kv.json, kv.list or kv.set");
32
+ }
33
+ return found;
34
+ }
35
+ class Entry {
36
+ name;
37
+ declaration;
38
+ openClient;
39
+ keyed;
40
+ constructor(name, declaration, openClient) {
41
+ this.name = name;
42
+ this.declaration = declaration;
43
+ this.openClient = openClient;
44
+ this.keyed = hasParameters(declaration.parsed);
45
+ }
46
+ open(operation) {
47
+ return this.openClient(`${this.name}.${operation}`);
48
+ }
49
+ split(args) {
50
+ if (!this.keyed)
51
+ return [buildKey(this.declaration.parsed, undefined), args];
52
+ const [key, ...rest] = args;
53
+ return [buildKey(this.declaration.parsed, key), rest];
54
+ }
55
+ keyOf(key) {
56
+ return buildKey(this.declaration.parsed, key);
57
+ }
58
+ ttl(options) {
59
+ return resolveTTL(this.declaration.ttl, options?.ttl);
60
+ }
61
+ async setString(client, key, value, ttl) {
62
+ switch (ttl.kind) {
63
+ case "expire":
64
+ await client.set(key, value, "PX", ttl.milliseconds);
65
+ return;
66
+ case "keep":
67
+ await client.set(key, value, "KEEPTTL");
68
+ return;
69
+ case "clear":
70
+ await client.set(key, value);
71
+ }
72
+ }
73
+ async write(client, key, ttl, command) {
74
+ let transaction = command(client.multi());
75
+ if (ttl.kind === "expire")
76
+ transaction = transaction.pexpire(key, ttl.milliseconds);
77
+ if (ttl.kind === "clear")
78
+ transaction = transaction.persist(key);
79
+ const results = (await transaction.exec()) ?? [];
80
+ for (const [error] of results) {
81
+ if (error)
82
+ throw error;
83
+ }
84
+ return results[0]?.[1];
85
+ }
86
+ async readMany(operation, keys) {
87
+ const client = this.open(operation);
88
+ if (keys.length === 0)
89
+ return [];
90
+ const pipeline = client.pipeline();
91
+ for (const key of keys)
92
+ pipeline.get(this.keyOf(key));
93
+ const results = (await pipeline.exec()) ?? [];
94
+ return results.map(([error, value]) => {
95
+ if (error)
96
+ throw error;
97
+ return value;
98
+ });
99
+ }
100
+ async deleteKey(operation, args) {
101
+ const client = this.open(operation);
102
+ const [key] = this.split(args);
103
+ return (await client.del(key)) > 0;
104
+ }
105
+ }
106
+ export class TextHandle extends Entry {
107
+ async get(...args) {
108
+ const client = this.open("get");
109
+ const [key] = this.split(args);
110
+ return (await client.get(key)) ?? undefined;
111
+ }
112
+ async getMany(keys) {
113
+ return (await this.readMany("getMany", keys)).map((value) => value ?? undefined);
114
+ }
115
+ async set(...args) {
116
+ const client = this.open("set");
117
+ const [key, [value, options]] = this.split(args);
118
+ if (typeof value !== "string") {
119
+ throw new TypeError(`${this.name}.set takes a string, not ${typeof value}`);
120
+ }
121
+ await this.setString(client, key, value, this.ttl(options));
122
+ }
123
+ async delete(...args) {
124
+ return this.deleteKey("delete", args);
125
+ }
126
+ }
127
+ const integer = /^(0|-?[1-9]\d*)$/;
128
+ const safeRange = "past the integers a number holds exactly, ±(2^53 - 1)";
129
+ function refuseUnsafeCount(key, count) {
130
+ const value = Number(count);
131
+ if (!Number.isSafeInteger(value)) {
132
+ throw new RangeError(`key "${key}" holds ${count}, ${safeRange}: read it through the client as a string`);
133
+ }
134
+ return value;
135
+ }
136
+ export class CounterHandle extends Entry {
137
+ decode(key, value) {
138
+ if (value === null)
139
+ return undefined;
140
+ if (!integer.test(value)) {
141
+ throw new InvalidKVValueError(key, `holds "${value}", which is no integer`);
142
+ }
143
+ return refuseUnsafeCount(key, value);
144
+ }
145
+ async get(...args) {
146
+ const client = this.open("get");
147
+ const [key] = this.split(args);
148
+ return this.decode(key, await client.get(key));
149
+ }
150
+ async getMany(keys) {
151
+ const values = await this.readMany("getMany", keys);
152
+ return values.map((value, i) => this.decode(this.keyOf(keys[i]), value));
153
+ }
154
+ async set(...args) {
155
+ const client = this.open("set");
156
+ const [key, [value, options]] = this.split(args);
157
+ if (!Number.isSafeInteger(value)) {
158
+ throw new TypeError(`${this.name}.set takes an integer, not ${String(value)}`);
159
+ }
160
+ await this.setString(client, key, String(value), this.ttl(options));
161
+ }
162
+ async increment(...args) {
163
+ return this.add("increment", args, 1);
164
+ }
165
+ async decrement(...args) {
166
+ return this.add("decrement", args, -1);
167
+ }
168
+ async add(operation, args, sign) {
169
+ const client = this.open(operation);
170
+ const [key, [by = 1, options]] = this.split(args);
171
+ if (!Number.isSafeInteger(by)) {
172
+ throw new TypeError(`${this.name}.${operation} takes an integer step, not ${String(by)}`);
173
+ }
174
+ const count = await this.write(client, key, this.ttl(options), (transaction) => transaction.incrby(key, sign * by));
175
+ if (!Number.isSafeInteger(count)) {
176
+ throw new RangeError(`${this.name}.${operation} left key "${key}" holding a count ${safeRange}: read it through the client as a string`);
177
+ }
178
+ return count;
179
+ }
180
+ async delete(...args) {
181
+ return this.deleteKey("delete", args);
182
+ }
183
+ }
184
+ export class JsonHandle extends Entry {
185
+ get schema() {
186
+ return this.declaration.schema;
187
+ }
188
+ async decode(key, raw) {
189
+ if (raw === null)
190
+ return undefined;
191
+ let parsed;
192
+ try {
193
+ parsed = JSON.parse(raw);
194
+ }
195
+ catch {
196
+ return this.refuseRead(key, "holds a value that is no JSON");
197
+ }
198
+ const result = await this.schema["~standard"].validate(parsed);
199
+ if (result.issues) {
200
+ return this.refuseRead(key, `holds a value that fails its schema: ${describeIssues(result.issues)}`);
201
+ }
202
+ return result.value;
203
+ }
204
+ refuseRead(key, reason) {
205
+ if (this.declaration.missOnInvalid)
206
+ return undefined;
207
+ throw new InvalidKVValueError(key, reason);
208
+ }
209
+ async get(...args) {
210
+ const client = this.open("get");
211
+ const [key] = this.split(args);
212
+ return this.decode(key, await client.get(key));
213
+ }
214
+ async getMany(keys) {
215
+ const values = await this.readMany("getMany", keys);
216
+ return Promise.all(values.map((value, i) => this.decode(this.keyOf(keys[i]), value)));
217
+ }
218
+ async set(...args) {
219
+ const client = this.open("set");
220
+ const [key, [value, options]] = this.split(args);
221
+ const result = await this.schema["~standard"].validate(value);
222
+ if (result.issues) {
223
+ throw new InvalidKVValueError(key, `would hold a value that fails its schema: ${describeIssues(result.issues)}`);
224
+ }
225
+ await this.setString(client, key, JSON.stringify(result.value), this.ttl(options));
226
+ }
227
+ async delete(...args) {
228
+ return this.deleteKey("delete", args);
229
+ }
230
+ }
231
+ function listOf(values, operation) {
232
+ const list = typeof values === "string" ? [values] : values;
233
+ if (!Array.isArray(list) ||
234
+ list.length === 0 ||
235
+ list.some((value) => typeof value !== "string")) {
236
+ throw new TypeError(`${operation} takes a string or a non-empty array of strings`);
237
+ }
238
+ return list;
239
+ }
240
+ export class ListHandle extends Entry {
241
+ async push(...args) {
242
+ return this.insert("push", args, "rpush");
243
+ }
244
+ async unshift(...args) {
245
+ return this.insert("unshift", args, "lpush");
246
+ }
247
+ async insert(operation, args, command) {
248
+ const client = this.open(operation);
249
+ const [key, [values, options]] = this.split(args);
250
+ const list = listOf(values, `${this.name}.${operation}`);
251
+ const ordered = command === "lpush" ? [...list].reverse() : list;
252
+ return this.write(client, key, this.ttl(options), (transaction) => transaction[command](key, ...ordered));
253
+ }
254
+ async pop(...args) {
255
+ const client = this.open("pop");
256
+ const [key] = this.split(args);
257
+ return (await client.rpop(key)) ?? undefined;
258
+ }
259
+ async shift(...args) {
260
+ const client = this.open("shift");
261
+ const [key] = this.split(args);
262
+ return (await client.lpop(key)) ?? undefined;
263
+ }
264
+ async at(...args) {
265
+ const client = this.open("at");
266
+ const [key, [index]] = this.split(args);
267
+ return (await client.lindex(key, index)) ?? undefined;
268
+ }
269
+ async slice(...args) {
270
+ const client = this.open("slice");
271
+ const [key, [start = 0, end]] = this.split(args);
272
+ if (end === 0)
273
+ return [];
274
+ return client.lrange(key, start, end === undefined ? -1 : end - 1);
275
+ }
276
+ async length(...args) {
277
+ const client = this.open("length");
278
+ const [key] = this.split(args);
279
+ return client.llen(key);
280
+ }
281
+ async delete(...args) {
282
+ return this.deleteKey("delete", args);
283
+ }
284
+ }
285
+ export class SetHandle extends Entry {
286
+ async add(...args) {
287
+ const client = this.open("add");
288
+ const [key, [members, options]] = this.split(args);
289
+ const list = listOf(members, `${this.name}.add`);
290
+ return this.write(client, key, this.ttl(options), (transaction) => transaction.sadd(key, ...list));
291
+ }
292
+ async delete(...args) {
293
+ const client = this.open("delete");
294
+ const [key, [member]] = this.split(args);
295
+ if (typeof member !== "string") {
296
+ throw new TypeError(`${this.name}.delete takes a string member, not ${typeof member}`);
297
+ }
298
+ return (await client.srem(key, member)) === 1;
299
+ }
300
+ async has(...args) {
301
+ const client = this.open("has");
302
+ const [key, [member]] = this.split(args);
303
+ return (await client.sismember(key, member)) === 1;
304
+ }
305
+ async size(...args) {
306
+ const client = this.open("size");
307
+ const [key] = this.split(args);
308
+ return client.scard(key);
309
+ }
310
+ async values(...args) {
311
+ const client = this.open("values");
312
+ const [key] = this.split(args);
313
+ return new Set(await client.smembers(key));
314
+ }
315
+ async clear(...args) {
316
+ return this.deleteKey("clear", args);
317
+ }
318
+ }
319
+ export const handles = {
320
+ text: TextHandle,
321
+ counter: CounterHandle,
322
+ json: JsonHandle,
323
+ list: ListHandle,
324
+ set: SetHandle,
325
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Thrown when a value written to an entry, or read from one, is not a value of the entry's
3
+ * shape: a `json` value that fails its schema, or a `counter` that holds no integer.
4
+ */
5
+ export declare class InvalidKVValueError extends Error {
6
+ /** The key whose value is invalid. */
7
+ readonly key: string;
8
+ name: string;
9
+ constructor(
10
+ /** The key whose value is invalid. */
11
+ key: string, reason: string);
12
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Thrown when a value written to an entry, or read from one, is not a value of the entry's
3
+ * shape: a `json` value that fails its schema, or a `counter` that holds no integer.
4
+ */
5
+ export class InvalidKVValueError extends Error {
6
+ key;
7
+ name = "InvalidKVValueError";
8
+ constructor(
9
+ /** The key whose value is invalid. */
10
+ key, reason) {
11
+ super(`key "${key}" ${reason}`);
12
+ this.key = key;
13
+ }
14
+ }
@@ -0,0 +1,6 @@
1
+ export { UnprovisionedResourceError } from "../binding/unprovisioned.js";
2
+ export type { CounterEntry, EntryDeclaration, EntryOptions, JsonEntry, JsonEntryOptions, ListEntry, SetEntry, TextEntry, } from "./entries.js";
3
+ export { InvalidKVValueError } from "./errors.js";
4
+ export { type Eviction, type KVEntries, type KVMemory, type KVOptions, type KVStore, kv, } from "./kv.js";
5
+ export type { KeyOf } from "./pattern.js";
6
+ export type { KVDuration, WriteOptions, WriteTTL } from "./ttl.js";
@@ -0,0 +1,3 @@
1
+ export { UnprovisionedResourceError } from "../binding/unprovisioned.js";
2
+ export { InvalidKVValueError } from "./errors.js";
3
+ export { kv, } from "./kv.js";
@@ -0,0 +1,2 @@
1
+ import type { Redis, RedisOptions } from "ioredis";
2
+ export declare function newRedis(options: RedisOptions): Redis;
@@ -0,0 +1,20 @@
1
+ const ioredis = await import("ioredis").then((module) => ({ module }), (error) => ({ error }));
2
+ function isIoredisMissing(error) {
3
+ for (let cause = error; cause instanceof Error; cause = cause.cause) {
4
+ const code = cause.code;
5
+ if ((code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") &&
6
+ /['"]ioredis['"]/.test(cause.message)) {
7
+ return true;
8
+ }
9
+ }
10
+ return false;
11
+ }
12
+ export function newRedis(options) {
13
+ if ("error" in ioredis) {
14
+ if (isIoredisMissing(ioredis.error)) {
15
+ throw new Error("a kv store is reached with ioredis, which is not installed. Install it: npm install ioredis", { cause: ioredis.error });
16
+ }
17
+ throw ioredis.error;
18
+ }
19
+ return new ioredis.module.Redis(options);
20
+ }
@@ -0,0 +1,84 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+ import type { Redis } from "ioredis";
3
+ import { type EntryDeclaration, type EntryOf, type EntryOptions, type JsonEntryOptions } from "./entries.js";
4
+ /** The policy a store evicts keys by once it reaches its memory. */
5
+ export type Eviction = "noeviction" | "allkeys-lru" | "allkeys-lfu" | "allkeys-random" | "volatile-lru" | "volatile-lfu" | "volatile-random" | "volatile-ttl";
6
+ /** A size written with its unit, such as `"256mb"` or `"1gb"`. */
7
+ export type KVMemory = `${number}${"kb" | "mb" | "gb"}`;
8
+ /** The entries of a store, by the member name each is reached through. */
9
+ export type KVEntries = Record<string, EntryDeclaration>;
10
+ /** How a store is declared. */
11
+ export interface KVOptions<TEntries extends KVEntries> {
12
+ /** The major Valkey version the store runs; without it, the provider's default. */
13
+ version?: "8" | "9";
14
+ /** The policy keys are evicted by once the store is full; without it, `noeviction`. */
15
+ eviction?: Eviction;
16
+ /** The memory the store holds, which eviction acts at; without it, the provider's default. */
17
+ memory?: KVMemory;
18
+ /**
19
+ * The typed entries the store holds, each a member of the store under its name: a letter,
20
+ * then letters, digits and _, at most 63 characters. `client`, `connectionString`,
21
+ * `connection_string`, `then` and `constructor` are members a store has in some SDK, and
22
+ * are refused.
23
+ */
24
+ entries?: TEntries & {
25
+ [Name in keyof TEntries]: Name extends ReservedName ? never : unknown;
26
+ };
27
+ }
28
+ declare const reservedNames: readonly ["client", "connectionString", "connection_string", "then", "constructor"];
29
+ type ReservedName = (typeof reservedNames)[number];
30
+ /** A declared store: its entries as members, beside the client that reaches the rest. */
31
+ export type KVStore<TEntries extends KVEntries> = {
32
+ readonly [Name in keyof TEntries]: EntryOf<TEntries[Name]>;
33
+ } & {
34
+ /**
35
+ * The ioredis client connected to the store, opened on first use and shared after. Over
36
+ * TLS it trusts only the store's own certificate authority when the provider names one.
37
+ * Reading it throws when the delivered authority holds no PEM certificate.
38
+ */
39
+ readonly client: Redis;
40
+ /** The store's URL, `redis://` or `rediss://` when it requires TLS, for tools that take one. */
41
+ readonly connectionString: string;
42
+ };
43
+ declare function declareStore<TEntries extends KVEntries = Record<never, never>>(name: string, options?: KVOptions<TEntries>): KVStore<TEntries>;
44
+ declare function text<TPattern extends string>(pattern: TPattern, options?: EntryOptions): EntryDeclaration<"text", TPattern, string, string>;
45
+ declare function counter<TPattern extends string>(pattern: TPattern, options?: EntryOptions): EntryDeclaration<"counter", TPattern, number, number>;
46
+ declare function json<TPattern extends string, TSchema extends StandardSchemaV1>(pattern: TPattern, options: JsonEntryOptions<TSchema>): EntryDeclaration<"json", TPattern, StandardSchemaV1.InferInput<TSchema>, StandardSchemaV1.InferOutput<TSchema>>;
47
+ declare function list<TPattern extends string>(pattern: TPattern, options?: EntryOptions): EntryDeclaration<"list", TPattern, string[], string[]>;
48
+ declare function set<TPattern extends string>(pattern: TPattern, options?: EntryOptions): EntryDeclaration<"set", TPattern, string[], string[]>;
49
+ /**
50
+ * Declares a key-value store named `name`, one Valkey instance of its own, and returns it
51
+ * with each of its `entries` as a member, beside its `client` and `connectionString`.
52
+ *
53
+ * An entry's keys are built from its pattern, `/`-separated segments that are each a
54
+ * literal or a `:parameter`, and every operation takes the parameters as its key:
55
+ *
56
+ * ```ts
57
+ * export const cache = kv("cache", {
58
+ * eviction: "allkeys-lru",
59
+ * entries: {
60
+ * requests: kv.counter("requests/:userId", { ttl: "10s" }),
61
+ * session: kv.json("session/:id", { schema: Session, ttl: "30d" }),
62
+ * },
63
+ * });
64
+ *
65
+ * await cache.requests.increment({ userId });
66
+ * await cache.session.get({ id }); // Session | undefined
67
+ * ```
68
+ *
69
+ * Two entries whose patterns could name the same key are refused when the store is
70
+ * declared. During discovery every member throws an `UnprovisionedResourceError`.
71
+ */
72
+ export declare const kv: typeof declareStore & {
73
+ /** Declares a `text` entry: a string under each key. */
74
+ text: typeof text;
75
+ /** Declares a `counter` entry: an integer under each key, changed atomically. */
76
+ counter: typeof counter;
77
+ /** Declares a `json` entry: a value its Standard Schema checks on write and on read. */
78
+ json: typeof json;
79
+ /** Declares a `list` entry: a list of strings under each key. */
80
+ list: typeof list;
81
+ /** Declares a `set` entry: a set of strings under each key. */
82
+ set: typeof set;
83
+ };
84
+ export {};
package/dist/kv/kv.js ADDED
@@ -0,0 +1,177 @@
1
+ import { X509Certificate } from "node:crypto";
2
+ import { bindingKey, getConfig } from "../binding/binding.js";
3
+ import { unprovisioned, unprovisionedPhase } from "../binding/unprovisioned.js";
4
+ import { declarationSite } from "../declaration/callsite.js";
5
+ import { defer } from "../declaration/defer.js";
6
+ import { ResourceType } from "../gen/proto/app/resources/v1/resources_pb.js";
7
+ import { BindingType } from "../gen/proto/common/bindings/v1/bindings_pb.js";
8
+ import { rpc } from "../runtime/rpc.js";
9
+ import { declareEntry, handles, readDeclaredEntry, shapes, } from "./entries.js";
10
+ import { newRedis } from "./ioredis.js";
11
+ import { patternsOverlap } from "./pattern.js";
12
+ const reservedNames = [
13
+ "client",
14
+ "connectionString",
15
+ "connection_string",
16
+ "then",
17
+ "constructor",
18
+ ];
19
+ const entryName = /^[A-Za-z][A-Za-z0-9_]{0,62}$/;
20
+ function declareStore(name, options = {}) {
21
+ const entries = Object.entries(options.entries ?? {}).map(([entry, declaration]) => [entry, readDeclaredEntry(declaration)]);
22
+ refuseEntries(name, entries);
23
+ if (process.env.OCEL_PHASE === "discovery") {
24
+ defer(rpc.resource.declare({
25
+ resource: { name, type: ResourceType.KV },
26
+ config: {
27
+ case: "kv",
28
+ value: {
29
+ version: options.version ?? "",
30
+ eviction: options.eviction ?? "",
31
+ memory: options.memory ?? "",
32
+ entries: entries.map(([entry, declaration]) => ({
33
+ name: entry,
34
+ pattern: declaration.pattern,
35
+ shape: shapes[declaration.shape],
36
+ source: declaration.source,
37
+ })),
38
+ },
39
+ },
40
+ source: declarationSite(),
41
+ }));
42
+ }
43
+ let client;
44
+ const openClient = (access) => {
45
+ if (unprovisionedPhase()) {
46
+ throw unprovisioned(`kv("${name}")`, access);
47
+ }
48
+ client ??= newRedis(clientOptions(name, getConfig(name, "kv")));
49
+ return client;
50
+ };
51
+ const store = {};
52
+ for (const [entry, declaration] of entries) {
53
+ store[entry] = new handles[declaration.shape](entry, declaration, openClient);
54
+ }
55
+ Object.defineProperties(store, {
56
+ client: { get: () => openClient("client") },
57
+ connectionString: {
58
+ get: () => {
59
+ if (unprovisionedPhase())
60
+ throw unprovisioned(`kv("${name}")`, "connectionString");
61
+ return connectionStringOf(getConfig(name, "kv"));
62
+ },
63
+ },
64
+ });
65
+ return store;
66
+ }
67
+ function refuseEntries(store, entries) {
68
+ for (const [i, [name, declaration]] of entries.entries()) {
69
+ if (reservedNames.includes(name)) {
70
+ throw new Error(`kv("${store}"): entry name "${name}" is reserved, since a store already has a member of that name in some SDK: name the entry otherwise`);
71
+ }
72
+ if (!entryName.test(name)) {
73
+ throw new Error(`kv("${store}"): entry name "${name}" is no name every SDK can hold: it starts with a letter and goes on in letters, digits and _, at most 63 characters`);
74
+ }
75
+ for (const [prior, priorDeclaration] of entries.slice(0, i)) {
76
+ if (patternsOverlap(priorDeclaration.parsed, declaration.parsed)) {
77
+ throw new Error(`kv("${store}"): entry "${name}" declared at ${declaration.source || "an unknown line"} has pattern "${declaration.pattern}", which overlaps pattern "${priorDeclaration.pattern}" of entry "${prior}" declared at ${priorDeclaration.source || "an unknown line"}: some key would match both, so neither entry could tell its keys from the other's`);
78
+ }
79
+ }
80
+ }
81
+ }
82
+ function clientOptions(store, properties) {
83
+ const { host, port, username, password, tls, caPem } = properties;
84
+ return {
85
+ host,
86
+ port,
87
+ username: username || undefined,
88
+ password: password || undefined,
89
+ tls: tls ? tlsOptions(store, host, caPem) : undefined,
90
+ };
91
+ }
92
+ function tlsOptions(store, host, caPem) {
93
+ if (caPem && !holdsCertificates(caPem)) {
94
+ throw new Error(`${bindingKey(store, BindingType.KV)} delivers a caPem for its kv store that holds no PEM certificate`);
95
+ }
96
+ return {
97
+ ...(isAddress(host) ? {} : { servername: host }),
98
+ ...(caPem ? { ca: caPem } : {}),
99
+ };
100
+ }
101
+ const pemBlock = /-----BEGIN ([A-Z0-9 ]+)-----([\s\S]*?)-----END \1-----/g;
102
+ function holdsCertificates(pem) {
103
+ const certificates = [...pem.matchAll(pemBlock)].filter(([, label]) => label === "CERTIFICATE");
104
+ return certificates.length > 0 && certificates.every(([block]) => isCertificate(block));
105
+ }
106
+ function isCertificate(block) {
107
+ try {
108
+ new X509Certificate(block);
109
+ return true;
110
+ }
111
+ catch {
112
+ return false;
113
+ }
114
+ }
115
+ function isAddress(host) {
116
+ return /^[\d.]+$/.test(host) || host.includes(":");
117
+ }
118
+ function connectionStringOf(properties) {
119
+ const { host, port, username, password, tls } = properties;
120
+ const url = new URL(`${tls ? "rediss" : "redis"}://${host}:${port}`);
121
+ url.username = username;
122
+ url.password = password;
123
+ return url.toString().replace(/\/$/, "");
124
+ }
125
+ function text(pattern, options = {}) {
126
+ return declareEntry("text", pattern, options);
127
+ }
128
+ function counter(pattern, options = {}) {
129
+ return declareEntry("counter", pattern, options);
130
+ }
131
+ function json(pattern, options) {
132
+ if (!options?.schema?.["~standard"]) {
133
+ throw new Error(`kv.json("${pattern}") takes a Standard Schema as its schema, and was given none`);
134
+ }
135
+ return declareEntry("json", pattern, options);
136
+ }
137
+ function list(pattern, options = {}) {
138
+ return declareEntry("list", pattern, options);
139
+ }
140
+ function set(pattern, options = {}) {
141
+ return declareEntry("set", pattern, options);
142
+ }
143
+ /**
144
+ * Declares a key-value store named `name`, one Valkey instance of its own, and returns it
145
+ * with each of its `entries` as a member, beside its `client` and `connectionString`.
146
+ *
147
+ * An entry's keys are built from its pattern, `/`-separated segments that are each a
148
+ * literal or a `:parameter`, and every operation takes the parameters as its key:
149
+ *
150
+ * ```ts
151
+ * export const cache = kv("cache", {
152
+ * eviction: "allkeys-lru",
153
+ * entries: {
154
+ * requests: kv.counter("requests/:userId", { ttl: "10s" }),
155
+ * session: kv.json("session/:id", { schema: Session, ttl: "30d" }),
156
+ * },
157
+ * });
158
+ *
159
+ * await cache.requests.increment({ userId });
160
+ * await cache.session.get({ id }); // Session | undefined
161
+ * ```
162
+ *
163
+ * Two entries whose patterns could name the same key are refused when the store is
164
+ * declared. During discovery every member throws an `UnprovisionedResourceError`.
165
+ */
166
+ export const kv = Object.assign(declareStore, {
167
+ /** Declares a `text` entry: a string under each key. */
168
+ text,
169
+ /** Declares a `counter` entry: an integer under each key, changed atomically. */
170
+ counter,
171
+ /** Declares a `json` entry: a value its Standard Schema checks on write and on read. */
172
+ json,
173
+ /** Declares a `list` entry: a list of strings under each key. */
174
+ list,
175
+ /** Declares a `set` entry: a set of strings under each key. */
176
+ set,
177
+ });
@@ -0,0 +1,27 @@
1
+ type Segment = {
2
+ literal: string;
3
+ } | {
4
+ parameter: string;
5
+ };
6
+ export interface Pattern {
7
+ readonly written: string;
8
+ readonly segments: readonly Segment[];
9
+ }
10
+ /** The parameter names a pattern such as `"rooms/:room/members/:member"` holds. */
11
+ export type PatternParameters<TPattern extends string> = TPattern extends `${infer Head}/${infer Rest}` ? SegmentParameter<Head> | PatternParameters<Rest> : SegmentParameter<TPattern>;
12
+ type SegmentParameter<TSegment extends string> = TSegment extends `:${infer Name}` ? Name : never;
13
+ /**
14
+ * The key of an entry: one value for each parameter of its pattern, a string or a safe
15
+ * integer. A number that is not a safe integer is refused when the key is built.
16
+ */
17
+ export type KeyOf<TPattern extends string> = {
18
+ [Name in PatternParameters<TPattern>]: string | number;
19
+ };
20
+ /** The leading argument an entry's operations take: its key, or nothing for a pattern without parameters. */
21
+ export type KeyArgs<TPattern extends string> = [PatternParameters<TPattern>] extends [never] ? [] : [key: KeyOf<TPattern>];
22
+ export declare function parsePattern(written: string): Pattern;
23
+ export declare function patternsOverlap(a: Pattern, b: Pattern): boolean;
24
+ export declare function hasParameters(pattern: Pattern): boolean;
25
+ export declare function encodeParameter(value: string): string;
26
+ export declare function buildKey(pattern: Pattern, key: Record<string, unknown> | undefined): string;
27
+ export {};