@rexezuge/runtime 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # @rexezuge/runtime
2
+
3
+ DI container, configuration, worker bases, logger, and KV cache for Cloudflare Workers.
4
+
5
+ Zero dependencies (the one platform import is type-only, resolved through each consumer's generated wrangler types).
6
+
7
+ ```bash
8
+ pnpm add @rexezuge/runtime
9
+ ```
10
+
11
+ ---
12
+
13
+ ## 1. DI — `Container` / `Token` / `RequestScope`
14
+
15
+ Minimal by design: factory + singleton scopes, branded tokens for inference, nothing else.
16
+
17
+ ```ts
18
+ import { Container, type Token } from '@rexezuge/runtime';
19
+
20
+ interface Mailer { send(to: string): Promise<void> }
21
+ const MailerToken: Token<Mailer> = 'Mailer';
22
+
23
+ const root = new Container().bindValue(MailerToken, new SmtpMailer());
24
+ const mailer = root.get(MailerToken); // Mailer, no explicit generic needed
25
+ ```
26
+
27
+ Composition roots wire implementations once; request handlers resolve via `asScopedContext` / `getRequestScope` rather than passing a container through business logic.
28
+
29
+ ## 2. Config — `AppConfiguration` / `EnvParser`
30
+
31
+ Consumers subclass `AppConfiguration` and compose their own typed sections (limits, auth, replication), each reading env once through `EnvParser`:
32
+
33
+ ```ts
34
+ import { AppConfiguration, EnvParser } from '@rexezuge/runtime';
35
+
36
+ class MyConfig extends AppConfiguration {
37
+ public getPageSize(): number {
38
+ return EnvParser.number(this.raw(), 'PAGE_SIZE', 50);
39
+ }
40
+ }
41
+
42
+ const config = MyConfig.fromEnv(env);
43
+ for (const warning of config.validate()) console.warn(warning); // warnings, never throws
44
+ ```
45
+
46
+ `getSiteUrl()` strips trailing slashes so callers can concatenate paths. `validate()` returns startup warnings (e.g. `DEMO_MODE` in production) instead of throwing a 500 on a request path.
47
+
48
+ ## 3. Worker bases
49
+
50
+ | Base | Extends for |
51
+ | --- | --- |
52
+ | `AbstractEntrypointWorker` | HTTP + cron entrypoints (`onRequest` / `onScheduled`) |
53
+ | `AbstractDurableObjectWorker` | Durable Object workers |
54
+ | `AbstractQueueWorker` | Queue consumers |
55
+ | `AbstractWorkflowWorker` | Workflows |
56
+
57
+ `fetch()` / `scheduled()` log-and-contain: an unhandled throw is a 500 response or a logged cron failure, never a platform crash. There is deliberately no `/__scheduled` HTTP branch — cron runs through the wrangler `triggers.crons` binding. The structural `WorkerExecutionContext` / `WorkerScheduledController` types mean this package typechecks without `@cloudflare/workers-types`.
58
+
59
+ ## 4. Logger and KV cache
60
+
61
+ ```ts
62
+ import { createLogger, KvCache } from '@rexezuge/runtime';
63
+
64
+ const logger = createLogger('Sync', 'my-repo', env); // level from env.LOG_LEVEL ?? process.env, defaults to info
65
+ logger.info('starting sync');
66
+
67
+ const cache = new KvCache(env.CACHE); // fail-soft: reads null, writes false, D1/DO stay authoritative
68
+ await cache.putText('session', 'sess:abc', 'payload', { ttlSeconds: 300 });
69
+ ```
70
+
71
+ `KV_DOMAINS` / `buildKvKey` / `clampTtl` keep every repo's key shapes and TTL ceilings identical.
72
+
73
+ ---
74
+
75
+ ## Verifying this package
76
+
77
+ ```bash
78
+ pnpm --filter @rexezuge/runtime exec tsc -p tsconfig.json
79
+ pnpm --filter @rexezuge/runtime typecheck
80
+ pnpm --filter @rexezuge/runtime test
81
+ ```
@@ -22,6 +22,15 @@ declare class EnvParser {
22
22
  */
23
23
  static isValidPositiveInt(env: unknown, key: string): boolean;
24
24
  static string(env: unknown, key: string, defaultValue: string): string;
25
+ /**
26
+ * A non-negative integer, zero included.
27
+ *
28
+ * Distinct from `positiveInt` because zero is a meaningful configuration for
29
+ * retention and quota knobs ("keep nothing"), and a parser that rejected it
30
+ * would silently substitute the default — so setting `0` in the dashboard
31
+ * would appear to do nothing at all.
32
+ */
33
+ static nonNegativeInt(env: unknown, key: string, defaultValue: string): number;
25
34
  static boolean(env: unknown, key: string, defaultValue: string): boolean;
26
35
  /** The raw value, or undefined when unset. Public because presence checks (validate) need it. */
27
36
  static readString(env: unknown, key: string): string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"EnvParser.d.ts","sourceRoot":"","sources":["../../src/config/EnvParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,cAAM,SAAS;WACC,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;IAMlF;;;;;;;OAOG;WACW,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;WAOtD,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;WAI/D,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO;IAI/E,iGAAiG;WACnF,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAGxE;AAED,OAAO,EAAE,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"EnvParser.d.ts","sourceRoot":"","sources":["../../src/config/EnvParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,cAAM,SAAS;WACC,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;IAMlF;;;;;;;OAOG;WACW,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;WAOtD,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;IAI7E;;;;;;;OAOG;WACW,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;WAMvE,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO;IAI/E,iGAAiG;WACnF,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAGxE;AAED,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -34,6 +34,19 @@ class EnvParser {
34
34
  static string(env, key, defaultValue) {
35
35
  return this.readString(env, key) ?? defaultValue;
36
36
  }
37
+ /**
38
+ * A non-negative integer, zero included.
39
+ *
40
+ * Distinct from `positiveInt` because zero is a meaningful configuration for
41
+ * retention and quota knobs ("keep nothing"), and a parser that rejected it
42
+ * would silently substitute the default — so setting `0` in the dashboard
43
+ * would appear to do nothing at all.
44
+ */
45
+ static nonNegativeInt(env, key, defaultValue) {
46
+ const value = this.readString(env, key);
47
+ const parsed = Number(value ?? defaultValue);
48
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : Number(defaultValue);
49
+ }
37
50
  static boolean(env, key, defaultValue) {
38
51
  return (this.readString(env, key) ?? defaultValue) === 'true';
39
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"EnvParser.js","sourceRoot":"","sources":["../../src/config/EnvParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,SAAS;IACN,MAAM,CAAC,WAAW,CAAC,GAAY,EAAE,GAAW,EAAE,YAAoB;QACvE,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI,YAAY,CAAC,CAAC;QAC7C,OAAO,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IACpF,CAAC;IAED;;;;;;;OAOG;IACI,MAAM,CAAC,kBAAkB,CAAC,GAAY,EAAE,GAAW;QACxD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACxC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACrC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7B,OAAO,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC;IACpD,CAAC;IAEM,MAAM,CAAC,MAAM,CAAC,GAAY,EAAE,GAAW,EAAE,YAAoB;QAClE,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,YAAY,CAAC;IACnD,CAAC;IAEM,MAAM,CAAC,OAAO,CAAC,GAAY,EAAE,GAAW,EAAE,YAAoB;QACnE,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,YAAY,CAAC,KAAK,MAAM,CAAC;IAChE,CAAC;IAED,iGAAiG;IAC1F,MAAM,CAAC,UAAU,CAAC,GAAY,EAAE,GAAW;QAChD,OAAQ,GAA0C,CAAC,GAAG,CAAC,CAAC;IAC1D,CAAC;CACF;AAED,OAAO,EAAE,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"EnvParser.js","sourceRoot":"","sources":["../../src/config/EnvParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,SAAS;IACN,MAAM,CAAC,WAAW,CAAC,GAAY,EAAE,GAAW,EAAE,YAAoB;QACvE,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI,YAAY,CAAC,CAAC;QAC7C,OAAO,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IACpF,CAAC;IAED;;;;;;;OAOG;IACI,MAAM,CAAC,kBAAkB,CAAC,GAAY,EAAE,GAAW;QACxD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACxC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACrC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7B,OAAO,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC;IACpD,CAAC;IAEM,MAAM,CAAC,MAAM,CAAC,GAAY,EAAE,GAAW,EAAE,YAAoB;QAClE,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,YAAY,CAAC;IACnD,CAAC;IAED;;;;;;;OAOG;IACI,MAAM,CAAC,cAAc,CAAC,GAAY,EAAE,GAAW,EAAE,YAAoB;QAC1E,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI,YAAY,CAAC,CAAC;QAC7C,OAAO,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IACrF,CAAC;IAEM,MAAM,CAAC,OAAO,CAAC,GAAY,EAAE,GAAW,EAAE,YAAoB;QACnE,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,YAAY,CAAC,KAAK,MAAM,CAAC;IAChE,CAAC;IAED,iGAAiG;IAC1F,MAAM,CAAC,UAAU,CAAC,GAAY,EAAE,GAAW;QAChD,OAAQ,GAA0C,CAAC,GAAG,CAAC,CAAC;IAC1D,CAAC;CACF;AAED,OAAO,EAAE,SAAS,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rexezuge/runtime",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,6 +29,7 @@
29
29
  "typescript": "6.0.3",
30
30
  "vitest": "4.1.11"
31
31
  },
32
+ "description": "DI container, configuration, worker bases, logger, and KV cache for Cloudflare Workers.",
32
33
  "scripts": {
33
34
  "build": "tsc -p tsconfig.json",
34
35
  "typecheck": "tsc -p tsconfig.test.json --noEmit",