@spfn/core 0.3.0-beta.2 → 0.3.0-beta.21

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 (94) hide show
  1. package/README.md +343 -16
  2. package/dist/app-config/index.d.ts +134 -0
  3. package/dist/app-config/index.js +58 -0
  4. package/dist/app-config/index.js.map +1 -0
  5. package/dist/authz/index.js +1 -381
  6. package/dist/authz/index.js.map +1 -1
  7. package/dist/{boss-D16fO2oG.d.ts → boss-BiDCbbAQ.d.ts} +60 -7
  8. package/dist/codegen/index.d.ts +93 -15
  9. package/dist/codegen/index.js +626 -165
  10. package/dist/codegen/index.js.map +1 -1
  11. package/dist/config/index.d.ts +0 -63
  12. package/dist/config/index.js +15 -15
  13. package/dist/config/index.js.map +1 -1
  14. package/dist/contract/index.d.ts +40 -17
  15. package/dist/contract/index.js +147 -23
  16. package/dist/contract/index.js.map +1 -1
  17. package/dist/db/index.d.ts +201 -34
  18. package/dist/db/index.js +294 -158
  19. package/dist/db/index.js.map +1 -1
  20. package/dist/{define-middleware-DfDP39Nq.d.ts → define-middleware-CVKgqo8S.d.ts} +1 -1
  21. package/dist/env/index.d.ts +6 -442
  22. package/dist/env/index.js +13 -16
  23. package/dist/env/index.js.map +1 -1
  24. package/dist/env/loader.js +24 -1
  25. package/dist/env/loader.js.map +1 -1
  26. package/dist/error-TsUIjp_x.d.ts +157 -0
  27. package/dist/errors/index.js +1 -381
  28. package/dist/errors/index.js.map +1 -1
  29. package/dist/event/index.d.ts +5 -5
  30. package/dist/event/index.js +32 -1
  31. package/dist/event/index.js.map +1 -1
  32. package/dist/event/sse/client.d.ts +18 -4
  33. package/dist/event/sse/client.js +38 -2
  34. package/dist/event/sse/client.js.map +1 -1
  35. package/dist/event/sse/index.d.ts +5 -4
  36. package/dist/event/ws/client.d.ts +3 -2
  37. package/dist/event/ws/index.d.ts +4 -3
  38. package/dist/job/index.d.ts +48 -3
  39. package/dist/job/index.js +131 -52
  40. package/dist/job/index.js.map +1 -1
  41. package/dist/logger/index.js +0 -12
  42. package/dist/logger/index.js.map +1 -1
  43. package/dist/middleware/index.d.ts +6 -2
  44. package/dist/middleware/index.js +7 -388
  45. package/dist/middleware/index.js.map +1 -1
  46. package/dist/nextjs/index.d.ts +88 -8
  47. package/dist/nextjs/index.js +138 -62
  48. package/dist/nextjs/index.js.map +1 -1
  49. package/dist/nextjs/server.d.ts +45 -7
  50. package/dist/nextjs/server.js +14 -0
  51. package/dist/nextjs/server.js.map +1 -1
  52. package/dist/ops/index.d.ts +62 -43
  53. package/dist/ops/index.js +638 -97
  54. package/dist/ops/index.js.map +1 -1
  55. package/dist/registry-C2KQCh0m.d.ts +473 -0
  56. package/dist/route/index.d.ts +6 -3
  57. package/dist/route/index.js +97 -28
  58. package/dist/route/index.js.map +1 -1
  59. package/dist/route/types.d.ts +7 -1
  60. package/dist/{router-Qbssr11H.d.ts → route-builder-DRW-MSCJ.d.ts} +52 -127
  61. package/dist/router-4YoIy5fN.d.ts +172 -0
  62. package/dist/{token-manager-BT5EnUAR.d.ts → router-CysBmgv3.d.ts} +81 -127
  63. package/dist/server/index.d.ts +185 -14
  64. package/dist/server/index.js +883 -78
  65. package/dist/server/index.js.map +1 -1
  66. package/dist/surface/index.d.ts +115 -0
  67. package/dist/surface/index.js +1003 -0
  68. package/dist/surface/index.js.map +1 -0
  69. package/dist/token-manager-Ckjt2-qq.d.ts +122 -0
  70. package/dist/{types-D1c57Ko-.d.ts → types-5Rl11_Nb.d.ts} +43 -4
  71. package/dist/{types-ZQODsBft.d.ts → types-B8zAxFsE.d.ts} +2 -1
  72. package/dist/types-CxhPH4s_.d.ts +375 -0
  73. package/dist/{types-2AbaW4Ie.d.ts → types-W4u_S0lF.d.ts} +4 -3
  74. package/docs/file-upload.md +195 -333
  75. package/package.json +20 -9
  76. package/src/cache/README.md +330 -0
  77. package/src/codegen/README.md +634 -0
  78. package/src/config/README.md +333 -0
  79. package/src/contract/README.md +440 -0
  80. package/src/db/README.md +589 -0
  81. package/src/db/manager/README.md +500 -0
  82. package/src/db/schema/README.md +354 -0
  83. package/src/db/transaction/README.md +822 -0
  84. package/src/env/README.md +787 -0
  85. package/src/errors/README.md +429 -0
  86. package/src/event/README.md +789 -0
  87. package/src/job/README.md +613 -0
  88. package/src/logger/README.md +321 -0
  89. package/src/middleware/README.md +634 -0
  90. package/src/nextjs/README.md +688 -0
  91. package/src/route/README.md +763 -0
  92. package/src/security/README.md +100 -0
  93. package/src/server/README.md +704 -0
  94. package/dist/types-9oszaJqp.d.ts +0 -250
@@ -0,0 +1,333 @@
1
+ # @spfn/core/config — @spfn/core's own validated env config
2
+
3
+ A **prebuilt env registry** for the variables `@spfn/core` itself consumes (database,
4
+ cache, server, fetch, Next.js integration). It is a thin application of `@spfn/core/env`:
5
+ it owns one fixed schema (`coreEnvSchema`), builds a registry from it, and exports the
6
+ validated proxy. It does **not** add new env machinery — for the schema DSL, parsers,
7
+ registry semantics, and file loading, see [@spfn/core/env](../env/README.md).
8
+
9
+ ## Import paths
10
+
11
+ ```typescript
12
+ // The validated proxy + the schema + the registry — all from one entry point.
13
+ import { env, envSchema, registry } from '@spfn/core/config';
14
+ ```
15
+
16
+ There is a single entry point (`@spfn/core/config`). It re-exports nothing from
17
+ `@spfn/core/env`; if you need `defineEnvSchema`, parsers, `loadEnv`, type guards, etc.,
18
+ import them from `@spfn/core/env` / `@spfn/core/env/loader` directly.
19
+
20
+ ---
21
+
22
+ ## Public API (complete)
23
+
24
+ The module exports exactly three values:
25
+
26
+ | Export | Type | What it is |
27
+ |--------|------|------------|
28
+ | `env` | `InferEnvType<typeof coreEnvSchema>` (a **Proxy**) | The validated config object. Read variables off it. |
29
+ | `envSchema` | `EnvSchemaCollection` | The schema object (`coreEnvSchema`), re-exported under the name `envSchema`. Read `.description` / `.default` / `.examples` per key. |
30
+ | `registry` | `EnvRegistry` | The `EnvRegistry` instance backing `env` (`createEnvRegistry(coreEnvSchema)`). Exposes `.validate()`, `.validateAll()`, `.reset()`, `.register(...)`. |
31
+
32
+ `env` is literally `registry.validate()` — they are the same proxy.
33
+
34
+ > **No** `getEnv` / `loadConfig` / `config()` / `getConfig` function exists, and the schema
35
+ > is **not** something you pass in — it is fixed. To define your *own* app's variables,
36
+ > build a separate schema/registry with `@spfn/core/env`; do not try to extend
37
+ > `coreEnvSchema` by mutation.
38
+
39
+ ---
40
+
41
+ ## Quick Start
42
+
43
+ ```typescript
44
+ import { env } from '@spfn/core/config';
45
+
46
+ const poolMax: number = env.DB_POOL_MAX; // number (default 10)
47
+ const logLevel = env.SPFN_LOG_LEVEL; // 'debug' | 'info' | 'warn' | 'error' | 'fatal'
48
+ const apiUrl: string = env.SPFN_API_URL; // string (required)
49
+ const appUrl = env.SPFN_APP_URL; // string | undefined (optional)
50
+ ```
51
+
52
+ In the **SPFN server** entry point, call `loadEnv()` (from `@spfn/core/env/loader`)
53
+ **before** reading any value, so `process.env` is populated from the `.env*` files:
54
+
55
+ ```typescript
56
+ import { loadEnv } from '@spfn/core/env/loader';
57
+ import { env } from '@spfn/core/config';
58
+
59
+ loadEnv(); // populate process.env (server reads .env.server too)
60
+ console.log(env.PORT); // validated lazily, here
61
+ ```
62
+
63
+ In **Next.js** code, do **not** call `loadEnv()` — Next.js loads `.env*` itself. Just read
64
+ from `env`.
65
+
66
+ ---
67
+
68
+ ## Validation is lazy (read this before relying on startup checks)
69
+
70
+ `env` is the Proxy returned by `registry.validate()`. Importing `@spfn/core/config` does
71
+ **not** read or validate any variable value. Each variable is read from `process.env`,
72
+ defaulted, and run through its `validator` **at the moment you access the property**.
73
+
74
+ ```typescript
75
+ import { env } from '@spfn/core/config'; // imports the proxy — validates nothing yet
76
+ import { loadEnv } from '@spfn/core/env/loader';
77
+
78
+ loadEnv();
79
+ env.SPFN_API_URL; // read + validated HERE; throws if missing (required) or invalid
80
+ ```
81
+
82
+ Consequences:
83
+
84
+ - A missing **required** variable (`SPFN_API_URL`) does not
85
+ throw on import — it throws on first access (`Error('Environment validation failed')`).
86
+ - You may import `env` before `loadEnv()` runs; values are picked up lazily.
87
+ - For an **eager** all-at-once check (CI / startup gate), call `registry.validateAll()`
88
+ (returns `{ errors, warnings }`) or pass `registry` to `validateAllEnv([...])` from
89
+ `@spfn/core/env`. `SKIP_ENV_VALIDATION=true` skips only the lazy `required` check, not
90
+ these eager checks.
91
+ - **The SPFN server runs that eager check at boot.** `startServer` validates this
92
+ registry — plus any registries the app hands over with
93
+ `defineServerConfig().env({ registries })` — before it listens, and exits with code 1
94
+ listing each failing key (never a value). See
95
+ [Boot-time check](../env/README.md#boot-time-check-the-server).
96
+
97
+ See [@spfn/core/env](../env/README.md) for the full registry / proxy / `SKIP_ENV_VALIDATION`
98
+ semantics — this module inherits all of it unchanged.
99
+
100
+ ---
101
+
102
+ ## Reading schema metadata
103
+
104
+ ```typescript
105
+ import { envSchema } from '@spfn/core/config';
106
+
107
+ envSchema.DB_POOL_MAX.description; // 'Maximum number of database connections in pool'
108
+ envSchema.DB_POOL_MAX.default; // 10
109
+ envSchema.DB_POOL_MAX.examples; // [10, 20, 50]
110
+ ```
111
+
112
+ `envSchema` is the schema object, not the values. Use `env` for actual (validated) values.
113
+
114
+ ---
115
+
116
+ ## Environment variables (the fixed `coreEnvSchema`)
117
+
118
+ All variables below are exactly what `coreEnvSchema` declares. URL variables marked with a
119
+ validator **throw on access** if the value is present but malformed.
120
+
121
+ ### Core
122
+
123
+ | Variable | Type | Default | Notes |
124
+ |----------|------|---------|-------|
125
+ | `NODE_ENV` | `'local' \| 'development' \| 'staging' \| 'production' \| 'test'` | `'local'` | `nextjs: true` |
126
+
127
+ ### Database — connection URLs (all optional, all `sensitive`, validated as postgres URLs)
128
+
129
+ | Variable | Type | Required | Validator |
130
+ |----------|------|----------|-----------|
131
+ | `DATABASE_URL` | `string` | No | `parsePostgresUrl` |
132
+ | `DATABASE_WRITE_URL` | `string` | No | `parsePostgresUrl` |
133
+ | `DATABASE_READ_URL` | `string` | No | `parsePostgresUrl` |
134
+
135
+ ### Database — connection pool
136
+
137
+ | Variable | Type | Default |
138
+ |----------|------|---------|
139
+ | `DB_POOL_MAX` | `number` | `10` |
140
+ | `DB_POOL_IDLE_TIMEOUT` | `number` | `30` (seconds) |
141
+
142
+ ### Database — retry
143
+
144
+ | Variable | Type | Default |
145
+ |----------|------|---------|
146
+ | `DB_RETRY_MAX` | `number` | `3` |
147
+ | `DB_RETRY_INITIAL_DELAY` | `number` | `100` (ms) |
148
+ | `DB_RETRY_MAX_DELAY` | `number` | `10000` (ms) |
149
+ | `DB_RETRY_FACTOR` | `number` | `2` |
150
+
151
+ ### Database — health check
152
+
153
+ | Variable | Type | Default |
154
+ |----------|------|---------|
155
+ | `DB_HEALTH_CHECK_ENABLED` | `boolean` | `true` |
156
+ | `DB_HEALTH_CHECK_INTERVAL` | `number` | `60000` (ms) |
157
+ | `DB_HEALTH_CHECK_RECONNECT` | `boolean` | `true` |
158
+ | `DB_HEALTH_CHECK_MAX_RETRIES` | `number` | `3` |
159
+ | `DB_HEALTH_CHECK_RETRY_INTERVAL` | `number` | `5000` (ms) |
160
+
161
+ ### Database — monitoring
162
+
163
+ | Variable | Type | Default |
164
+ |----------|------|---------|
165
+ | `DB_MONITORING_ENABLED` | `boolean` | `false` |
166
+ | `DB_MONITORING_SLOW_THRESHOLD` | `number` | `1000` (ms) |
167
+ | `DB_MONITORING_LOG_QUERIES` | `boolean` | `false` |
168
+
169
+ ### Database — transaction / development
170
+
171
+ | Variable | Type | Default |
172
+ |----------|------|---------|
173
+ | `TRANSACTION_TIMEOUT` | `number` | `30000` (ms) |
174
+ | `DB_DEBUG_TRACE` | `boolean` | `false` |
175
+
176
+ ### Drizzle ORM
177
+
178
+ | Variable | Type | Default |
179
+ |----------|------|---------|
180
+ | `DRIZZLE_SCHEMA_PATH` | `string` | `'./src/server/entities/config.ts'` |
181
+ | `DRIZZLE_OUT_DIR` | `string` | `'./drizzle'` |
182
+
183
+ ### Logger
184
+
185
+ | Variable | Type | Default |
186
+ |----------|------|---------|
187
+ | `SPFN_LOG_LEVEL` | `'debug' \| 'info' \| 'warn' \| 'error' \| 'fatal'` | `'info'` |
188
+
189
+ ### Cache (Redis/Valkey) — all optional
190
+
191
+ | Variable | Type | `sensitive` | Validator |
192
+ |----------|------|-------------|-----------|
193
+ | `CACHE_URL` | `string` | yes | `parseRedisUrl` |
194
+ | `CACHE_WRITE_URL` | `string` | yes | `parseRedisUrl` |
195
+ | `CACHE_READ_URL` | `string` | yes | `parseRedisUrl` |
196
+ | `CACHE_SENTINEL_HOSTS` | `string` | no | — (comma-separated hosts) |
197
+ | `CACHE_CLUSTER_NODES` | `string` | no | — (comma-separated nodes) |
198
+ | `CACHE_MASTER_NAME` | `string` | no | — (Sentinel master name) |
199
+ | `CACHE_PASSWORD` | `string` | yes | — |
200
+ | `CACHE_TLS_REJECT_UNAUTHORIZED` | `boolean` | — | default `true` |
201
+
202
+ > These four patterns are mutually exclusive at the connection layer (single / master-replica
203
+ > / sentinel / cluster) — the schema does not enforce that; it only validates individual values.
204
+
205
+ ### Server
206
+
207
+ | Variable | Type | Default |
208
+ |----------|------|---------|
209
+ | `PORT` | `number` | `4000` |
210
+ | `HOST` | `string` | `'localhost'` |
211
+ | `SERVER_TIMEOUT` | `number` | `120000` (ms) |
212
+ | `SERVER_KEEPALIVE_TIMEOUT` | `number` | `65000` (ms) |
213
+ | `SERVER_HEADERS_TIMEOUT` | `number` | `60000` (ms) |
214
+ | `SHUTDOWN_TIMEOUT` | `number` | `280000` (ms) — must be < k8s `terminationGracePeriodSeconds` minus preStop sleep |
215
+
216
+ ### Fetch (outbound HTTP via Node `undici`)
217
+
218
+ | Variable | Type | Default |
219
+ |----------|------|---------|
220
+ | `FETCH_CONNECT_TIMEOUT` | `number` | `10000` (ms) |
221
+ | `FETCH_HEADERS_TIMEOUT` | `number` | `300000` (ms) |
222
+ | `FETCH_BODY_TIMEOUT` | `number` | `300000` (ms) |
223
+
224
+ ### Next.js integration
225
+
226
+ | Variable | Type | Required | Validator | Notes |
227
+ |----------|------|----------|-----------|-------|
228
+ | `SPFN_API_URL` | `string` (`envUrl`) | **Yes** | URL | `nextjs: true`; Next.js → backend |
229
+ | `NEXT_PUBLIC_SPFN_API_URL` | `string` (`envUrl`) | No | URL | `nextjs: true`; client-exposed, inlined by Next.js at build; the server uses `SPFN_API_URL` |
230
+ | `SPFN_APP_URL` | `string` (`envUrl`) | No (no default) | URL | `nextjs: true`; SPFN server → Next.js |
231
+ | `RPC_PROXY_TIMEOUT` | `number` | No (default `120000`) | — | `nextjs: true`; keep < `FETCH_HEADERS_TIMEOUT` |
232
+
233
+ > `SPFN_API_URL` is **required** — a missing or non-URL value throws on first access.
234
+ > `NEXT_PUBLIC_SPFN_API_URL` is optional to the server, which falls back to `SPFN_API_URL`;
235
+ > set it wherever browser code needs the API URL. These three URLs are declared by
236
+ > `@spfn/core` only — packages read them from `@spfn/core/config` rather than declaring them again.
237
+
238
+ ---
239
+
240
+ ## Pitfalls & anti-patterns
241
+
242
+ - **Don't expect validation on import.** Importing `@spfn/core/config` validates nothing;
243
+ the proxy validates each variable on property access. A missing `SPFN_API_URL` throws
244
+ when you *read* it, not when you import `env`. The SPFN server does check it eagerly at
245
+ boot (`startServer` / `createServerlessApp`), so a server process needs it set.
246
+ - **The three URLs are validated as URLs (`envUrl`).** A non-URL string in `SPFN_API_URL`,
247
+ `NEXT_PUBLIC_SPFN_API_URL` or `SPFN_APP_URL` fails on access and at boot.
248
+ - **Don't call `loadEnv()` inside Next.js.** Next.js already loads `.env*`. Calling it there
249
+ (with `server: true`) would pull `.env.server` secrets into the Next.js process. In the
250
+ **SPFN server** entry point you *do* call `loadEnv()` before touching `env`.
251
+ - **The schema is fixed — don't try to add app variables here.** `coreEnvSchema` covers only
252
+ `@spfn/core`'s own variables. For your application's variables, define a separate schema
253
+ and registry via `@spfn/core/env` (`defineEnvSchema` + `createEnvRegistry`); validate both
254
+ registries together with `validateAllEnv([coreRegistry, appRegistry])`.
255
+ - **`env` vs `envSchema`.** `env.DB_POOL_MAX` is the resolved value (`number`);
256
+ `envSchema.DB_POOL_MAX` is the schema entry (read `.default` / `.description` / `.examples`).
257
+ Don't read values off `envSchema` or metadata off `env`.
258
+ - **`CACHE_*` / `DATABASE_*` URLs are validated.** A present-but-malformed value throws via
259
+ `parseRedisUrl` / `parsePostgresUrl` on access — these are not free-form strings.
260
+ - **No config-specific loader.** This module re-exports nothing from `@spfn/core/env`. Import
261
+ `loadEnv` from `@spfn/core/env/loader`, parsers/guards from `@spfn/core/env`.
262
+
263
+ ---
264
+
265
+ ## Complete example
266
+
267
+ ```typescript
268
+ // SPFN server entry point
269
+ import { loadEnv } from '@spfn/core/env/loader';
270
+ import { env, registry } from '@spfn/core/config';
271
+
272
+ loadEnv(); // populate process.env from .env* (server includes .env.server)
273
+
274
+ // Eager gate for a custom entry point — startServer() already runs this check
275
+ // (see "Boot-time check" in the env README), so an SPFN server needs none of it.
276
+ const { errors, warnings } = registry.validateAll();
277
+ for (const w of warnings) console.warn(`${w.key}: ${w.message}`);
278
+ if (errors.length)
279
+ {
280
+ for (const e of errors) console.error(`${e.key}: ${e.message}`);
281
+ process.exit(1);
282
+ }
283
+
284
+ // Lazily-validated reads:
285
+ const server =
286
+ {
287
+ port: env.PORT, // 4000 (default)
288
+ host: env.HOST, // 'localhost' (default)
289
+ requestTimeout: env.SERVER_TIMEOUT, // 120000 (default)
290
+ };
291
+
292
+ const db =
293
+ {
294
+ url: env.DATABASE_URL, // string | undefined
295
+ poolMax: env.DB_POOL_MAX, // 10 (default)
296
+ monitoring: env.DB_MONITORING_ENABLED // false (default)
297
+ };
298
+
299
+ const apiUrl = env.SPFN_API_URL; // required URL — throws if unset/invalid
300
+ ```
301
+
302
+ ```typescript
303
+ // Next.js — no loadEnv()
304
+ import { env } from '@spfn/core/config';
305
+
306
+ const apiUrl = env.NEXT_PUBLIC_SPFN_API_URL; // optional URL, client-exposed
307
+ ```
308
+
309
+ ```typescript
310
+ // Reset the validated flag (tests only)
311
+ import { registry } from '@spfn/core/config';
312
+ registry.reset();
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Types reference
318
+
319
+ ```typescript
320
+ // env's element types come straight from coreEnvSchema via InferEnvType:
321
+ type NodeEnv = 'local' | 'development' | 'staging' | 'production' | 'test';
322
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
323
+ // `typeof env` = InferEnvType<typeof coreEnvSchema> (see @spfn/core/env)
324
+ ```
325
+
326
+ ## Related
327
+
328
+ - [@spfn/core/env](../env/README.md) — the schema DSL, parsers, registry, type guards, and
329
+ `loadEnv` that this module is built on. **Read this for everything not specific to
330
+ `coreEnvSchema`.**
331
+ - [@spfn/core/logger](../logger/README.md) — consumes `SPFN_LOG_LEVEL`.
332
+ - [@spfn/core/db](../db/README.md) — consumes the `DATABASE_*` / `DB_*` variables.
333
+ - [@spfn/core](../../README.md) — main package documentation.