@rexezuge/runtime 0.0.0-stage → 1.0.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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/dist/base/AbstractDurableObjectWorker.d.ts +26 -0
  3. package/dist/base/AbstractDurableObjectWorker.d.ts.map +1 -0
  4. package/dist/base/AbstractDurableObjectWorker.js +38 -0
  5. package/dist/base/AbstractDurableObjectWorker.js.map +1 -0
  6. package/dist/base/AbstractEntrypointWorker.d.ts +40 -0
  7. package/dist/base/AbstractEntrypointWorker.d.ts.map +1 -0
  8. package/dist/base/AbstractEntrypointWorker.js +35 -0
  9. package/dist/base/AbstractEntrypointWorker.js.map +1 -0
  10. package/dist/base/AbstractQueueWorker.d.ts +20 -0
  11. package/dist/base/AbstractQueueWorker.d.ts.map +1 -0
  12. package/dist/base/AbstractQueueWorker.js +13 -0
  13. package/dist/base/AbstractQueueWorker.js.map +1 -0
  14. package/dist/base/AbstractWorkflowWorker.d.ts +22 -0
  15. package/dist/base/AbstractWorkflowWorker.d.ts.map +1 -0
  16. package/dist/base/AbstractWorkflowWorker.js +13 -0
  17. package/dist/base/AbstractWorkflowWorker.js.map +1 -0
  18. package/dist/base/index.d.ts +8 -0
  19. package/dist/base/index.d.ts.map +1 -0
  20. package/dist/base/index.js +5 -0
  21. package/dist/base/index.js.map +1 -0
  22. package/dist/config/AppConfiguration.d.ts +36 -0
  23. package/dist/config/AppConfiguration.d.ts.map +1 -0
  24. package/dist/config/AppConfiguration.js +63 -0
  25. package/dist/config/AppConfiguration.js.map +1 -0
  26. package/dist/config/ConfigurationDefaults.d.ts +12 -0
  27. package/dist/config/ConfigurationDefaults.d.ts.map +1 -0
  28. package/dist/config/ConfigurationDefaults.js +12 -0
  29. package/dist/config/ConfigurationDefaults.js.map +1 -0
  30. package/dist/config/EnvParser.d.ts +30 -0
  31. package/dist/config/EnvParser.d.ts.map +1 -0
  32. package/dist/config/EnvParser.js +46 -0
  33. package/dist/config/EnvParser.js.map +1 -0
  34. package/dist/config/index.d.ts +4 -0
  35. package/dist/config/index.d.ts.map +1 -0
  36. package/dist/config/index.js +4 -0
  37. package/dist/config/index.js.map +1 -0
  38. package/dist/di/Container.d.ts +39 -0
  39. package/dist/di/Container.d.ts.map +1 -0
  40. package/dist/di/Container.js +48 -0
  41. package/dist/di/Container.js.map +1 -0
  42. package/dist/di/RequestScope.d.ts +32 -0
  43. package/dist/di/RequestScope.d.ts.map +1 -0
  44. package/dist/di/RequestScope.js +22 -0
  45. package/dist/di/RequestScope.js.map +1 -0
  46. package/dist/di/index.d.ts +5 -0
  47. package/dist/di/index.d.ts.map +1 -0
  48. package/dist/di/index.js +3 -0
  49. package/dist/di/index.js.map +1 -0
  50. package/dist/index.d.ts +24 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +17 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/kv/KvCache.d.ts +74 -0
  55. package/dist/kv/KvCache.d.ts.map +1 -0
  56. package/dist/kv/KvCache.js +198 -0
  57. package/dist/kv/KvCache.js.map +1 -0
  58. package/dist/kv/KvDomains.d.ts +34 -0
  59. package/dist/kv/KvDomains.d.ts.map +1 -0
  60. package/dist/kv/KvDomains.js +101 -0
  61. package/dist/kv/KvDomains.js.map +1 -0
  62. package/dist/kv/index.d.ts +5 -0
  63. package/dist/kv/index.d.ts.map +1 -0
  64. package/dist/kv/index.js +3 -0
  65. package/dist/kv/index.js.map +1 -0
  66. package/dist/logger.d.ts +26 -0
  67. package/dist/logger.d.ts.map +1 -0
  68. package/dist/logger.js +47 -0
  69. package/dist/logger.js.map +1 -0
  70. package/package.json +34 -3
  71. package/README.md +0 -3
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The dependency-injection container.
3
+ *
4
+ * Converged verbatim from `backend-runtime/src/di/Container.ts` — the file is
5
+ * byte-identical (modulo scope) across all nine full-framework consumer repos,
6
+ * because the last divergence in it was fixed in all of them at once.
7
+ *
8
+ * Minimal by design: factory + singleton scopes, branded tokens for inference,
9
+ * nothing else. A DI container that grows features grows coupling with it.
10
+ */
11
+ /**
12
+ * Composition roots (a request scope, a cron entrypoint, tests) wire concrete
13
+ * implementations once; handlers resolve via `scope.get(Tokens.X)`. Prefer
14
+ * constructor injection of ports at registration time over inline
15
+ * `scope.get()` in business logic.
16
+ */
17
+ class Container {
18
+ factories = new Map();
19
+ singletons = new Map();
20
+ bind(token, factory) {
21
+ this.factories.set(token, factory);
22
+ return this;
23
+ }
24
+ bindValue(token, value) {
25
+ this.singletons.set(token, value);
26
+ return this;
27
+ }
28
+ /**
29
+ * Is a token bound (as a value or a factory)? Used to assert wiring.
30
+ */
31
+ has(token) {
32
+ return this.singletons.has(token) || this.factories.has(token);
33
+ }
34
+ get(token) {
35
+ if (this.singletons.has(token)) {
36
+ return this.singletons.get(token);
37
+ }
38
+ const factory = this.factories.get(token);
39
+ if (!factory) {
40
+ throw new Error(`DI container has no binding for token: ${String(token)}`);
41
+ }
42
+ const instance = factory(this);
43
+ this.singletons.set(token, instance);
44
+ return instance;
45
+ }
46
+ }
47
+ export { Container };
48
+ //# sourceMappingURL=Container.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Container.js","sourceRoot":"","sources":["../../src/di/Container.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAWH;;;;;GAKG;AACH,MAAM,SAAS;IACI,SAAS,GAAG,IAAI,GAAG,EAAoC,CAAC;IAExD,UAAU,GAAG,IAAI,GAAG,EAA2B,CAAC;IAE1D,IAAI,CAAI,KAAe,EAAE,OAAmB;QACjD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IAEM,SAAS,CAAI,KAAe,EAAE,KAAQ;QAC3C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACI,GAAG,CAAI,KAAe;QAC3B,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACjE,CAAC;IAEM,GAAG,CAAI,KAAe;QAC3B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/B,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAM,CAAC;QACzC,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,0CAA0C,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC7E,CAAC;QACD,MAAM,QAAQ,GAAI,OAAsB,CAAC,IAAI,CAAC,CAAC;QAC/C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QACrC,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF;AAED,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { Container } from './Container';
2
+ /**
3
+ * Single-scope-per-request helper.
4
+ *
5
+ * Converged verbatim from `backend-runtime/src/di/RequestScope.ts` (identical in
6
+ * all nine repos). Middleware creates one `Container` per request and stores it
7
+ * on the request context; handlers resolve via `getRequestScope(c)` instead of
8
+ * building a scope per handler — which minted N scopes per request and broke
9
+ * singleton memoization.
10
+ */
11
+ interface ScopedContext {
12
+ get(key: string): unknown;
13
+ set(key: string, value: unknown): void;
14
+ readonly env: unknown;
15
+ }
16
+ declare function setRequestScope(c: ScopedContext, scope: Container): void;
17
+ /**
18
+ * Structural adapter for framework contexts.
19
+ *
20
+ * Hono's `Context.get` overloads are not assignable to `ScopedContext['get']`,
21
+ * so call sites used `c as never`. Centralize that single unsafe cast here —
22
+ * one audited location instead of many scattered ones.
23
+ */
24
+ declare function asScopedContext(c: {
25
+ get(key: string): unknown;
26
+ set?(key: string, value: unknown): void;
27
+ readonly env: unknown;
28
+ }): ScopedContext;
29
+ declare function getRequestScope(c: ScopedContext): Container;
30
+ export { setRequestScope, getRequestScope, asScopedContext };
31
+ export type { ScopedContext };
32
+ //# sourceMappingURL=RequestScope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RequestScope.d.ts","sourceRoot":"","sources":["../../src/di/RequestScope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;;GAQG;AACH,UAAU,aAAa;IACrB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;CACvB;AAID,iBAAS,eAAe,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAEjE;AAED;;;;;;GAMG;AACH,iBAAS,eAAe,CAAC,CAAC,EAAE;IAC1B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;CACvB,GAAG,aAAa,CAEhB;AAED,iBAAS,eAAe,CAAC,CAAC,EAAE,aAAa,GAAG,SAAS,CAIpD;AAED,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,CAAC;AAC7D,YAAY,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,22 @@
1
+ const SCOPE_KEY = '__kitScope';
2
+ function setRequestScope(c, scope) {
3
+ c.set(SCOPE_KEY, scope);
4
+ }
5
+ /**
6
+ * Structural adapter for framework contexts.
7
+ *
8
+ * Hono's `Context.get` overloads are not assignable to `ScopedContext['get']`,
9
+ * so call sites used `c as never`. Centralize that single unsafe cast here —
10
+ * one audited location instead of many scattered ones.
11
+ */
12
+ function asScopedContext(c) {
13
+ return c;
14
+ }
15
+ function getRequestScope(c) {
16
+ const scope = c.get(SCOPE_KEY);
17
+ if (!scope)
18
+ throw new Error('Request scope is not set. Register the scope middleware before routes.');
19
+ return scope;
20
+ }
21
+ export { setRequestScope, getRequestScope, asScopedContext };
22
+ //# sourceMappingURL=RequestScope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RequestScope.js","sourceRoot":"","sources":["../../src/di/RequestScope.ts"],"names":[],"mappings":"AAiBA,MAAM,SAAS,GAAG,YAAY,CAAC;AAE/B,SAAS,eAAe,CAAC,CAAgB,EAAE,KAAgB;IACzD,CAAC,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;GAMG;AACH,SAAS,eAAe,CAAC,CAIxB;IACC,OAAO,CAA6B,CAAC;AACvC,CAAC;AAED,SAAS,eAAe,CAAC,CAAgB;IACvC,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAA0B,CAAC;IACxD,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,wEAAwE,CAAC,CAAC;IACtG,OAAO,KAAK,CAAC;AACf,CAAC;AAED,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { Container } from './Container';
2
+ export type { Factory, Token } from './Container';
3
+ export { asScopedContext, getRequestScope, setRequestScope } from './RequestScope';
4
+ export type { ScopedContext } from './RequestScope';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/di/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACnF,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { Container } from './Container';
2
+ export { asScopedContext, getRequestScope, setRequestScope } from './RequestScope';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/di/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * `@rexezuge/runtime` — the platform foundation layer: DI, configuration,
3
+ * worker bases, logger, KV cache. Zero dependencies (the one platform import
4
+ * is type-only, resolved through each consumer's generated wrangler types).
5
+ */
6
+ export { Container } from './di/Container';
7
+ export type { Factory, Token } from './di/Container';
8
+ export { asScopedContext, getRequestScope, setRequestScope } from './di/RequestScope';
9
+ export type { ScopedContext } from './di/RequestScope';
10
+ export { AppConfiguration } from './config/AppConfiguration';
11
+ export { EnvParser } from './config/EnvParser';
12
+ export { DEFAULT_SITE_URL } from './config/ConfigurationDefaults';
13
+ export { AbstractEntrypointWorker } from './base/AbstractEntrypointWorker';
14
+ export type { WorkerExecutionContext, WorkerScheduledController } from './base/AbstractEntrypointWorker';
15
+ export { AbstractDurableObjectWorker } from './base/AbstractDurableObjectWorker';
16
+ export { AbstractQueueWorker } from './base/AbstractQueueWorker';
17
+ export type { WorkerMessageBatch } from './base/AbstractQueueWorker';
18
+ export { AbstractWorkflowWorker } from './base/AbstractWorkflowWorker';
19
+ export type { WorkflowEventLike, WorkflowStepLike } from './base/AbstractWorkflowWorker';
20
+ export { createLogger, isLogLevel, resolveLogLevel } from './logger';
21
+ export type { LogLevel, Logger } from './logger';
22
+ export { KvCache, KV_DOMAINS, buildKvKey, clampTtl, digest128 } from './kv';
23
+ export type { KvDomainDef, KvDomainName, KvNamespaceLike } from './kv';
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACtF,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,YAAY,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AACzG,OAAO,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACzF,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AACrE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAC5E,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `@rexezuge/runtime` — the platform foundation layer: DI, configuration,
3
+ * worker bases, logger, KV cache. Zero dependencies (the one platform import
4
+ * is type-only, resolved through each consumer's generated wrangler types).
5
+ */
6
+ export { Container } from './di/Container';
7
+ export { asScopedContext, getRequestScope, setRequestScope } from './di/RequestScope';
8
+ export { AppConfiguration } from './config/AppConfiguration';
9
+ export { EnvParser } from './config/EnvParser';
10
+ export { DEFAULT_SITE_URL } from './config/ConfigurationDefaults';
11
+ export { AbstractEntrypointWorker } from './base/AbstractEntrypointWorker';
12
+ export { AbstractDurableObjectWorker } from './base/AbstractDurableObjectWorker';
13
+ export { AbstractQueueWorker } from './base/AbstractQueueWorker';
14
+ export { AbstractWorkflowWorker } from './base/AbstractWorkflowWorker';
15
+ export { createLogger, isLogLevel, resolveLogLevel } from './logger';
16
+ export { KvCache, KV_DOMAINS, buildKvKey, clampTtl, digest128 } from './kv';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAE3E,OAAO,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEvE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAErE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC"}
@@ -0,0 +1,74 @@
1
+ import type { KvDomainName } from './KvDomains';
2
+ interface KvListPage {
3
+ keys: Array<{
4
+ name: string;
5
+ }>;
6
+ list_complete: boolean;
7
+ cursor?: string;
8
+ }
9
+ interface KvGetWithMetadataResult {
10
+ value: string | ArrayBuffer | null;
11
+ metadata: unknown;
12
+ }
13
+ interface KvNamespaceLike {
14
+ get(key: string): Promise<string | null>;
15
+ get(key: string, type: 'text'): Promise<string | null>;
16
+ get(key: string, type: 'arrayBuffer'): Promise<ArrayBuffer | null>;
17
+ getWithMetadata?(key: string, type: 'arrayBuffer'): Promise<KvGetWithMetadataResult>;
18
+ put(key: string, value: string | ArrayBuffer, options?: {
19
+ expirationTtl?: number;
20
+ metadata?: unknown;
21
+ }): Promise<void>;
22
+ delete(key: string): Promise<unknown>;
23
+ list(options: {
24
+ prefix: string;
25
+ limit?: number;
26
+ cursor?: string;
27
+ }): Promise<KvListPage>;
28
+ }
29
+ interface KvPutOptions {
30
+ ttlSeconds?: number;
31
+ }
32
+ interface KvFileMetadata {
33
+ etag: string;
34
+ contentType: string | null;
35
+ }
36
+ interface KvBinaryEntry {
37
+ bytes: Uint8Array;
38
+ metadata: KvFileMetadata;
39
+ }
40
+ declare const PURGE_LIST_LIMIT = 1000;
41
+ declare const PURGE_MAX_PAGES = 10;
42
+ declare class KvCache {
43
+ private readonly namespace?;
44
+ constructor(namespace?: (KvNamespaceLike | null) | undefined);
45
+ get available(): boolean;
46
+ getText(domain: KvDomainName, parts: readonly string[]): Promise<string | null>;
47
+ putText(domain: KvDomainName, parts: readonly string[], value: string, options?: KvPutOptions): Promise<boolean>;
48
+ getJson<T>(domain: KvDomainName, parts: readonly string[]): Promise<T | null>;
49
+ putJson(domain: KvDomainName, parts: readonly string[], value: unknown, options?: KvPutOptions): Promise<boolean>;
50
+ /**
51
+ * Binary read for the `asset` domain: value bytes plus `{etag, contentType}`
52
+ * KV metadata in a single entry. Legacy base64-JSON entries carry no metadata
53
+ * and read as a miss here — the caller falls back to `getJson` for one TTL
54
+ * window rather than this layer knowing the legacy shape.
55
+ */
56
+ getBinary(domain: KvDomainName, parts: readonly string[]): Promise<KvBinaryEntry | null>;
57
+ /**
58
+ * Binary write for the `asset` domain. Size is enforced on raw bytes (no
59
+ * base64 inflation) and failures stay fail-soft like `putText`.
60
+ */
61
+ putBinary(domain: KvDomainName, parts: readonly string[], bytes: Uint8Array, metadata: {
62
+ etag: string;
63
+ contentType: string | null;
64
+ }, options?: KvPutOptions): Promise<boolean>;
65
+ del(domain: KvDomainName, parts: readonly string[]): Promise<void>;
66
+ /**
67
+ * Delete every key under a prefix, page-capped. Restarts from the start after
68
+ * each page: deleting while a positional cursor advances would skip keys.
69
+ */
70
+ purgePrefix(domain: KvDomainName, parts?: readonly string[]): Promise<number>;
71
+ }
72
+ export { KvCache, PURGE_LIST_LIMIT, PURGE_MAX_PAGES };
73
+ export type { KvBinaryEntry, KvFileMetadata, KvNamespaceLike, KvPutOptions };
74
+ //# sourceMappingURL=KvCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KvCache.d.ts","sourceRoot":"","sources":["../../src/kv/KvCache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,198 @@
1
+ import { createLogger } from '../logger';
2
+ import { KV_DOMAINS, buildKvKey, clampTtl, utf8ByteLength } from './KvDomains';
3
+ /**
4
+ * `KvCache` — typed facade over the single `CACHE` KV binding.
5
+ *
6
+ * Converged from `backend-runtime/src/kv/KvCache.ts`, identical in the four
7
+ * repos that cache through KV. Semantics are fail-soft by design: reads return
8
+ * `null` on miss, missing binding, or backend error; writes return `false`
9
+ * when skipped (no binding, oversize value, backend error) and `true` when
10
+ * stored. Callers treat KV as a pure optimization — D1/DO stay authoritative.
11
+ */
12
+ const logger = createLogger('KvCache');
13
+ const PURGE_LIST_LIMIT = 1000;
14
+ const PURGE_MAX_PAGES = 10;
15
+ class KvCache {
16
+ namespace;
17
+ constructor(namespace) {
18
+ this.namespace = namespace;
19
+ }
20
+ get available() {
21
+ return !!this.namespace;
22
+ }
23
+ async getText(domain, parts) {
24
+ const ns = this.namespace;
25
+ if (!ns)
26
+ return null;
27
+ try {
28
+ return await ns.get(buildKvKey(domain, parts));
29
+ }
30
+ catch (error) {
31
+ logger.debug(`KV get failed for ${domain}: ${error instanceof Error ? error.message : String(error)}`);
32
+ return null;
33
+ }
34
+ }
35
+ async putText(domain, parts, value, options) {
36
+ const ns = this.namespace;
37
+ if (!ns)
38
+ return false;
39
+ // No unknown-domain check: `buildKvKey` below throws for one, and the
40
+ // return type has never been able to express that. A silent `false` here
41
+ // would have made an unknown domain look like a namespace miss.
42
+ const def = KV_DOMAINS[domain];
43
+ if (utf8ByteLength(value) > def.maxValueBytes) {
44
+ logger.debug(`KV put skipped for ${domain}: value exceeds ${def.maxValueBytes} bytes.`);
45
+ return false;
46
+ }
47
+ try {
48
+ const ttl = clampTtl(options?.ttlSeconds, domain);
49
+ await ns.put(buildKvKey(domain, parts), value, { expirationTtl: ttl });
50
+ return true;
51
+ }
52
+ catch (error) {
53
+ logger.debug(`KV put failed for ${domain}: ${error instanceof Error ? error.message : String(error)}`);
54
+ return false;
55
+ }
56
+ }
57
+ async getJson(domain, parts) {
58
+ const raw = await this.getText(domain, parts);
59
+ if (raw === null)
60
+ return null;
61
+ try {
62
+ return JSON.parse(raw);
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ }
68
+ async putJson(domain, parts, value, options) {
69
+ let raw;
70
+ try {
71
+ raw = JSON.stringify(value);
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ return typeof raw === 'string' && this.putText(domain, parts, raw, options);
77
+ }
78
+ /**
79
+ * Binary read for the `asset` domain: value bytes plus `{etag, contentType}`
80
+ * KV metadata in a single entry. Legacy base64-JSON entries carry no metadata
81
+ * and read as a miss here — the caller falls back to `getJson` for one TTL
82
+ * window rather than this layer knowing the legacy shape.
83
+ */
84
+ async getBinary(domain, parts) {
85
+ const ns = this.namespace;
86
+ if (!ns || typeof ns.getWithMetadata !== 'function')
87
+ return null;
88
+ try {
89
+ const res = await ns.getWithMetadata(buildKvKey(domain, parts), 'arrayBuffer');
90
+ if (!res || res.value == null)
91
+ return null;
92
+ const value = res.value;
93
+ let bytes = null;
94
+ if (value instanceof ArrayBuffer) {
95
+ bytes = new Uint8Array(value.slice(0));
96
+ }
97
+ else if (value instanceof Uint8Array) {
98
+ bytes = value.slice();
99
+ }
100
+ else if (typeof value === 'string') {
101
+ bytes = new TextEncoder().encode(value);
102
+ }
103
+ if (!bytes)
104
+ return null;
105
+ const meta = res.metadata;
106
+ if (typeof meta !== 'object' || meta === null || typeof meta.etag !== 'string' || meta.etag === '')
107
+ return null;
108
+ return { bytes, metadata: { etag: meta.etag, contentType: typeof meta.contentType === 'string' ? meta.contentType : null } };
109
+ }
110
+ catch (error) {
111
+ logger.debug(`KV getBinary failed for ${domain}: ${error instanceof Error ? error.message : String(error)}`);
112
+ return null;
113
+ }
114
+ }
115
+ /**
116
+ * Binary write for the `asset` domain. Size is enforced on raw bytes (no
117
+ * base64 inflation) and failures stay fail-soft like `putText`.
118
+ */
119
+ async putBinary(domain, parts, bytes, metadata, options) {
120
+ const ns = this.namespace;
121
+ if (!ns)
122
+ return false;
123
+ if (!(bytes instanceof Uint8Array)) {
124
+ logger.debug(`KV putBinary skipped for ${domain}: value is not bytes.`);
125
+ return false;
126
+ }
127
+ const def = KV_DOMAINS[domain];
128
+ if (bytes.byteLength > def.maxValueBytes) {
129
+ logger.debug(`KV putBinary skipped for ${domain}: value exceeds ${def.maxValueBytes} bytes.`);
130
+ return false;
131
+ }
132
+ if (typeof metadata?.etag !== 'string' || metadata.etag === '') {
133
+ logger.debug(`KV putBinary skipped for ${domain}: missing etag metadata.`);
134
+ return false;
135
+ }
136
+ try {
137
+ const ttl = clampTtl(options?.ttlSeconds, domain);
138
+ const contentType = typeof metadata.contentType === 'string' ? metadata.contentType : null;
139
+ await ns.put(buildKvKey(domain, parts), bytes.slice().buffer, {
140
+ expirationTtl: ttl,
141
+ metadata: { etag: metadata.etag, contentType },
142
+ });
143
+ return true;
144
+ }
145
+ catch (error) {
146
+ logger.debug(`KV putBinary failed for ${domain}: ${error instanceof Error ? error.message : String(error)}`);
147
+ return false;
148
+ }
149
+ }
150
+ async del(domain, parts) {
151
+ const ns = this.namespace;
152
+ if (!ns)
153
+ return;
154
+ try {
155
+ await ns.delete(buildKvKey(domain, parts));
156
+ }
157
+ catch (error) {
158
+ logger.debug(`KV delete failed for ${domain}: ${error instanceof Error ? error.message : String(error)}`);
159
+ }
160
+ }
161
+ /**
162
+ * Delete every key under a prefix, page-capped. Restarts from the start after
163
+ * each page: deleting while a positional cursor advances would skip keys.
164
+ */
165
+ async purgePrefix(domain, parts = []) {
166
+ const ns = this.namespace;
167
+ if (!ns)
168
+ return 0;
169
+ const prefix = parts.length === 0 ? `${domain}:` : buildKvKey(domain, parts);
170
+ let deleted = 0;
171
+ try {
172
+ for (let page = 0; page < PURGE_MAX_PAGES; page += 1) {
173
+ const result = await ns.list({ prefix, limit: PURGE_LIST_LIMIT });
174
+ if (result.keys.length === 0)
175
+ return deleted;
176
+ for (const key of result.keys) {
177
+ await ns.delete(key.name);
178
+ deleted += 1;
179
+ }
180
+ if (result.keys.length < PURGE_LIST_LIMIT)
181
+ return deleted;
182
+ }
183
+ // The cap was hit with a full page still coming back, so keys remain and
184
+ // the caller sees a count indistinguishable from a complete purge. A
185
+ // write-invalidation that silently under-purges serves pre-write bytes
186
+ // for a whole TTL while reporting success, so this is never silent.
187
+ logger.warn(`KV purge for ${domain} hit the ${PURGE_MAX_PAGES * PURGE_LIST_LIMIT}-key page cap; ` +
188
+ `at least one key may survive invalidation`);
189
+ return deleted;
190
+ }
191
+ catch (error) {
192
+ logger.warn(`KV purge for ${domain} failed: ${error instanceof Error ? error.message : String(error)}`);
193
+ return deleted;
194
+ }
195
+ }
196
+ }
197
+ export { KvCache, PURGE_LIST_LIMIT, PURGE_MAX_PAGES };
198
+ //# sourceMappingURL=KvCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KvCache.js","sourceRoot":"","sources":["../../src/kv/KvCache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ declare const KV_MAX_KEY_LENGTH = 512;
2
+ declare const KV_MIN_TTL_SECONDS = 60;
3
+ type KvDomainName = 'session' | 'asset' | 'metadata';
4
+ interface KvDomainDef {
5
+ ttlSeconds: number;
6
+ maxValueBytes: number;
7
+ description: string;
8
+ }
9
+ declare const KV_DOMAINS: Record<KvDomainName, KvDomainDef>;
10
+ /**
11
+ * 128-bit digest built from four independent FNV-1a lanes.
12
+ *
13
+ * Why not a single 32-bit FNV: `buildKvKey` falls back to a digest when a key
14
+ * exceeds the platform's 512-character limit, and the longest keys embed a
15
+ * user-controlled path or name. A 32-bit digest over a shared keyspace is
16
+ * collision-searchable — someone who controls names in their own scope could
17
+ * grind a colliding key and read another tenant's cached bytes. Four lanes over
18
+ * the same bytes with different offsets/primes cost one extra pass and take a
19
+ * birthday collision to ~2^64 work, which is not worth attacking.
20
+ *
21
+ * Synchronous on purpose: `buildKvKey` is called from sync cache-key builders.
22
+ */
23
+ declare function digest128(input: string): string;
24
+ declare function sanitizeSegment(segment: string): string;
25
+ declare function buildKvKey(domain: KvDomainName, parts: readonly string[]): string;
26
+ declare function utf8ByteLength(value: string): number;
27
+ /**
28
+ * Resolve a TTL, defaulting to the domain's own. Every domain declares a
29
+ * required `ttlSeconds`, so there is no "no TTL" case to represent.
30
+ */
31
+ declare function clampTtl(ttlSeconds: number | undefined, domain: KvDomainName): number;
32
+ export { KV_DOMAINS, KV_MAX_KEY_LENGTH, KV_MIN_TTL_SECONDS, buildKvKey, clampTtl, digest128, sanitizeSegment, utf8ByteLength };
33
+ export type { KvDomainDef, KvDomainName };
34
+ //# sourceMappingURL=KvDomains.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KvDomains.d.ts","sourceRoot":"","sources":["../../src/kv/KvDomains.ts"],"names":[],"mappings":"AAiBA,QAAA,MAAM,iBAAiB,MAAM,CAAC;AAC9B,QAAA,MAAM,kBAAkB,KAAK,CAAC;AAE9B,KAAK,YAAY,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;AAErD,UAAU,WAAW;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,QAAA,MAAM,UAAU,EAAE,MAAM,CAAC,YAAY,EAAE,WAAW,CAgBjD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,iBAAS,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAcxC;AAED,iBAAS,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,iBAAS,UAAU,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAc1E;AAED,iBAAS,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;GAGG;AACH,iBAAS,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,MAAM,CAG9E;AAED,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC;AAC/H,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Single-KV keyspace: one `CACHE` binding, domains separated by key prefix.
3
+ *
4
+ * Converged from `backend-runtime/src/kv/KvDomains.ts` (Durable-DAV,
5
+ * Durable-DAV-Router, Edge-Sonic, Mail-Otter — the same table under four sets
6
+ * of names). The domains are generalized to the three the whole family shares;
7
+ * a consumer with a different cache vocabulary renames its call sites (clients
8
+ * adapt) rather than extending the registry, because an open registry is how
9
+ * prefix collisions start.
10
+ *
11
+ * Rationale: D1 and the Durable Object stay the source of truth. KV is a
12
+ * loss-tolerant, read-heavy cache only — a miss or eviction must always be
13
+ * recoverable by recompute. Call sites never touch `env.CACHE` directly; they
14
+ * go through `KvCache` with a closed `KvDomainName` registry so prefixes cannot
15
+ * collide and TTL/size policy lives in one table.
16
+ */
17
+ const KV_KEY_VERSION = 'v1';
18
+ const KV_MAX_KEY_LENGTH = 512;
19
+ const KV_MIN_TTL_SECONDS = 60;
20
+ const KV_DOMAINS = {
21
+ session: {
22
+ ttlSeconds: 300,
23
+ maxValueBytes: 65_536,
24
+ description: 'Per-user session/profile snapshots; invalidated on any credential change.',
25
+ },
26
+ asset: {
27
+ ttlSeconds: 300,
28
+ maxValueBytes: 10_485_760,
29
+ description: 'Binary payloads (file bodies, sealed secrets) with etag/contentType metadata; invalidated on write.',
30
+ },
31
+ metadata: {
32
+ ttlSeconds: 120,
33
+ maxValueBytes: 1_048_576,
34
+ description: 'Listing/derived snapshots keyed by owner or collection; invalidated on mutation.',
35
+ },
36
+ };
37
+ /**
38
+ * 128-bit digest built from four independent FNV-1a lanes.
39
+ *
40
+ * Why not a single 32-bit FNV: `buildKvKey` falls back to a digest when a key
41
+ * exceeds the platform's 512-character limit, and the longest keys embed a
42
+ * user-controlled path or name. A 32-bit digest over a shared keyspace is
43
+ * collision-searchable — someone who controls names in their own scope could
44
+ * grind a colliding key and read another tenant's cached bytes. Four lanes over
45
+ * the same bytes with different offsets/primes cost one extra pass and take a
46
+ * birthday collision to ~2^64 work, which is not worth attacking.
47
+ *
48
+ * Synchronous on purpose: `buildKvKey` is called from sync cache-key builders.
49
+ */
50
+ function digest128(input) {
51
+ const LANES = 4;
52
+ const offsets = [0x81_1c_9d_c5, 0xc2_b2_ae_35, 0x27_d4_eb_2f, 0x16_56_67_b1];
53
+ let out = '';
54
+ for (let lane = 0; lane < LANES; lane += 1) {
55
+ let hash = offsets[lane];
56
+ const prime = 0x01_00_01_93 + lane * 0x9e_37_79_b9;
57
+ for (let i = 0; i < input.length; i += 1) {
58
+ hash ^= input.codePointAt(i) ?? 0;
59
+ hash = Math.imul(hash, prime);
60
+ }
61
+ out += (hash >>> 0).toString(16).padStart(8, '0');
62
+ }
63
+ return out;
64
+ }
65
+ function sanitizeSegment(segment) {
66
+ const trimmed = segment.trim();
67
+ if (!trimmed)
68
+ throw new Error('KV key segment must not be empty.');
69
+ return encodeURIComponent(trimmed);
70
+ }
71
+ function buildKvKey(domain, parts) {
72
+ const def = KV_DOMAINS[domain];
73
+ if (!def)
74
+ throw new Error(`Unknown KV domain: ${domain}.`);
75
+ if (parts.length === 0)
76
+ throw new Error(`KV domain ${domain} requires at least one key part.`);
77
+ const prefix = `${domain}:${KV_KEY_VERSION}:`;
78
+ const joined = parts.map((part) => sanitizeSegment(part)).join(':');
79
+ const full = prefix + joined;
80
+ if (full.length <= KV_MAX_KEY_LENGTH)
81
+ return full;
82
+ // Deterministic overflow form. Note the consequence for invalidation: an
83
+ // overflowing key does not start with the plain prefix, so `purgePrefix` on
84
+ // the parts will not match it. Callers must therefore bound the key material
85
+ // they cache (path length, identifier length) — the digest is a crash guard,
86
+ // not a caching strategy.
87
+ return `${prefix}h:${digest128(joined)}`;
88
+ }
89
+ function utf8ByteLength(value) {
90
+ return new TextEncoder().encode(value).length;
91
+ }
92
+ /**
93
+ * Resolve a TTL, defaulting to the domain's own. Every domain declares a
94
+ * required `ttlSeconds`, so there is no "no TTL" case to represent.
95
+ */
96
+ function clampTtl(ttlSeconds, domain) {
97
+ const effective = ttlSeconds ?? KV_DOMAINS[domain].ttlSeconds;
98
+ return Number.isFinite(effective) ? Math.max(KV_MIN_TTL_SECONDS, Math.floor(effective)) : KV_DOMAINS[domain].ttlSeconds;
99
+ }
100
+ export { KV_DOMAINS, KV_MAX_KEY_LENGTH, KV_MIN_TTL_SECONDS, buildKvKey, clampTtl, digest128, sanitizeSegment, utf8ByteLength };
101
+ //# sourceMappingURL=KvDomains.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KvDomains.js","sourceRoot":"","sources":["../../src/kv/KvDomains.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { KvCache, PURGE_LIST_LIMIT, PURGE_MAX_PAGES } from './KvCache';
2
+ export type { KvBinaryEntry, KvFileMetadata, KvNamespaceLike, KvPutOptions } from './KvCache';
3
+ export { KV_DOMAINS, KV_MAX_KEY_LENGTH, KV_MIN_TTL_SECONDS, buildKvKey, clampTtl, digest128, sanitizeSegment, utf8ByteLength, } from './KvDomains';
4
+ export type { KvDomainDef, KvDomainName } from './KvDomains';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/kv/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACvE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9F,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACV,QAAQ,EACR,SAAS,EACT,eAAe,EACf,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}