@luckystack/core 0.6.7 → 0.7.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 (42) hide show
  1. package/CHANGELOG.md +148 -107
  2. package/CLAUDE.md +245 -245
  3. package/LICENSE +21 -21
  4. package/README.md +93 -93
  5. package/dist/{chunk-WFABOO6J.js → chunk-AFG7KKMS.js} +3 -3
  6. package/dist/chunk-AFG7KKMS.js.map +1 -0
  7. package/dist/{chunk-3AWP2YEA.js → chunk-J3UOWYT4.js} +2 -28
  8. package/dist/chunk-J3UOWYT4.js.map +1 -0
  9. package/dist/{chunk-LU3IPFTR.js → chunk-LTCDQU5Z.js} +1 -1
  10. package/dist/chunk-LTCDQU5Z.js.map +1 -0
  11. package/dist/chunk-R7IRJFVC.js +67 -0
  12. package/dist/chunk-R7IRJFVC.js.map +1 -0
  13. package/dist/client.d.ts +3 -2
  14. package/dist/client.js +4 -4
  15. package/dist/client.js.map +1 -1
  16. package/dist/config-DZCzBNwP.d.ts +122 -0
  17. package/dist/config.d.ts +2 -0
  18. package/dist/config.js +31 -0
  19. package/dist/eslint/index.js.map +1 -1
  20. package/dist/index.d.ts +21 -149
  21. package/dist/index.js +66 -70
  22. package/dist/index.js.map +1 -1
  23. package/dist/pageRouteValidation-sJJCX-YV.d.ts +270 -0
  24. package/dist/{projectConfig-XL5VB76A.js → projectConfig-DBLSQ3NY.js} +2 -2
  25. package/dist/{sentrySetup-4NTWLN2A.js → sentrySetup-MF6JSVWA.js} +3 -3
  26. package/dist/sentrySetup-MF6JSVWA.js.map +1 -0
  27. package/dist/{pageRouteValidation-gqktrJci.d.ts → sessionTypes-Dzen_rPK.d.ts} +23 -265
  28. package/docs/app-bootstrap.md +418 -418
  29. package/docs/config-registry.md +348 -348
  30. package/docs/csrf-config.md +88 -88
  31. package/docs/error-tracker-registry.md +194 -174
  32. package/docs/hooks.md +298 -298
  33. package/docs/rate-limit-strategy.md +170 -170
  34. package/docs/redis-adapter.md +111 -111
  35. package/docs/session-types.md +180 -180
  36. package/docs/socket-bootstrap.md +366 -366
  37. package/package.json +6 -2
  38. package/dist/chunk-3AWP2YEA.js.map +0 -1
  39. package/dist/chunk-LU3IPFTR.js.map +0 -1
  40. package/dist/chunk-WFABOO6J.js.map +0 -1
  41. /package/dist/{projectConfig-XL5VB76A.js.map → config.js.map} +0 -0
  42. /package/dist/{sentrySetup-4NTWLN2A.js.map → projectConfig-DBLSQ3NY.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,107 +1,148 @@
1
- # Changelog
2
-
3
- All notable changes to `@luckystack/core` are documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [Unreleased]
9
-
10
- ### Added
11
-
12
- - **Timestamps in the built-in loggers.** The default `console.*` logger and
13
- `createDevLogger` now prefix each line with an ISO-8601 UTC timestamp
14
- (`[2026-07-13T15:20:01.123Z] Connected to Redis`), controlled by a new
15
- `logging.timestamps` config key (default `true`; set `false` under a log
16
- aggregator that stamps its own time). Only the message is prefixed — context /
17
- error args stay separate. A registered custom logger owns its own formatting.
18
- - **Decoupled secrets-resolved hook** (ADR 0026): `notifySecretsResolved(changedKeys?)`
19
- + `registerSecretsResolvedListener(fn)`. A secret resolver (e.g.
20
- `@luckystack/secret-manager` via `onApplied: notifySecretsResolved`) fires it after
21
- overwriting `process.env`; `redis.ts` self-registers a listener that EAGERLY REBUILDS +
22
- registers the default client when a `REDIS_` credential changed, so Redis auth via a
23
- secret-manager POINTER survives boot AND rotation with no hand-wiring. Generic — Prisma
24
- pools / SDK clients can subscribe too. Also adds `rebuildDefaultRedisClient()` and an
25
- optional structural `secretManager?` field on `ProjectConfig` (`SecretManagerConfigRef`)
26
- so the server boot can detect a resolver.
27
-
28
- ### Fixed
29
-
30
- - **Redis secret-manager pointer boot** (ADR 0026): the default Redis client no longer
31
- fails auth with a baked-in `REDIS_PASSWORD_V<n>` pointer when it was built (during an
32
- early import) before secrets resolved. The framework EAGERLY REBUILDS + registers a
33
- fresh client from the resolved env (`rebuildDefaultRedisClient()`), so the boot-UUID
34
- write authenticates with the real password — no consumer code. **Correction over
35
- 0.6.3/0.6.4:** the rebuild logic was right but never TRIGGERED for a normal project (the
36
- server-boot gate `getProjectConfig().secretManager?.url` is falsy — the scaffold doesn't
37
- register `secretManager` into `projectConfig` — and bare `initSecretManager` wires no
38
- `onApplied`). Core now publishes `notifySecretsResolved` onto a decoupled global-symbol
39
- ARRAY (`Symbol.for('luckystack.secretsResolved.listeners')`) at module load, which
40
- `@luckystack/secret-manager` fires automatically after every resolve — so the rebuild
41
- happens at resolve time with zero consumer code, in prod and dev, and even survives a
42
- dual `@luckystack/core` instance. (A short-lived 0.6.3/0.6.4 server-boot gate + a
43
- `ProjectConfig.secretManager` field were vestigial once the channel existed and are
44
- removed — the channel is the single trigger.)
45
- - **CORE-2** — `tryCatchSync<T, P = void>` now mirrors `tryCatch`'s `P` default,
46
- so a params-less call can pass only the result type (`tryCatchSync<URL>(() =>
47
- new URL(raw))`) instead of failing with TS2558.
48
-
49
- ### Added
50
-
51
- - **CORE-1** — `resetDefaultRedisClient()` drops + disconnects the cached lazy
52
- default Redis client so the next resolve rebuilds it from current env. Call it
53
- after `initSecretManager(...)` when `REDIS_PASSWORD`/`REDIS_HOST` were
54
- secret-manager pointers at first import (the early function-injection scan may
55
- have already built a client with the raw pointer value). The default client's
56
- error handler also turns the resulting `WRONGPASS` into an actionable message
57
- when the password still looks like an unresolved pointer.
58
-
59
- ## [0.6.0] - 2026-07-12
60
-
61
- ### Added
62
-
63
- - `AuthConfig` slots for email-code login + 2FA (ADR 0024, all additive with
64
- safe defaults): `emailCodeLogin` (false), `emailCodeTtlSeconds` (600),
65
- `emailCodeLength` (6), `emailCodeMaxAttempts` (5), `twoFactor`
66
- (`'disabled' | 'optional'`, default disabled), `twoFactorEmailFallback`
67
- (true), `twoFactorChallengeTtlSeconds` (300), `twoFactorMaxAttempts` (5).
68
-
69
- ## [0.5.0] - 2026-07-11
70
-
71
- ### Changed
72
-
73
- - **`@prisma/client` is now an OPTIONAL peer dependency** (ADR 0020, the ORM
74
- choice). npm no longer auto-installs it; scaffolded projects get it via the
75
- template's direct dependency when `orm: 'prisma'`. Non-scaffolded projects
76
- that relied on peer auto-install must add `@prisma/client` to their own
77
- `package.json`. The same demotion applies in `@luckystack/api`, `devkit`,
78
- `server`, `sync`, and `login`.
79
- - `@prisma/client` left the static-import position in `db.ts`: the default
80
- resolver now lazy-`require()`s it on first access. When it is absent AND no
81
- client was registered, DB access throws an actionable error pointing at
82
- `luckystack/core/clients.ts` instead of a bare `ERR_MODULE_NOT_FOUND` at boot.
83
-
84
- ### Added
85
-
86
- - `isPrismaClientResolvable()` — is a generated `@prisma/client` present?
87
- - `registerDbHealthCheck` / `getDbHealthCheck` / `isDbHealthCheckRegistered` /
88
- `resetDbHealthCheckForTests` (+ types `DbHealthCheck`, `DbHealthResult`) —
89
- pluggable `/readyz` database probe; without one the server falls back to the
90
- built-in Prisma ping when Prisma is present, else reports `'skipped'`.
91
-
92
- ## [0.1.5]
93
-
94
- ### Removed
95
-
96
- - **`DNS` dropped from the env schema.** It was a reserved/legacy var that
97
- conflated the backend origin (OAuth callback host) with the public origin
98
- (where users browse). Projects now derive the backend origin from
99
- `SERVER_IP`/`SERVER_PORT` and set the public origin via `app.publicUrl` (see the
100
- scaffold's `config.ts`). Leftover `DNS=` lines in existing `.env` files are
101
- harmless — the schema is `loose()`, so unknown keys pass through ignored.
102
-
103
- ## [0.1.0]
104
-
105
- ### Added
106
-
107
- - Initial public release as part of the LuckyStack package split.
1
+ # Changelog
2
+
3
+ All notable changes to `@luckystack/core` are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.7.0] - 2026-07-16
11
+
12
+ ### Added
13
+
14
+ - **`registerSecretsResolvedListener` / `notifySecretsResolved` are now exported from
15
+ the client-safe `@luckystack/core/config` subpath** (they were already on the main
16
+ barrel). A project's `config.ts` — which is client-bundled, so it can only import from
17
+ `/config` — can now re-register env-derived slots the moment the secret manager
18
+ resolves. This closes a real defect (finding C-04): `config.ts` runs at module load,
19
+ *before* `resolveSecretsIfConfigured()`, so any slot derived from a secret-manager
20
+ pointer (`EMAIL_FROM`, `EXTERNAL_ORIGINS`, …) froze as the unresolved pointer —
21
+ measured live, CORS held `["ORIGINS_BASE_V1"]` while `process.env` already had the real
22
+ origin, so it would reject the very host the operator configured. `secretsResolved.ts`
23
+ imports nothing, so the subpath stays free of server deps (`configEntry.test.ts`).
24
+
25
+ - **Bun env auto-load guard.** Bun auto-loads `.env` files before any user code
26
+ runs; Node does not. That silently breaks two guarantees of `loadEnvFiles`:
27
+ `LUCKYSTACK_ENV_FILES` stops being an ambient-only override (a value set INSIDE
28
+ `.env` — exactly what `.env_template` shows commented — hijacks the file list),
29
+ and `.env.<mode>` / `.env.<mode>.local`, which the framework never loads, come
30
+ to outrank `.env` (verified under Bun 1.3.14: a key in `.env.development` beat
31
+ `.env`). Both failures were 100% silent. `loadEnvFiles` now detects the symptom
32
+ at boot — an env file already applied to `process.env` byte-for-byte before we
33
+ load it — and warns with the fix. It warns rather than throws: `bun install`
34
+ ignores `env = false` (oven-sh/bun#31450), so a postinstall boot would
35
+ otherwise be unfixably fatal. Node is never affected and never warns.
36
+ - **`bunfig.toml` with `env = false`** at the repo root and in the
37
+ `create-luckystack-app` template, disabling Bun's `.env` auto-load (requires
38
+ Bun >= 1.3.3) so `bun` and `node` load byte-identical values. The "a real
39
+ ambient env var wins over `.env`" contract (Docker/K8s/CI) is unchanged — that
40
+ is LuckyStack's own loader, not Bun's.
41
+ - **Timestamps in the built-in loggers.** The default `console.*` logger and
42
+ `createDevLogger` now prefix each line with an ISO-8601 UTC timestamp
43
+ (`[2026-07-13T15:20:01.123Z] Connected to Redis`), controlled by a new
44
+ `logging.timestamps` config key (default `true`; set `false` under a log
45
+ aggregator that stamps its own time). Only the message is prefixed — context /
46
+ error args stay separate. A registered custom logger owns its own formatting.
47
+ - **Decoupled secrets-resolved hook** (ADR 0026): `notifySecretsResolved(changedKeys?)`
48
+ + `registerSecretsResolvedListener(fn)`. A secret resolver (e.g.
49
+ `@luckystack/secret-manager` via `onApplied: notifySecretsResolved`) fires it after
50
+ overwriting `process.env`; `redis.ts` self-registers a listener that EAGERLY REBUILDS +
51
+ registers the default client when a `REDIS_` credential changed, so Redis auth via a
52
+ secret-manager POINTER survives boot AND rotation with no hand-wiring. Generic — Prisma
53
+ pools / SDK clients can subscribe too. Also adds `rebuildDefaultRedisClient()` and an
54
+ optional structural `secretManager?` field on `ProjectConfig` (`SecretManagerConfigRef`)
55
+ so the server boot can detect a resolver.
56
+
57
+ ### Fixed
58
+
59
+ - **`@luckystack/core/client` no longer reaches `node:async_hooks` in its built
60
+ chunk graph.** The browser-safe lazy capture path and the server-only
61
+ AsyncLocalStorage identity scope previously shared `errorTrackerRegistry.ts`;
62
+ tsup coalesced that dynamic capture path with the client logger and emitted a
63
+ static Node builtin import in a client-reached chunk. The identity scope now
64
+ lives in a dedicated server module, while capture fan-out remains browser-safe.
65
+ A post-tsup graph check rejects any Node builtin reachable from `dist/client.js`.
66
+ - **`Jsonify<T>` now preserves already-JSON-stable recursive values.** Prisma's
67
+ self-referential `JsonValue` previously recursed through the array branch until
68
+ TypeScript rendered `... N more ...`, producing malformed generated route types
69
+ for a Prisma `SessionLayout`. A non-distributive JSON-stability guard keeps the
70
+ value intact while `Date | null` still becomes `string | null`.
71
+ - **Redis secret-manager pointer boot** (ADR 0026): the default Redis client no longer
72
+ fails auth with a baked-in `REDIS_PASSWORD_V<n>` pointer when it was built (during an
73
+ early import) before secrets resolved. The framework EAGERLY REBUILDS + registers a
74
+ fresh client from the resolved env (`rebuildDefaultRedisClient()`), so the boot-UUID
75
+ write authenticates with the real password — no consumer code. **Correction over
76
+ 0.6.3/0.6.4:** the rebuild logic was right but never TRIGGERED for a normal project (the
77
+ server-boot gate `getProjectConfig().secretManager?.url` is falsy — the scaffold doesn't
78
+ register `secretManager` into `projectConfig` — and bare `initSecretManager` wires no
79
+ `onApplied`). Core now publishes `notifySecretsResolved` onto a decoupled global-symbol
80
+ ARRAY (`Symbol.for('luckystack.secretsResolved.listeners')`) at module load, which
81
+ `@luckystack/secret-manager` fires automatically after every resolve — so the rebuild
82
+ happens at resolve time with zero consumer code, in prod and dev, and even survives a
83
+ dual `@luckystack/core` instance. (A short-lived 0.6.3/0.6.4 server-boot gate + a
84
+ `ProjectConfig.secretManager` field were vestigial once the channel existed and are
85
+ removed — the channel is the single trigger.)
86
+ - **CORE-2** — `tryCatchSync<T, P = void>` now mirrors `tryCatch`'s `P` default,
87
+ so a params-less call can pass only the result type (`tryCatchSync<URL>(() =>
88
+ new URL(raw))`) instead of failing with TS2558.
89
+
90
+ ### Added
91
+
92
+ - **CORE-1** — `resetDefaultRedisClient()` drops + disconnects the cached lazy
93
+ default Redis client so the next resolve rebuilds it from current env. Call it
94
+ after `initSecretManager(...)` when `REDIS_PASSWORD`/`REDIS_HOST` were
95
+ secret-manager pointers at first import (the early function-injection scan may
96
+ have already built a client with the raw pointer value). The default client's
97
+ error handler also turns the resulting `WRONGPASS` into an actionable message
98
+ when the password still looks like an unresolved pointer.
99
+
100
+ ## [0.6.0] - 2026-07-12
101
+
102
+ ### Added
103
+
104
+ - `AuthConfig` slots for email-code login + 2FA (ADR 0024, all additive with
105
+ safe defaults): `emailCodeLogin` (false), `emailCodeTtlSeconds` (600),
106
+ `emailCodeLength` (6), `emailCodeMaxAttempts` (5), `twoFactor`
107
+ (`'disabled' | 'optional'`, default disabled), `twoFactorEmailFallback`
108
+ (true), `twoFactorChallengeTtlSeconds` (300), `twoFactorMaxAttempts` (5).
109
+
110
+ ## [0.5.0] - 2026-07-11
111
+
112
+ ### Changed
113
+
114
+ - **`@prisma/client` is now an OPTIONAL peer dependency** (ADR 0020, the ORM
115
+ choice). npm no longer auto-installs it; scaffolded projects get it via the
116
+ template's direct dependency when `orm: 'prisma'`. Non-scaffolded projects
117
+ that relied on peer auto-install must add `@prisma/client` to their own
118
+ `package.json`. The same demotion applies in `@luckystack/api`, `devkit`,
119
+ `server`, `sync`, and `login`.
120
+ - `@prisma/client` left the static-import position in `db.ts`: the default
121
+ resolver now lazy-`require()`s it on first access. When it is absent AND no
122
+ client was registered, DB access throws an actionable error pointing at
123
+ `luckystack/core/clients.ts` instead of a bare `ERR_MODULE_NOT_FOUND` at boot.
124
+
125
+ ### Added
126
+
127
+ - `isPrismaClientResolvable()` — is a generated `@prisma/client` present?
128
+ - `registerDbHealthCheck` / `getDbHealthCheck` / `isDbHealthCheckRegistered` /
129
+ `resetDbHealthCheckForTests` (+ types `DbHealthCheck`, `DbHealthResult`) —
130
+ pluggable `/readyz` database probe; without one the server falls back to the
131
+ built-in Prisma ping when Prisma is present, else reports `'skipped'`.
132
+
133
+ ## [0.1.5]
134
+
135
+ ### Removed
136
+
137
+ - **`DNS` dropped from the env schema.** It was a reserved/legacy var that
138
+ conflated the backend origin (OAuth callback host) with the public origin
139
+ (where users browse). Projects now derive the backend origin from
140
+ `SERVER_IP`/`SERVER_PORT` and set the public origin via `app.publicUrl` (see the
141
+ scaffold's `config.ts`). Leftover `DNS=` lines in existing `.env` files are
142
+ harmless — the schema is `loose()`, so unknown keys pass through ignored.
143
+
144
+ ## [0.1.0]
145
+
146
+ ### Added
147
+
148
+ - Initial public release as part of the LuckyStack package split.