@luckystack/core 0.10.2 → 0.12.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.
- package/AGENTS.md +11 -0
- package/CHANGELOG.md +18 -0
- package/README.md +2 -88
- package/dist/apiInterceptors.d.ts +36 -0
- package/dist/apiMethodMapRegistry.d.ts +5 -0
- package/dist/apiRequest.d.ts +71 -0
- package/dist/apiTypeStubs.d.ts +3 -5
- package/dist/avatarConfig.d.ts +22 -0
- package/dist/bindAddress.d.ts +15 -0
- package/dist/bootUuid.d.ts +24 -0
- package/dist/cancelRegistry.d.ts +7 -0
- package/dist/checkOrigin.d.ts +6 -0
- package/dist/{chunk-IJYNVM2W.js → chunk-P3SSLA3E.js} +2 -2
- package/dist/chunk-P3SSLA3E.js.map +1 -0
- package/dist/{chunk-5FXBTGYL.js → chunk-XDX5MV54.js} +2 -2
- package/dist/chunk-XDX5MV54.js.map +1 -0
- package/dist/client.d.ts +46 -335
- package/dist/client.js +5 -5
- package/dist/client.js.map +1 -1
- package/dist/clientHookBus.d.ts +62 -0
- package/dist/clients.d.ts +16 -0
- package/dist/config.d.ts +8 -2
- package/dist/configUtils.d.ts +16 -0
- package/dist/consoleLog.d.ts +1 -0
- package/dist/cookies.d.ts +28 -0
- package/dist/createRegistry.d.ts +39 -0
- package/dist/csrf.d.ts +30 -0
- package/dist/csrfConfig.d.ts +32 -0
- package/dist/db.d.ts +12 -0
- package/dist/dbHealthCheck.d.ts +6 -0
- package/dist/{config-CwW3KY9r.d.ts → deployConfigRegistry.d.ts} +8 -43
- package/dist/emailRegistry.d.ts +139 -0
- package/dist/env.d.ts +38 -0
- package/dist/errorFormatterRegistry.d.ts +54 -0
- package/dist/errorTrackerIdentity.d.ts +14 -0
- package/dist/errorTrackerRegistry.d.ts +85 -0
- package/dist/escapeHtml.d.ts +6 -0
- package/dist/eslint/index.d.ts +5 -8
- package/dist/eslint/index.js +125 -8
- package/dist/eslint/index.js.map +1 -1
- package/dist/eslint/internal/hasPackage.d.ts +1 -0
- package/dist/eslint/internal/ruleTypes.d.ts +4 -0
- package/dist/eslint/rules/no-arbitrary-tailwind-color.d.ts +3 -0
- package/dist/eslint/rules/no-direct-prisma-import-in-components.d.ts +3 -0
- package/dist/eslint/rules/no-local-socket-enumeration.d.ts +3 -0
- package/dist/eslint/rules/no-raw-fetch-in-src.d.ts +3 -0
- package/dist/eslint/rules/no-raw-try-catch.d.ts +3 -0
- package/dist/eslint/rules/no-unsafe-api-wrappers.d.ts +3 -0
- package/dist/eslint/rules/no-unsafe-sync-wrappers.d.ts +3 -0
- package/dist/eslint/rules/no-untranslated-jsx-text.d.ts +4 -0
- package/dist/eslint/rules/prefer-luckystack-confirm.d.ts +3 -0
- package/dist/eslint/rules/prefer-luckystack-dropdown.d.ts +3 -0
- package/dist/eslint/rules/prefer-luckystack-notify.d.ts +3 -0
- package/dist/extractToken.d.ts +12 -0
- package/dist/extractTokenFromRequest.d.ts +12 -0
- package/dist/getParams.d.ts +9 -0
- package/dist/hooks/registry.d.ts +12 -0
- package/dist/hooks/types.d.ts +446 -0
- package/dist/httpApiUtils.d.ts +54 -0
- package/dist/index.d.ts +110 -1892
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lease.d.ts +18 -0
- package/dist/localSocketEnumerationGuard.d.ts +22 -0
- package/dist/localeReloader.d.ts +3 -0
- package/dist/localesRegistry.d.ts +8 -0
- package/dist/localizedNormalizer.d.ts +50 -0
- package/dist/loggerRegistry.d.ts +12 -0
- package/dist/middlewareRegistry.d.ts +45 -0
- package/dist/notifier.d.ts +17 -0
- package/dist/offlineQueue.d.ts +48 -0
- package/dist/oneTimeToken.d.ts +43 -0
- package/dist/pageRouteValidation.d.ts +37 -0
- package/dist/paths.d.ts +35 -0
- package/dist/peerDeps.d.ts +23 -0
- package/dist/portOverrideRegistry.d.ts +3 -0
- package/dist/processUpload.d.ts +44 -0
- package/dist/{sessionTypes-DanjuPaE.d.ts → projectConfig.d.ts} +32 -129
- package/dist/rateLimiter.d.ts +90 -0
- package/dist/react/AvatarProvider.d.ts +11 -0
- package/dist/react/Middleware.d.ts +10 -0
- package/dist/react/Router.d.ts +1 -0
- package/dist/react/TranslationProvider.d.ts +22 -0
- package/dist/react/notify.d.ts +12 -0
- package/dist/react/sessionContext.d.ts +31 -0
- package/dist/react/useTheme.d.ts +5 -0
- package/dist/redactedLogKeys.d.ts +22 -0
- package/dist/redis.d.ts +15 -0
- package/dist/redisKeyFormatter.d.ts +7 -0
- package/dist/resolveClientIp.d.ts +51 -0
- package/dist/responseNormalizer.d.ts +33 -0
- package/dist/roomNameFormatterRegistry.d.ts +36 -0
- package/dist/roomSockets.d.ts +23 -0
- package/dist/routedDataQuery.d.ts +7 -0
- package/dist/routedHttpInvocation.d.ts +20 -0
- package/dist/runtimeMapsRegistry.d.ts +18 -0
- package/dist/runtimeTypeValidation.d.ts +13 -0
- package/dist/secretsResolved.d.ts +3 -0
- package/dist/{sentrySetup-HNIURVUZ.js → sentrySetup-OLZ4BO7Q.js} +2 -2
- package/dist/sentrySetup.d.ts +21 -0
- package/dist/serveAvatars.d.ts +5 -0
- package/dist/serviceRoute.d.ts +27 -0
- package/dist/servicesConfigRegistry.d.ts +22 -0
- package/dist/sessionProviderRegistry.d.ts +46 -0
- package/dist/sessionTypes.d.ts +79 -0
- package/dist/sleep.d.ts +1 -0
- package/dist/socketEvents.d.ts +37 -0
- package/dist/socketMiddlewareRegistry.d.ts +20 -0
- package/dist/socketRedisAdapter.d.ts +21 -0
- package/dist/socketState.d.ts +5 -0
- package/dist/socketStatusTypes.d.ts +6 -0
- package/dist/socketTypes.d.ts +34 -0
- package/dist/synchronizedEnvHashes.d.ts +66 -0
- package/dist/testFileConvention.d.ts +11 -0
- package/dist/testResetTargets.d.ts +5 -0
- package/dist/tryCatch.d.ts +1 -0
- package/dist/tryCatchClient.d.ts +1 -0
- package/dist/tryCatchSync.d.ts +1 -0
- package/dist/validateRequest.d.ts +50 -0
- package/docs/app-bootstrap.md +4 -449
- package/docs/config-registry.md +3 -346
- package/docs/csrf-config.md +3 -86
- package/docs/error-tracker-registry.md +3 -192
- package/docs/hooks.md +2 -295
- package/docs/rate-limit-strategy.md +3 -168
- package/docs/redis-adapter.md +5 -110
- package/docs/session-types.md +3 -178
- package/docs/socket-bootstrap.md +5 -385
- package/package.json +4 -4
- package/CLAUDE.md +0 -256
- package/dist/chunk-5FXBTGYL.js.map +0 -1
- package/dist/chunk-IJYNVM2W.js.map +0 -1
- package/dist/pageRouteValidation-B4Cmtfjv.d.ts +0 -280
- /package/dist/{sentrySetup-HNIURVUZ.js.map → sentrySetup-OLZ4BO7Q.js.map} +0 -0
package/docs/config-registry.md
CHANGED
|
@@ -1,348 +1,5 @@
|
|
|
1
|
-
# Config
|
|
1
|
+
# Config registry
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The project, deployment and services registries decouple reusable framework packages from consumer files. Register during boot, read at call time; avoid capturing a configuration snapshot at import time. Explicit registration checks allow security-critical boot paths to reject missing setup rather than silently using permissive assumptions. See `../src/projectConfig.ts`, `../src/deployConfigRegistry.ts`, `../src/servicesConfigRegistry.ts` and `../../server/src/verifyBootstrap.ts`.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
The config registries are the single source of truth for runtime project configuration. They follow a uniform pattern:
|
|
8
|
-
|
|
9
|
-
1. A consumer's boot entry calls `registerXConfig({...})` once before any framework code reads a value.
|
|
10
|
-
2. Framework code reads at call time via `getXConfig()` (never at module load) so the registration timing is flexible.
|
|
11
|
-
3. Boolean `isXConfigRegistered()` helpers exist for code paths that must fail closed when the consumer forgot to register.
|
|
12
|
-
|
|
13
|
-
Four independent slots live in this topic:
|
|
14
|
-
|
|
15
|
-
- **Project config** — runtime tunables (rate limits, session policy, HTTP, auth, sockets, paths). Deep-merged over `DEFAULT_PROJECT_CONFIG` so consumers only supply the keys they want to override.
|
|
16
|
-
- **Deploy config** — multi-env topology consumed by `@luckystack/router` (resources, environments, routing, development).
|
|
17
|
-
- **Services config** — service + preset definitions consumed by the router / preset loader.
|
|
18
|
-
- **Avatar config** — disk formats + cache header for `serveAvatar`.
|
|
19
|
-
|
|
20
|
-
Path getters (`getSrcDir`, `getGeneratedApiDocsPath`, …) resolve project-relative paths against `ROOT_DIR` (auto-detected workspace root) and read `getProjectConfig().paths.*` at call time.
|
|
21
|
-
|
|
22
|
-
## API Reference — Project Config
|
|
23
|
-
|
|
24
|
-
### `registerProjectConfig(config: ProjectConfigInput): void`
|
|
25
|
-
|
|
26
|
-
**Signature:**
|
|
27
|
-
```typescript
|
|
28
|
-
export const registerProjectConfig = (config: ProjectConfigInput): void
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
**Parameters:**
|
|
32
|
-
| Name | Type | Required | Description |
|
|
33
|
-
|---|---|---|---|
|
|
34
|
-
| `config` | `ProjectConfigInput` (deep-partial of `ProjectConfig`) | yes | Only the keys to override. Missing keys fall back to `DEFAULT_PROJECT_CONFIG`. |
|
|
35
|
-
|
|
36
|
-
**Returns:** `void`
|
|
37
|
-
|
|
38
|
-
**Behavior:**
|
|
39
|
-
- Deep-merges `config` over `DEFAULT_PROJECT_CONFIG` (plain-object branches recurse; primitives + arrays are replaced wholesale).
|
|
40
|
-
- Sets the registration flag so `isProjectConfigRegistered()` returns true.
|
|
41
|
-
- Replaces (last-write-wins) on repeated calls — the latest call is the active config.
|
|
42
|
-
|
|
43
|
-
**Errors / Edge cases:**
|
|
44
|
-
- `undefined` keys in the override are skipped (treated as "no override").
|
|
45
|
-
- Arrays are NOT merged element-wise — passing `cors.allowedOrigins: ['x']` replaces the default `[]` array.
|
|
46
|
-
- Calling after framework hot paths already ran is allowed but the values only take effect on subsequent reads (everything reads call-time).
|
|
47
|
-
|
|
48
|
-
**Example:**
|
|
49
|
-
```typescript
|
|
50
|
-
import { registerProjectConfig } from '@luckystack/core';
|
|
51
|
-
|
|
52
|
-
registerProjectConfig({
|
|
53
|
-
defaultLanguage: 'en',
|
|
54
|
-
session: { expiryDays: 30 },
|
|
55
|
-
http: {
|
|
56
|
-
cors: {
|
|
57
|
-
allowedOrigins: ['https://app.example.com'],
|
|
58
|
-
allowLocalhost: false,
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### `getProjectConfig(): ProjectConfig`
|
|
65
|
-
|
|
66
|
-
**Signature:**
|
|
67
|
-
```typescript
|
|
68
|
-
export const getProjectConfig = (): ProjectConfig
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
**Returns:** The active fully-merged `ProjectConfig`.
|
|
72
|
-
|
|
73
|
-
**Behavior:**
|
|
74
|
-
- Returns the currently merged config object (reference-stable until the next `registerProjectConfig`).
|
|
75
|
-
- Returns `DEFAULT_PROJECT_CONFIG` when nothing was registered yet.
|
|
76
|
-
|
|
77
|
-
**Example:**
|
|
78
|
-
```typescript
|
|
79
|
-
import { getProjectConfig } from '@luckystack/core';
|
|
80
|
-
|
|
81
|
-
const { http, rateLimiting } = getProjectConfig();
|
|
82
|
-
const cookieName = http.sessionCookieName;
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### `getProjectName(): string`
|
|
86
|
-
|
|
87
|
-
**Signature:**
|
|
88
|
-
```typescript
|
|
89
|
-
export const getProjectName = (): string
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
**Returns:** The active project namespace (used as Redis key prefix for session, password reset, rate limits, OAuth state, etc.).
|
|
93
|
-
|
|
94
|
-
**Behavior:** Resolves in this order (first non-empty wins):
|
|
95
|
-
1. `projectConfig.session.projectName`
|
|
96
|
-
2. `process.env.PROJECT_NAME`
|
|
97
|
-
3. literal `'luckystack'`
|
|
98
|
-
|
|
99
|
-
**Example:**
|
|
100
|
-
```typescript
|
|
101
|
-
import { getProjectName } from '@luckystack/core';
|
|
102
|
-
|
|
103
|
-
const key = `${getProjectName()}-session:${token}`;
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### `isProjectConfigRegistered(): boolean`
|
|
107
|
-
|
|
108
|
-
**Signature:**
|
|
109
|
-
```typescript
|
|
110
|
-
export const isProjectConfigRegistered = (): boolean
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
**Returns:** `true` only after `registerProjectConfig` has been called at least once.
|
|
114
|
-
|
|
115
|
-
**Behavior:**
|
|
116
|
-
- Pure boot-time guard — does not throw.
|
|
117
|
-
- Framework packages use this for "did the installer forget to register?" warnings without crashing tests/CLI contexts.
|
|
118
|
-
|
|
119
|
-
### `DEFAULT_PROJECT_CONFIG: ProjectConfig`
|
|
120
|
-
|
|
121
|
-
Exported so consumers can spread + override structurally:
|
|
122
|
-
|
|
123
|
-
```typescript
|
|
124
|
-
import { DEFAULT_PROJECT_CONFIG, registerProjectConfig } from '@luckystack/core';
|
|
125
|
-
|
|
126
|
-
registerProjectConfig({
|
|
127
|
-
...DEFAULT_PROJECT_CONFIG,
|
|
128
|
-
defaultLanguage: 'nl',
|
|
129
|
-
});
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
Notable defaults (verbatim from `projectConfig.ts`):
|
|
133
|
-
|
|
134
|
-
| Key | Default |
|
|
135
|
-
|---|---|
|
|
136
|
-
| `rateLimiting.enabled` | `true` |
|
|
137
|
-
| `rateLimiting.store` | `'memory'` |
|
|
138
|
-
| `rateLimiting.defaultApiLimit` | `60` |
|
|
139
|
-
| `rateLimiting.defaultIpLimit` | `100` |
|
|
140
|
-
| `rateLimiting.windowMs` | `60_000` |
|
|
141
|
-
| `session.expiryDays` | `7` |
|
|
142
|
-
| `session.perUser` | `'single'` |
|
|
143
|
-
| `session.onConflict` | `'revokeOld'` |
|
|
144
|
-
| `session.notifyOldDeviceOnRevoke` | `true` |
|
|
145
|
-
| `http.sessionCookieName` | `'token'` |
|
|
146
|
-
| `http.sessionCookieSameSite` | `'Strict'` |
|
|
147
|
-
| `http.requestBodyMaxBytes` | `1048576` |
|
|
148
|
-
| `http.healthEndpoint` | `'/_health'` |
|
|
149
|
-
| `http.liveEndpoint` | `'/livez'` |
|
|
150
|
-
| `http.readyEndpoint` | `'/readyz'` |
|
|
151
|
-
| `http.cors.credentials` | `true` |
|
|
152
|
-
| `http.cors.allowedOrigins` | `[]` |
|
|
153
|
-
| `http.cors.allowLocalhost` | `false` |
|
|
154
|
-
| `auth.bcryptRounds` | `10` |
|
|
155
|
-
| `auth.providerAccountStrategy` | `'per-provider'` |
|
|
156
|
-
| `auth.forgotPassword` | `'disabled'` |
|
|
157
|
-
| `auth.passwordResetTtlSeconds` | `3600` |
|
|
158
|
-
| `socket.maxHttpBufferSize` | `5 * 1024 * 1024` |
|
|
159
|
-
| `socket.pingTimeout` | `20_000` |
|
|
160
|
-
| `socket.pingInterval` | `25_000` |
|
|
161
|
-
| `sync.fanoutYieldEvery` | `100` |
|
|
162
|
-
| `sync.fanoutYieldMs` | `1` |
|
|
163
|
-
| `offlineQueue.maxSize` | `200` |
|
|
164
|
-
| `offlineQueue.maxAgeMs` | `3_600_000` |
|
|
165
|
-
| `offlineQueue.dropPolicy` | `'drop-oldest'` |
|
|
166
|
-
| `dev.hotReloadDebounceMs` | `120` |
|
|
167
|
-
| `defaultLanguage` | `'en'` |
|
|
168
|
-
| `defaultTheme` | `'light'` |
|
|
169
|
-
|
|
170
|
-
## API Reference — Deploy Config
|
|
171
|
-
|
|
172
|
-
### `registerDeployConfig(config: DeployConfigShape): DeployConfigShape`
|
|
173
|
-
|
|
174
|
-
**Signature:**
|
|
175
|
-
```typescript
|
|
176
|
-
export const registerDeployConfig = (config: DeployConfigShape): DeployConfigShape
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
**Parameters:**
|
|
180
|
-
| Name | Type | Required | Description |
|
|
181
|
-
|---|---|---|---|
|
|
182
|
-
| `config` | `DeployConfigShape` | yes | Deploy topology object — `{ resources, environments?, routing?, development? }`. |
|
|
183
|
-
|
|
184
|
-
**Returns:** The stored config (same reference passed in).
|
|
185
|
-
|
|
186
|
-
**Behavior:**
|
|
187
|
-
- Stores the provided config wholesale (no deep-merge, unlike `registerProjectConfig`).
|
|
188
|
-
- Sets `isDeployConfigRegistered()` to `true`.
|
|
189
|
-
|
|
190
|
-
**Example:**
|
|
191
|
-
```typescript
|
|
192
|
-
import { registerDeployConfig } from '@luckystack/core';
|
|
193
|
-
|
|
194
|
-
registerDeployConfig({
|
|
195
|
-
resources: {
|
|
196
|
-
redis: { type: 'redis', urlEnvKey: 'REDIS_URL', synchronizedEnvKeys: ['SESSION_SECRET'] },
|
|
197
|
-
mongo: { type: 'mongo', urlEnvKey: 'MONGO_URL' },
|
|
198
|
-
},
|
|
199
|
-
environments: {
|
|
200
|
-
production: { redis: 'redis', mongo: 'mongo', bindings: {} },
|
|
201
|
-
},
|
|
202
|
-
routing: { strictBootHandshake: true },
|
|
203
|
-
});
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
### `getDeployConfig(): DeployConfigShape`
|
|
207
|
-
|
|
208
|
-
Returns the registered deploy config, or `{ resources: {} }` when nothing was registered. Never throws.
|
|
209
|
-
|
|
210
|
-
### `isDeployConfigRegistered(): boolean`
|
|
211
|
-
|
|
212
|
-
Non-throwing guard. Used by router / boot probes that need a real registration before behaving differently from the empty default.
|
|
213
|
-
|
|
214
|
-
## API Reference — Services Config
|
|
215
|
-
|
|
216
|
-
### `registerServicesConfig(config: ServicesConfigShape): ServicesConfigShape`
|
|
217
|
-
|
|
218
|
-
**Signature:**
|
|
219
|
-
```typescript
|
|
220
|
-
export const registerServicesConfig = (config: ServicesConfigShape): ServicesConfigShape
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
**Parameters:**
|
|
224
|
-
| Name | Type | Required | Description |
|
|
225
|
-
|---|---|---|---|
|
|
226
|
-
| `config` | `ServicesConfigShape` | yes | `{ services, presets }` — services define folder sources, presets bundle multiple services into one artifact. |
|
|
227
|
-
|
|
228
|
-
**Returns:** The stored config.
|
|
229
|
-
|
|
230
|
-
**Behavior:**
|
|
231
|
-
- Stores the config (last-write-wins).
|
|
232
|
-
- Side-effect pattern: the consumer's `services.config.ts` is expected to call this on module import.
|
|
233
|
-
|
|
234
|
-
### `getServicesConfig(): ServicesConfigShape`
|
|
235
|
-
|
|
236
|
-
**Throws** `Error` when no services config has been registered. Message includes guidance to import `services.config.ts` as a side-effect.
|
|
237
|
-
|
|
238
|
-
### `isServicesConfigRegistered(): boolean`
|
|
239
|
-
|
|
240
|
-
Non-throwing guard for code that wants to fall back instead of crashing.
|
|
241
|
-
|
|
242
|
-
## API Reference — Avatar Config
|
|
243
|
-
|
|
244
|
-
### `registerAvatarConfig(config: AvatarConfigInput): void`
|
|
245
|
-
|
|
246
|
-
**Signature:**
|
|
247
|
-
```typescript
|
|
248
|
-
export const registerAvatarConfig = (config: AvatarConfigInput): void
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
**Parameters:**
|
|
252
|
-
| Name | Type | Required | Description |
|
|
253
|
-
|---|---|---|---|
|
|
254
|
-
| `config.formats` | `{ extension: string; contentType: string }[]` | no | Disk formats checked in order; first existing file wins. |
|
|
255
|
-
| `config.cacheControl` | `string` | no | `Cache-Control` header. Default `'public, max-age=86400'`. |
|
|
256
|
-
|
|
257
|
-
**Behavior:**
|
|
258
|
-
- Empty `formats` array is treated as "use default" (keeps the built-in `[{ extension: 'webp', contentType: 'image/webp' }]`).
|
|
259
|
-
- Missing `cacheControl` keeps the default.
|
|
260
|
-
|
|
261
|
-
**Example:**
|
|
262
|
-
```typescript
|
|
263
|
-
import { registerAvatarConfig } from '@luckystack/core';
|
|
264
|
-
|
|
265
|
-
registerAvatarConfig({
|
|
266
|
-
formats: [
|
|
267
|
-
{ extension: 'webp', contentType: 'image/webp' },
|
|
268
|
-
{ extension: 'png', contentType: 'image/png' },
|
|
269
|
-
],
|
|
270
|
-
cacheControl: 'public, max-age=604800',
|
|
271
|
-
});
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### `getAvatarConfig(): AvatarConfig`
|
|
275
|
-
|
|
276
|
-
Returns the active avatar config (defaults applied).
|
|
277
|
-
|
|
278
|
-
### `DEFAULT_AVATAR_CONFIG: AvatarConfig`
|
|
279
|
-
|
|
280
|
-
```typescript
|
|
281
|
-
{
|
|
282
|
-
formats: [{ extension: 'webp', contentType: 'image/webp' }],
|
|
283
|
-
cacheControl: 'public, max-age=86400',
|
|
284
|
-
}
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
## API Reference — Paths
|
|
288
|
-
|
|
289
|
-
`paths.ts` exports lazy getters that resolve `projectConfig.paths.*` against `ROOT_DIR`. Read at call time so a late `registerProjectConfig({ paths: {...} })` still wins.
|
|
290
|
-
|
|
291
|
-
| Function | Returns | Reads from |
|
|
292
|
-
|---|---|---|
|
|
293
|
-
| `getSrcDir()` | absolute path | `paths.srcDir` |
|
|
294
|
-
| `getServerDir()` | absolute path | `paths.serverDir` |
|
|
295
|
-
| `getSharedDir()` | absolute path | `paths.sharedDir` |
|
|
296
|
-
| `getUploadsDir()` | absolute path | `paths.uploadsDir` |
|
|
297
|
-
| `getPublicDir()` | absolute path | `paths.publicDir` |
|
|
298
|
-
| `getServerFunctionsDir()` | absolute path | `paths.serverFunctionsDir` |
|
|
299
|
-
| `getGeneratedSocketTypesPath()` | absolute path | `paths.generatedSocketTypes` |
|
|
300
|
-
| `getGeneratedApiSchemasPath()` | absolute path | `paths.generatedApiSchemas` |
|
|
301
|
-
| `getGeneratedApiDocsPath()` | absolute path | `paths.generatedApiDocs` |
|
|
302
|
-
| `resolveFromRoot(...segments: string[])` | absolute path | `ROOT_DIR` + joined segments |
|
|
303
|
-
|
|
304
|
-
`ROOT_DIR` is auto-detected by walking up from `process.cwd()` (then `__dirname`) and stopping at the first directory containing `package.json` plus either `tsconfig.json` or `tsconfig.server.json`. Falls back to `process.cwd()` when no marker is found.
|
|
305
|
-
|
|
306
|
-
Deprecated module-level constants exist for backwards compatibility (`SRC_DIR`, `SERVER_DIR`, `SHARED_DIR`, `UPLOADS_DIR`, `PUBLIC_DIR`, `SERVER_FUNCTIONS_DIR`, `GENERATED_SOCKET_TYPES_PATH`, `GENERATED_API_SCHEMAS_PATH`, `GENERATED_API_DOCS_PATH`). These resolve at module load and ignore later `registerProjectConfig` overrides — prefer the getters for new code.
|
|
307
|
-
|
|
308
|
-
## Config keys (top-level `ProjectConfig`)
|
|
309
|
-
|
|
310
|
-
| Key | Type | Default | Description |
|
|
311
|
-
|---|---|---|---|
|
|
312
|
-
| `app.publicUrl` | `string` | `''` | Absolute origin of the deployment; consumed by OAuth callbacks and transactional email links. |
|
|
313
|
-
| `logging.*` | flags | all `false` | Dev-only logging toggles. |
|
|
314
|
-
| `rateLimiting.enabled` | `boolean` | `true` | Kill-switch — when `false`, every check short-circuits to allowed. |
|
|
315
|
-
| `rateLimiting.store` | `'memory' \| 'redis'` | `'memory'` | Backend for the default strategy. |
|
|
316
|
-
| `rateLimiting.windowMs` | `number` | `60000` | Default sliding window. |
|
|
317
|
-
| `session.basedToken` | `boolean` | `false` | Internal project-config slot: `true` uses tab-scoped sessionStorage + bearer/handshake auth; `false` uses an HttpOnly cookie + CSRF. The scaffold's public `config.sessionBasedToken` field maps here during registration. |
|
|
318
|
-
| `session.expiryDays` | `number` | `7` | Session TTL. |
|
|
319
|
-
| `session.perUser` | `'single' \| 'multiple'` | `'single'` | Whether a single user may be logged in on multiple devices/browsers concurrently. |
|
|
320
|
-
| `session.maxConcurrentPerUser` | `number \| null` | `null` | Cap when `perUser === 'multiple'`. |
|
|
321
|
-
| `session.onConflict` | `'revokeOld' \| 'rejectNew'` | `'revokeOld'` | Policy when the cap is hit. |
|
|
322
|
-
| `session.projectName` | `string` | `''` | Explicit Redis prefix; empty means fall back to `PROJECT_NAME` env. |
|
|
323
|
-
| `http.sessionCookieName` | `string` | `'token'` | Cookie name read by `extractTokenFromSocket` / `extractTokenFromRequest`. |
|
|
324
|
-
| `http.requestBodyMaxBytes` | `number` | `1048576` | Hard cap enforced by `getParams`. |
|
|
325
|
-
| `http.healthEndpoint` | `string` | `'/_health'` | Router boot-handshake endpoint. |
|
|
326
|
-
| `http.cors.allowedOrigins` | `string[] \| (origin: string) => boolean` | `[]` | Static allowlist or sync resolver. |
|
|
327
|
-
| `http.cors.allowLocalhost` | `boolean` | `false` | Convenience flag for dev. |
|
|
328
|
-
| `auth.bcryptRounds` | `number` | `10` | Hash cost factor. |
|
|
329
|
-
| `auth.passwordPolicy.*` | rules | various | Credentials policy. |
|
|
330
|
-
| `auth.forgotPassword` | `'framework' \| 'custom' \| 'disabled'` | `'disabled'` | Reset-flow ownership. |
|
|
331
|
-
| `socket.maxHttpBufferSize` | `number` | `5MB` | Per-message ceiling. |
|
|
332
|
-
| `sync.fanoutYieldEvery` | `number` | `100` | Recipients between event-loop yields. |
|
|
333
|
-
| `offlineQueue.dropPolicy` | `'drop-oldest' \| 'drop-newest' \| 'reject'` | `'drop-oldest'` | Default eviction when full. |
|
|
334
|
-
| `dev.hotReloadDebounceMs` | `number` | `120` | File-watcher debounce. |
|
|
335
|
-
| `paths.*` | paths | see defaults table | Project filesystem layout. |
|
|
336
|
-
| `defaultLanguage` | `string` | `'en'` | Initial language for new sessions. |
|
|
337
|
-
| `socketActivityBroadcaster` | `boolean` | `false` | Enable presence broadcasts. |
|
|
338
|
-
| `socketStatusIndicator` | `boolean` | `false` | Show the floating socket-status badge. |
|
|
339
|
-
| `loginRedirectUrl` | `string` | `'/'` | Post-login destination. |
|
|
340
|
-
|
|
341
|
-
See `packages/core/src/projectConfig.ts` for the full type definitions of `LoggingConfig`, `RateLimitingConfig`, `SessionConfig`, `HttpConfig`, `CorsConfig`, `SecurityHeadersConfig`, `AuthConfig`, `PasswordPolicyConfig`, `SocketConfig`, `SyncConfig`, `SyncStreamThrottleConfig`, `OfflineQueueConfig`, `DevConfig`, and `PathsConfig`.
|
|
342
|
-
|
|
343
|
-
## Related
|
|
344
|
-
|
|
345
|
-
- Function INDEX: `packages/core/CLAUDE.md`
|
|
346
|
-
- Architecture: `docs/ARCHITECTURE_PACKAGING.md`, `docs/ARCHITECTURE_ROUTING.md`
|
|
347
|
-
- README: `packages/core/README.md`
|
|
348
|
-
- Source: `packages/core/src/projectConfig.ts`, `deployConfigRegistry.ts`, `servicesConfigRegistry.ts`, `avatarConfig.ts`, `paths.ts`
|
|
5
|
+
Cookie versus bearer session mode is a security choice, not a formatting choice. Switching modes affects CSRF, token extraction and persistence; audit those boundaries before changing it (`../src/extractToken.ts`, `../../server/src/httpRoutes/csrfMiddleware.ts`). Types and defaults belong to `../src/projectConfig.ts`, never a second hand-maintained table here.
|
package/docs/csrf-config.md
CHANGED
|
@@ -1,88 +1,5 @@
|
|
|
1
|
-
# CSRF
|
|
1
|
+
# CSRF configuration
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CSRF protects cookie-mode writes because browsers attach cookies without an explicit action from JavaScript. It does not replace the HTTP origin policy or authorization. Token-mode requests explicitly supply bearer credentials and follow a different boundary. See `../src/csrfConfig.ts`, `../src/csrf.ts`, `../../server/src/httpRoutes/csrfMiddleware.ts` and `../../server/docs/security-defaults.md`.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
LuckyStack has two cookie-mode CSRF branches. With `@luckystack/login`, the login package mints a per-session token and the server compares the configured request header with the session record. Without login, `GET /auth/csrf` issues a JavaScript-readable cookie and the server timing-safely compares that double-submit cookie with the header. Token/sessionStorage mode skips CSRF because the browser does not ambiently attach the bearer token. Use `registerCsrfConfig({ ... })` for external gateway conventions or a stronger token-length policy.
|
|
8
|
-
|
|
9
|
-
| Setting | Default | Where it is consumed |
|
|
10
|
-
|---|---|---|
|
|
11
|
-
| `cookieName` | `'csrf-token'` | Login-absent double-submit issue and validation. |
|
|
12
|
-
| `headerName` | `'x-csrf-token'` | Middleware reads `req.headers[headerName]`; `httpFetch` attaches it on writes. |
|
|
13
|
-
| `tokenLength` | `32` (bytes) | Session-bound and login-absent token minting (`randomBytes(tokenLength).toString('hex')`). |
|
|
14
|
-
| `cookieOptions.sameSite` | `'lax'` | Login-absent double-submit cookie. |
|
|
15
|
-
| `cookieOptions.secure` | unset | Follows `SECURE=true` unless explicitly overridden. |
|
|
16
|
-
| `cookieOptions.httpOnly` | `false` | Compatibility default. May be `true`: the client reads the token from the same-origin JSON response, not from `document.cookie`. |
|
|
17
|
-
| `cookieOptions.path` | `'/'` | Double-submit cookie path. |
|
|
18
|
-
| `cookieOptions.maxAgeMs` | `86_400_000` (1 day) | Double-submit cookie lifetime. |
|
|
19
|
-
|
|
20
|
-
## API Reference
|
|
21
|
-
|
|
22
|
-
### `registerCsrfConfig(input: Partial<CsrfConfig>): void`
|
|
23
|
-
|
|
24
|
-
**Signature:**
|
|
25
|
-
```typescript
|
|
26
|
-
export interface CsrfConfig {
|
|
27
|
-
cookieName: string;
|
|
28
|
-
headerName: string;
|
|
29
|
-
tokenLength: number;
|
|
30
|
-
cookieOptions: CsrfCookieOptions;
|
|
31
|
-
}
|
|
32
|
-
export const registerCsrfConfig = (input: Partial<CsrfConfig>): void
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
**Behavior:** Shallow-merges `input` over the current config. `cookieOptions` is deep-merged so a partial override of `sameSite` does not clobber `path` / `maxAgeMs`. Last-write-wins.
|
|
36
|
-
|
|
37
|
-
**Example — rename the header for an external gateway:**
|
|
38
|
-
```typescript
|
|
39
|
-
import { registerCsrfConfig } from '@luckystack/core';
|
|
40
|
-
|
|
41
|
-
registerCsrfConfig({
|
|
42
|
-
headerName: 'x-xsrf-token',
|
|
43
|
-
});
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
**Example — FIPS-grade token length + strict same-site:**
|
|
47
|
-
```typescript
|
|
48
|
-
import { registerCsrfConfig } from '@luckystack/core';
|
|
49
|
-
|
|
50
|
-
registerCsrfConfig({
|
|
51
|
-
tokenLength: 64,
|
|
52
|
-
cookieOptions: { sameSite: 'strict' },
|
|
53
|
-
});
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### `getCsrfConfig(): CsrfConfig`
|
|
57
|
-
|
|
58
|
-
Read the active config at call time. Framework code uses this — never read at module load.
|
|
59
|
-
|
|
60
|
-
### `DEFAULT_CSRF_CONFIG: CsrfConfig`
|
|
61
|
-
|
|
62
|
-
Exported so consumers can spread + override structurally:
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
import { DEFAULT_CSRF_CONFIG, registerCsrfConfig } from '@luckystack/core';
|
|
66
|
-
|
|
67
|
-
registerCsrfConfig({
|
|
68
|
-
...DEFAULT_CSRF_CONFIG,
|
|
69
|
-
headerName: 'x-app-csrf',
|
|
70
|
-
});
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### `resetCsrfConfigForTests(): void`
|
|
74
|
-
|
|
75
|
-
Test-only helper. Restore defaults between scenarios. Not part of the runtime contract.
|
|
76
|
-
|
|
77
|
-
## What this does NOT change
|
|
78
|
-
|
|
79
|
-
- The validation branch itself (session-bound comparison with login; timing-safe cookie/header comparison without login).
|
|
80
|
-
- Token-mode session behaviour — token-mode sessions skip CSRF entirely (cross-origin requests don't auto-attach `sessionStorage`).
|
|
81
|
-
- The framework's HTTP route paths (`GET /auth/csrf` is still the issuing endpoint).
|
|
82
|
-
- The `csrfMismatch` hook fan-out — payload shape and timing are unchanged.
|
|
83
|
-
|
|
84
|
-
## Related
|
|
85
|
-
|
|
86
|
-
- Function INDEX: `packages/core/CLAUDE.md`
|
|
87
|
-
- Source: `packages/core/src/csrfConfig.ts`, `packages/core/src/csrf.ts`, `packages/server/src/httpRoutes/csrfMiddleware.ts`, `packages/login/src/session.ts`
|
|
88
|
-
- Architecture: `docs/ARCHITECTURE_EXTENSION_POINTS.md`, `packages/server/docs/security-defaults.md`
|
|
5
|
+
Changing token/header or cookie policy must keep the issuer, browser helper and verifier in agreement. Do not exempt a state-changing route merely to avoid obtaining a token; a signed webhook exemption must authenticate independently. Consult `../src/csrfConfig.ts` for names and defaults.
|
|
@@ -1,194 +1,5 @@
|
|
|
1
|
-
# Error
|
|
1
|
+
# Error tracker registry
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A registry keeps framework request paths independent of any single observability backend. Capture fans out to adapters, but an adapter failure must not break an application request. Spans remain a single execution scope rather than nesting spans across all adapters. See `../src/errorTrackerRegistry.ts` and `../../error-tracking/src/adapter.ts`.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
The error tracker registry is the seam between framework code (which never imports a specific observability backend) and adapters for Sentry / Datadog / PostHog / etc. The slot lives in `@luckystack/core`; adapter implementations ship in `@luckystack/error-tracking`. Multiple adapters can be active at once via `registerErrorTrackers([...])` — every event fans out to all of them, and per-adapter errors are swallowed so one buggy tracker can't break the chain.
|
|
8
|
-
|
|
9
|
-
The legacy `initSharedSentry(instance)` + `captureException` / `captureMessage` / `setSentryUser` / `startSpan` API predates the multi-adapter registry. It is kept for backwards compatibility and dual-fans events through both the legacy slot AND any adapters registered with the new API, so a project can migrate incrementally.
|
|
10
|
-
|
|
11
|
-
`startSpanAcrossTrackers` deliberately invokes only the first adapter that supports `startSpan` — spans don't fan out cleanly because they're nested execution scopes.
|
|
12
|
-
|
|
13
|
-
The fan-out registry and per-request identity scope are separate modules. The registry
|
|
14
|
-
is browser-safe because `@luckystack/core/client` reaches it through its lazy capture
|
|
15
|
-
path. `errorTrackerIdentity.ts` is server-only and owns `node:async_hooks`; API/sync
|
|
16
|
-
handlers and server adapters reach those identity exports through the main core barrel.
|
|
17
|
-
The core package build walks the emitted `dist/client.js` chunk graph and fails if any
|
|
18
|
-
Node builtin becomes reachable, because source-level import checks cannot see tsup's
|
|
19
|
-
cross-entry chunk coalescing.
|
|
20
|
-
|
|
21
|
-
## Types
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
export interface ErrorTrackerContext {
|
|
25
|
-
[key: string]: unknown;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface ErrorTrackerUser {
|
|
29
|
-
id?: string;
|
|
30
|
-
email?: string;
|
|
31
|
-
username?: string;
|
|
32
|
-
[key: string]: unknown;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface ErrorTrackerEvent {
|
|
36
|
-
forwarded: boolean;
|
|
37
|
-
kind: 'exception' | 'message';
|
|
38
|
-
payload: Record<string, unknown>;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export type SpanResult<T> = T;
|
|
42
|
-
|
|
43
|
-
export interface ErrorTracker {
|
|
44
|
-
name: string;
|
|
45
|
-
captureException: (error: unknown, context?: ErrorTrackerContext) => void;
|
|
46
|
-
captureMessage: (
|
|
47
|
-
message: string,
|
|
48
|
-
level: 'info' | 'warning' | 'error' | 'fatal',
|
|
49
|
-
context?: ErrorTrackerContext,
|
|
50
|
-
) => void;
|
|
51
|
-
setUser: (user: ErrorTrackerUser | null) => void;
|
|
52
|
-
setContext?: (key: string, context: ErrorTrackerContext | null) => void;
|
|
53
|
-
startSpan?: <T>(name: string, op: string, fn: () => T) => T;
|
|
54
|
-
recordMetric?: (name: string, value: number, tags?: Record<string, string>) => void;
|
|
55
|
-
beforeSend?: (event: ErrorTrackerEvent) => ErrorTrackerEvent | null;
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
`SpanResult<T>` is an alias for `T` — the conditional unwrap collapsed in both branches and `tsup`'s dts emit choked on the widening. Kept as an exported alias so adapter authors can still annotate intent.
|
|
60
|
-
|
|
61
|
-
## API Reference — Multi-Adapter Registry
|
|
62
|
-
|
|
63
|
-
### `registerErrorTracker(tracker: ErrorTracker): void`
|
|
64
|
-
|
|
65
|
-
**Behavior:** Replaces the active list with `[tracker]`. Use this for single-tracker setups.
|
|
66
|
-
|
|
67
|
-
### `registerErrorTrackers(trackers: ErrorTracker[]): void`
|
|
68
|
-
|
|
69
|
-
**Behavior:** Replaces the active list with a copy of the input array (`[...trackers]`). Use for multi-backend setups (Sentry + Datadog, etc.).
|
|
70
|
-
|
|
71
|
-
### `getActiveErrorTrackers(): ErrorTracker[]`
|
|
72
|
-
|
|
73
|
-
Returns the current array (the internal reference — treat as read-only).
|
|
74
|
-
|
|
75
|
-
### `captureExceptionAcrossTrackers(error, context?): void`
|
|
76
|
-
|
|
77
|
-
**Behavior:** For each active tracker, calls `tracker.captureException(error, context)` inside a per-tracker `try / catch` that silently swallows failures.
|
|
78
|
-
|
|
79
|
-
### `captureMessageAcrossTrackers(message, level, context?): void`
|
|
80
|
-
|
|
81
|
-
Same fan-out shape as `captureExceptionAcrossTrackers` for messages.
|
|
82
|
-
|
|
83
|
-
### `setErrorTrackerUser(user: ErrorTrackerUser | null): void`
|
|
84
|
-
|
|
85
|
-
Calls `tracker.setUser(user)` on every active tracker (errors swallowed).
|
|
86
|
-
|
|
87
|
-
### `recordMetricAcrossTrackers(name, value, tags?): void`
|
|
88
|
-
|
|
89
|
-
**Behavior:** Skips trackers that don't define `recordMetric`. Errors are swallowed.
|
|
90
|
-
|
|
91
|
-
### `startSpanAcrossTrackers<T>(name, op, fn): T`
|
|
92
|
-
|
|
93
|
-
**Behavior:**
|
|
94
|
-
- Finds the first tracker with a `startSpan` method.
|
|
95
|
-
- If found, returns `tracker.startSpan(name, op, fn)` cast to `T`.
|
|
96
|
-
- If none exist, returns `fn()` directly (no instrumentation).
|
|
97
|
-
|
|
98
|
-
**Why not fan-out:** Spans are nested execution scopes — invoking multiple `startSpan` implementations would nest each other and produce confusing traces.
|
|
99
|
-
|
|
100
|
-
### Example — register multiple adapters
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
import { registerErrorTrackers, type ErrorTracker } from '@luckystack/core';
|
|
104
|
-
|
|
105
|
-
const sentryAdapter: ErrorTracker = { /* ... */ };
|
|
106
|
-
const datadogAdapter: ErrorTracker = { /* ... */ };
|
|
107
|
-
|
|
108
|
-
registerErrorTrackers([sentryAdapter, datadogAdapter]);
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## API Reference — Per-request identity
|
|
112
|
-
|
|
113
|
-
`runWithErrorTrackerIdentityScope(fn)` opens an empty AsyncLocalStorage scope before
|
|
114
|
-
session lookup. `setCurrentErrorTrackerIdentity(user)` fills the mutable box once the
|
|
115
|
-
session is known; `getCurrentErrorTrackerIdentity()` lets adapters read it without a
|
|
116
|
-
process-global user. `runWithErrorTrackerIdentity(user, fn)` is the convenience form
|
|
117
|
-
when the user is already known. Outside a scope, reads return `null` and writes are
|
|
118
|
-
no-ops.
|
|
119
|
-
|
|
120
|
-
These functions are server-only even though they are exported from the main core
|
|
121
|
-
barrel. They are intentionally absent from `@luckystack/core/client`.
|
|
122
|
-
|
|
123
|
-
## API Reference — Legacy Sentry Slot
|
|
124
|
-
|
|
125
|
-
### `initSharedSentry(instance: SentryInstance): void`
|
|
126
|
-
|
|
127
|
-
**Signature:**
|
|
128
|
-
```typescript
|
|
129
|
-
interface SentryInstance {
|
|
130
|
-
captureException: (exception: unknown, ...args: unknown[]) => string;
|
|
131
|
-
captureMessage: (message: string, ...args: unknown[]) => string;
|
|
132
|
-
setUser: (user: unknown) => void;
|
|
133
|
-
setContext: (key: string, context: unknown) => void;
|
|
134
|
-
startInactiveSpan?: (context: unknown) => unknown;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export const initSharedSentry = (instance: SentryInstance): void
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
**Behavior:** Stores the Sentry SDK instance in a module-level slot. New code should `registerErrorTracker(...)` from `@luckystack/error-tracking` instead.
|
|
141
|
-
|
|
142
|
-
### `captureException(error, context?): void`
|
|
143
|
-
|
|
144
|
-
**Behavior:**
|
|
145
|
-
- When the legacy Sentry slot is populated, calls `sentry.captureException(error, { extra: context })` (or no second arg when no context).
|
|
146
|
-
- ALSO calls `captureExceptionAcrossTrackers(error, context)` so any modern adapters receive the same event.
|
|
147
|
-
|
|
148
|
-
**Concurrency note:** Context is passed inline via the hint-shaped second argument so contexts don't leak across concurrent captures. The previous implementation used `sentry.setContext('additional', context)` which was process-global and leaked under concurrent captures.
|
|
149
|
-
|
|
150
|
-
### `captureMessage(message, level?, context?): void`
|
|
151
|
-
|
|
152
|
-
**Behavior:** Mirror of `captureException` for messages. Level defaults to `'info'`.
|
|
153
|
-
|
|
154
|
-
### `setSentryUser(user): void`
|
|
155
|
-
|
|
156
|
-
Calls `sentry?.setUser(user)` and fans out via `setErrorTrackerUser(user)`.
|
|
157
|
-
|
|
158
|
-
### `startSpan(name, op): unknown`
|
|
159
|
-
|
|
160
|
-
**Behavior:**
|
|
161
|
-
- If `sentry?.startInactiveSpan` exists, returns `sentry.startInactiveSpan({ name, op })`.
|
|
162
|
-
- Otherwise falls back to `startSpanAcrossTrackers(name, op, () => undefined)` — useful when the project uses an adapter-only setup that supports spans.
|
|
163
|
-
|
|
164
|
-
## Edge cases
|
|
165
|
-
|
|
166
|
-
- All multi-adapter fan-out paths swallow per-tracker errors. There is no way to surface them — by design, since framework hot paths (`tryCatch`, hook dispatch) must never crash because of a misbehaving tracker.
|
|
167
|
-
- `registerErrorTracker` and `registerErrorTrackers` replace the active list — call once at boot. There is no `unregister` API.
|
|
168
|
-
- `beforeSend` and `forwarded` on `ErrorTrackerEvent` are advisory for adapter authors. Core does not call `beforeSend` itself; the adapter implementation is expected to honor it.
|
|
169
|
-
|
|
170
|
-
## Example — minimal custom adapter
|
|
171
|
-
|
|
172
|
-
```typescript
|
|
173
|
-
import { registerErrorTracker, type ErrorTracker } from '@luckystack/core';
|
|
174
|
-
|
|
175
|
-
const stdoutAdapter: ErrorTracker = {
|
|
176
|
-
name: 'stdout',
|
|
177
|
-
captureException: (error, context) => {
|
|
178
|
-
console.error('exception:', error, context);
|
|
179
|
-
},
|
|
180
|
-
captureMessage: (message, level, context) => {
|
|
181
|
-
console.log(`[${level}] ${message}`, context);
|
|
182
|
-
},
|
|
183
|
-
setUser: () => { /* no-op */ },
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
registerErrorTracker(stdoutAdapter);
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
## Related
|
|
190
|
-
|
|
191
|
-
- Function INDEX: `packages/core/CLAUDE.md`
|
|
192
|
-
- Architecture: `docs/ARCHITECTURE_EXTENSION_POINTS.md`
|
|
193
|
-
- README: `packages/core/README.md`, `packages/error-tracking/README.md`
|
|
194
|
-
- Source: `packages/core/src/errorTrackerRegistry.ts`, `sentrySetup.ts`
|
|
5
|
+
Request identity is scoped so concurrent requests cannot attribute errors to the wrong user; do not store it in a global mutable user slot. Sanitize context before it reaches adapters, including nested sensitive fields. The server-side scope must not enter browser bundles. See `../src/errorTrackerIdentity.ts`, `../src/redactedLogKeys.ts`, `../src/sentrySetup.ts`.
|