@luckystack/core 0.10.1 → 0.11.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 +13 -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/client.d.ts +46 -335
- package/dist/client.js +2 -2
- 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/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/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 -1886
- package/dist/index.js +8 -0
- 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.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 +2 -2
- package/CLAUDE.md +0 -256
- package/dist/pageRouteValidation-B4Cmtfjv.d.ts +0 -280
package/CLAUDE.md
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
# @luckystack/core
|
|
2
|
-
|
|
3
|
-
> AI summary + function INDEX. For deep specs see `docs/` next to this file.
|
|
4
|
-
|
|
5
|
-
## What this package does
|
|
6
|
-
|
|
7
|
-
Foundation package for LuckyStack. Owns the socket-first transport contracts (`apiMessage`, `syncMessage`, io-instance slot), the registries that every other framework package reads from (project config, deploy/services config, runtime maps, prisma/redis clients, notifier, email senders, error trackers, logger), and the cross-cutting primitives (rate limiter, CORS check, validateRequest, hook bus, response normalizer, offline queue, CSRF, cookies). Every other `@luckystack/*` package depends on this one and never reaches around it.
|
|
8
|
-
|
|
9
|
-
## When to USE
|
|
10
|
-
|
|
11
|
-
- Reading the active runtime config (`getProjectConfig()`, `getProjectName()`, `getBindAddress()`).
|
|
12
|
-
- Registering DI slots from the project's boot entry (`registerProjectConfig`, `registerPrismaClient`, `registerRedisClient`, `registerLogger`, `registerNotifier`, `registerEmailSender(s)`, `registerErrorTracker(s)`, `registerRuntimeMapsProvider`, `registerApiMethodMap`, `registerAvatarConfig`, `registerBindAddress`, `registerRateLimitStrategy`).
|
|
13
|
-
- Subscribing to lifecycle/transport events via `registerHook(...)` / `registerSyncHook(...)`.
|
|
14
|
-
- Calling the cross-cutting primitives directly when a feature package would be overkill (`tryCatch`, `notify`, `checkRateLimit`, `allowedOrigin`, `validateRequest`, `extractTokenFromSocket`, `extractTokenFromRequest`, the offline queue API).
|
|
15
|
-
- Reading shared transport types (`BaseSessionLayout`, `AuthProps`, `apiMessage`, `syncMessage`, `SOCKETSTATUS`).
|
|
16
|
-
|
|
17
|
-
## When NOT to suggest yet
|
|
18
|
-
|
|
19
|
-
- Building a feature endpoint: import `@luckystack/api` / `@luckystack/sync` instead — they wrap core's hooks and validation in the file-based routing contract.
|
|
20
|
-
- Sending email: use `@luckystack/email`'s `sendEmail(...)` (it routes through `getEmailSenderByName`).
|
|
21
|
-
- Booting an HTTP server: use `createLuckyStackServer` from `@luckystack/server` — it wires `registerBindAddress`, `setIoInstance`, `attachSocketRedisAdapter`, security headers, etc. for you.
|
|
22
|
-
- Replacing Sentry: register an adapter via `@luckystack/error-tracking` (which calls `registerErrorTracker(s)` here). Do not call `initSharedSentry` in new code.
|
|
23
|
-
- Anything React-coupled: import from `@luckystack/core/client` (browser-safe subpath) or from the consuming feature package's `/client` entry. The server barrel intentionally does NOT export `apiRequest`.
|
|
24
|
-
- Defining project session types: extend `BaseSessionLayout` in your project's own `config.ts` — do not edit the core type.
|
|
25
|
-
|
|
26
|
-
## Function Index
|
|
27
|
-
|
|
28
|
-
| Function / Export | 1-line | Deep doc |
|
|
29
|
-
| --- | --- | --- |
|
|
30
|
-
| `registerProjectConfig(config: ProjectConfigInput): void` | Deep-merge installer config over `DEFAULT_PROJECT_CONFIG`; call once at boot before any framework code runs. | -> docs/config-registry.md |
|
|
31
|
-
| `getProjectConfig(): ProjectConfig` | Read the active config at call time (never at module load). | -> docs/config-registry.md |
|
|
32
|
-
| `getProjectName(): string` | Resolve the Redis-prefix project name (config -> `PROJECT_NAME` env -> `'luckystack'`). | -> docs/config-registry.md |
|
|
33
|
-
| `isProjectConfigRegistered(): boolean` | Boot-time guard for code paths that must run after registration. | -> docs/config-registry.md |
|
|
34
|
-
| `DEFAULT_PROJECT_CONFIG: ProjectConfig` | Built-in defaults exported so consumers can spread + override structurally. | -> docs/config-registry.md |
|
|
35
|
-
| `registerDeployConfig(config: DeployConfigShape): DeployConfigShape` | Register the project's deploy topology (resources, environments, routing, development). | -> docs/config-registry.md |
|
|
36
|
-
| `getDeployConfig(): DeployConfigShape` | Read deploy topology (defaults to `{ resources: {} }`). | -> docs/config-registry.md |
|
|
37
|
-
| `isDeployConfigRegistered(): boolean` | Guard for router/boot probes that need a registered deploy config. | -> docs/config-registry.md |
|
|
38
|
-
| `registerServicesConfig(config: ServicesConfigShape): ServicesConfigShape` | Register services + presets topology (consumed by router/presetLoader). | -> docs/config-registry.md |
|
|
39
|
-
| `getServicesConfig(): ServicesConfigShape` | Throws when no services config has been registered. | -> docs/config-registry.md |
|
|
40
|
-
| `isServicesConfigRegistered(): boolean` | Non-throwing guard. | -> docs/config-registry.md |
|
|
41
|
-
| `registerAvatarConfig(config: AvatarConfigInput): void` | Override avatar disk format(s) + Cache-Control header. | -> docs/config-registry.md |
|
|
42
|
-
| `getAvatarConfig(): AvatarConfig` | Read active avatar config. | -> docs/config-registry.md |
|
|
43
|
-
| `DEFAULT_AVATAR_CONFIG: AvatarConfig` | Default formats `[{ extension: 'webp', contentType: 'image/webp' }]` + 24h cache. | -> docs/config-registry.md |
|
|
44
|
-
| `registerPortOverride(port)` / `getPortOverride()` | Browser-safe runtime bridge for a positional server port; consumer config falls back to its own `config.ports.ts` when empty. Exported from `@luckystack/core/config` and the main barrel. | -> docs/app-bootstrap.md |
|
|
45
|
-
| `registerBindAddress(address: { ip: string; port: number; configuredPort?: number }): void` | Store the intended pre-listen address plus optional consumer-default metadata; resets the OAuth pre-hop baseline. | -> docs/app-bootstrap.md |
|
|
46
|
-
| `registerBoundAddress(address: { ip: string; port: number }): void` | Store the address reported by `node:http` after bind while preserving the intended baseline. | -> docs/app-bootstrap.md |
|
|
47
|
-
| `getBindAddress(): { ip: string; port: string }` | Resolve the actually-bound address at call time (registry -> `SERVER_IP` + generic port fallback). | -> docs/app-bootstrap.md |
|
|
48
|
-
| `resolveDevCallbackUrl(callbackUrl: string): string` | In non-prod, rewrite a loopback OAuth callback from the intended/configured-default port to the actually-bound port; preserves explicit local ingress ports. | -> docs/app-bootstrap.md |
|
|
49
|
-
| `registerRuntimeMapsProvider(provider: RuntimeMapsProvider): void` | DI slot for generated api/sync maps; called by project `server/prod/runtimeMaps.ts`. | -> docs/app-bootstrap.md |
|
|
50
|
-
| `getRuntimeApiMaps(): Promise<RuntimeApiMapsResult>` | Async accessor for `{ apisObject, functionsObject }`. | -> docs/app-bootstrap.md |
|
|
51
|
-
| `getRuntimeSyncMaps(): Promise<RuntimeSyncMapsResult>` | Async accessor for `{ syncObject, functionsObject }`. | -> docs/app-bootstrap.md |
|
|
52
|
-
| `isRuntimeMapsProviderRegistered(): boolean` | Boot-fail-mode detector for `verifyBootstrap`. | -> docs/app-bootstrap.md |
|
|
53
|
-
| `registerApiMethodMap(map: ApiMethodMap): void` | Register generated `apiMethodMap` so `apiRequest` can look up real HTTP methods. | -> docs/app-bootstrap.md |
|
|
54
|
-
| `getRegisteredApiMethod(pagePath: string, apiName: string, version: string): HttpMethodLiteral \| undefined` | Lookup helper used by `isGetMethod`. | -> docs/app-bootstrap.md |
|
|
55
|
-
| `isApiMethodMapRegistered(): boolean` | Detect whether the prefix-heuristic fallback is active. | -> docs/app-bootstrap.md |
|
|
56
|
-
| `registerPrismaClient(client: PrismaClient, key?: string): PrismaClient` | Register a Prisma client into a slot (default `'default'`). Pass a `key` for graded credentials (e.g. `'ro'`/`'rw'`) or per-tenant clients. | -> docs/app-bootstrap.md |
|
|
57
|
-
| `registerRedisClient(client: RedisClient, key?: string): RedisClient` | Register an ioredis client into a slot (default `'default'`). A consumer-owned default keeps precedence across automatic secret refresh; rebuild it from your own secrets-resolved listener when needed. | -> docs/app-bootstrap.md |
|
|
58
|
-
| `getPrismaClient(): PrismaClient` | Read the `'default'` slot (registered client or lazy default resolver). | -> docs/app-bootstrap.md |
|
|
59
|
-
| `getRedisClient(): RedisClient` | Read the `'default'` slot (registered client or lazy default resolver). | -> docs/app-bootstrap.md |
|
|
60
|
-
| `getPrismaClientFor(key?: string): PrismaClient` | Read a specific slot. `'default'` falls back to the resolver; any other unregistered slot throws (never silently returns the privileged default). | -> docs/app-bootstrap.md |
|
|
61
|
-
| `getRedisClientFor(key?: string): RedisClient` | Read a specific Redis slot (same semantics as `getPrismaClientFor`). | -> docs/app-bootstrap.md |
|
|
62
|
-
| `getPrismaClientKeys(): string[]` / `getRedisClientKeys(): string[]` | Diagnostic — which slots have an explicitly-registered client. | -> docs/app-bootstrap.md |
|
|
63
|
-
| `DEFAULT_CLIENT_KEY: 'default'` | The reserved slot backing every framework internal + the `prisma`/`redis` proxies. | -> docs/app-bootstrap.md |
|
|
64
|
-
| `isPrismaClientRegistered(): boolean` | Boot guard — true when the `'default'` slot is registered. | -> docs/app-bootstrap.md |
|
|
65
|
-
| `isRedisClientRegistered(): boolean` | Boot guard — true when the `'default'` slot is registered. | -> docs/app-bootstrap.md |
|
|
66
|
-
| `resetClientsForTests(): void` | Test-only — drop every registered slot (default resolvers stay set). | -> docs/app-bootstrap.md |
|
|
67
|
-
| `isPrismaClientResolvable(): boolean` | Is a generated `@prisma/client` present in this install? (ADR 0020 — `@prisma/client` is lazily require()d, never statically imported: with `orm: 'none'` it may legitimately be absent; a DB access without a registered client then throws an actionable error pointing at `luckystack/core/clients.ts`.) | -> docs/app-bootstrap.md |
|
|
68
|
-
| `registerDbHealthCheck(check)` / `getDbHealthCheck()` / `isDbHealthCheckRegistered()` / `resetDbHealthCheckForTests()` | Pluggable `/readyz` database probe (ADR 0020): a registered check wins; without one the server falls back to the built-in Prisma ping when Prisma is present, else reports `'skipped'` so a DB-less project can go ready. Types `DbHealthCheck` / `DbHealthResult` (`boolean \| 'skipped'`). | -> docs/app-bootstrap.md |
|
|
69
|
-
| `redis` | Proxy that forwards every call to the currently registered ioredis client. Applies `applyStrayKeyPrefix` to single-key commands as a best-effort namespace net (keys containing `:` pass through untouched). | -> docs/redis-adapter.md |
|
|
70
|
-
| `getRedisConnectionOptions(): RedisConnectionOptions` | Single source of truth for `{ host, port, username?, password? }` (router reuses for cross-env probes). | -> docs/redis-adapter.md |
|
|
71
|
-
| `formatKey(namespace: string, suffix?: string): string` | Build a namespaced Redis key through the active formatter. The single authority every framework key-site routes through (`-session`, `-activeUsers`, `-pwreset`, `-email-change`, `-oauth-state`, `:rate-limit`). Default reproduces historical key bytes (zero migration). | -> docs/redis-adapter.md |
|
|
72
|
-
| `registerRedisKeyFormatter(fn: RedisKeyFormatter): void` | Override how every framework Redis key is namespaced (multi-tenant per-tenant prefixing). Must preserve the `<namespace-root>:<suffix>` join so `SCAN` enumeration still works. | -> docs/redis-adapter.md |
|
|
73
|
-
| `getRedisKeyFormatter(): RedisKeyFormatter` / `defaultRedisKeyFormatter` / `resetRedisKeyFormatterForTests()` | Read the active formatter / the built-in default / test reset. | -> docs/redis-adapter.md |
|
|
74
|
-
| `applyStrayKeyPrefix(key: string): string` | Project-prefix an un-namespaced (colon-free) key; pass-through for already-namespaced keys. Used by the `redis` proxy net. | -> docs/redis-adapter.md |
|
|
75
|
-
| `acquireLease(name, ttlMs): Promise<string \| null>` | Acquire an exclusive Redis lease (`SET NX PX`). Returns an owner token, or null if held. Single-Redis best-effort (not Redlock) — the lease is a primitive; the renew loop is app code. | -> docs/redis-adapter.md |
|
|
76
|
-
| `renewLease(name, token, ttlMs): Promise<boolean>` | Owner-checked compare-and-pexpire (Lua); extends only if `token` still holds the lease. | -> docs/redis-adapter.md |
|
|
77
|
-
| `releaseLease(name, token): Promise<boolean>` | Owner-checked compare-and-delete (Lua); releasing another owner's lease is a no-op. | -> docs/redis-adapter.md |
|
|
78
|
-
| `attachSocketRedisAdapter(io: SocketIOServer): void` | Wire `@socket.io/redis-adapter` with duplicated pub/sub clients so room broadcasts span instances. The adapter `key` defaults to `resolveSocketAdapterKey()`. | -> docs/redis-adapter.md |
|
|
79
|
-
| `resolveSocketAdapterKey(): string` | The Redis-adapter channel key: `<project>:<envKey>:socket.io`. Instances on the same key + same Redis are ONE cluster; the env part keeps a dev laptop and a staging deploy that share a Redis server apart. | -> docs/redis-adapter.md |
|
|
80
|
-
| `setIoInstance(io: SocketIOServer \| null): void` | Module-level slot for the running Socket.io server. | -> docs/socket-bootstrap.md |
|
|
81
|
-
| `getIoInstance(options?: { raw?: boolean }): SocketIOServer \| null` | Read the slot from framework code that needs to broadcast. Outside production returns a guarded view that THROWS on `sockets.adapter.rooms` / `adapter.sids` and on enumerating `sockets.sockets` (per-instance maps; `.get(id)` allowed). `{ raw: true }` = explicit opt-in for deliberate per-instance work. Production always returns the raw instance. | -> docs/socket-bootstrap.md |
|
|
82
|
-
| `getRoomSockets(room, { userId? }): Promise<RemoteSocket[]>` | Cross-instance room enumeration: formats `room` under `'broadcast'`, then `io.in(...).fetchSockets()`; `'all'` = every socket everywhere. Throws when no Socket.io server is registered (never a silent `[]`). Use instead of the local adapter maps. | -> docs/socket-bootstrap.md |
|
|
83
|
-
| `socket`, `setSocket`, `incrementResponseIndex`, `waitForSocket` | Client-side socket singleton + response-index counter. | -> docs/socket-bootstrap.md |
|
|
84
|
-
| `extractTokenFromSocket(socket): string \| null` | Read session token from Socket.io handshake. Token-mode reads `handshake.auth.token` then cookie; cookie-mode reads ONLY the cookie unless `http.acceptBearerInCookieMode` is true (CORE-O10). | -> docs/socket-bootstrap.md |
|
|
85
|
-
| `extractTokenFromRequest(request): string \| null` | Read session token from a Node IncomingMessage. Token-mode reads `Authorization: Bearer` then cookie; cookie-mode reads ONLY the cookie unless `http.acceptBearerInCookieMode` is true (CORE-O10). | -> docs/socket-bootstrap.md |
|
|
86
|
-
| `allowedOrigin(origin: string): boolean` | Same-origin + project-configured allow-list CORS check; dispatches `corsRejected` hook on miss. | -> docs/socket-bootstrap.md |
|
|
87
|
-
| `validateRequest({ auth, user }): ValidationResult` | Evaluates the `auth.additional[]` predicates against the session; returns success immediately when `additional` is absent. Does NOT check `auth.login` — login is enforced by the surrounding API/sync handler, not by this function. | -> docs/session-types.md |
|
|
88
|
-
| `isFalsy(value): boolean` | Helper used inside `validateRequest`. | -> docs/session-types.md |
|
|
89
|
-
| `validateInputByType(value, type)` | Runtime input validation; lazy-loads `@luckystack/devkit` in dev. | -> docs/session-types.md |
|
|
90
|
-
| `registerNotifier(notifier: Notifier): void` | DI for client-side toast notifier (success/error/info/warning). | -> docs/app-bootstrap.md |
|
|
91
|
-
| `getNotifier(): Notifier` | Read active notifier (no-op default). | -> docs/app-bootstrap.md |
|
|
92
|
-
| `notify: Notifier` | Delegating wrapper used by framework hot paths. | -> docs/app-bootstrap.md |
|
|
93
|
-
| `registerEmailSender(sender: EmailSender): void` | Legacy single-sender registration; mirrors into `default` slot. `send(message, context?)` may honor `AbortSignal` + stable idempotency key; timeout outcomes can be explicitly unknown. | -> docs/app-bootstrap.md |
|
|
94
|
-
| `registerEmailSenders(senders: EmailSenderRegistry): void` | Multi-adapter registration (`'default'`, `'transactional'`, `'marketing'`, custom). | -> docs/app-bootstrap.md |
|
|
95
|
-
| `getEmailSender(): EmailSender \| null` | Read legacy/`default` sender. | -> docs/app-bootstrap.md |
|
|
96
|
-
| `getEmailSenderByName(name: string): EmailSender \| null` | Read a specific slot (falls back to legacy sender for `'default'`). | -> docs/app-bootstrap.md |
|
|
97
|
-
| `listEmailSenderNames(): string[]` | Diagnostic helper. | -> docs/app-bootstrap.md |
|
|
98
|
-
| `isEmailSenderRegistered(): boolean` | Boot guard. | -> docs/app-bootstrap.md |
|
|
99
|
-
| `registerLogger(logger: Logger): void` | Plug in Pino/Winston/Datadog (or `createDevLogger()` for colored dev output). | -> docs/app-bootstrap.md |
|
|
100
|
-
| `getLogger(): Logger` | Read at call time (default = console.{debug,info,warn,error}). | -> docs/app-bootstrap.md |
|
|
101
|
-
| `isLoggerRegistered(): boolean` | Boot guard. | -> docs/app-bootstrap.md |
|
|
102
|
-
| `createDevLogger(): Logger` | Factory for the dev-mode colored logger (depends on `initConsolelog()`). | -> docs/app-bootstrap.md |
|
|
103
|
-
| `resetLoggerForTests(): void` | Test-only — restore default logger. | -> docs/app-bootstrap.md |
|
|
104
|
-
| `registerRedactedLogKeys(keys: Iterable<string>): void` | Add keys that the framework will redact from log payloads. | -> docs/app-bootstrap.md |
|
|
105
|
-
| `getRedactedLogKeys(): Set<string>` | Read current redacted-keys set. | -> docs/app-bootstrap.md |
|
|
106
|
-
| `isRedactedLogKey(key: string): boolean` | Hot-path lookup used inside the framework. Matches the exact registered set AND any key whose lowercased form ENDS WITH a sensitive suffix (`token`/`secret`/`apikey`/`password`) — so `targetToken` / `clientSecret` / `stripeApiKey` redact without being registered, while `tokenCount` / `secretSanta` stay untouched. | -> docs/app-bootstrap.md |
|
|
107
|
-
| `resetRedactedLogKeysForTests(): void` | Test-only reset. | -> docs/app-bootstrap.md |
|
|
108
|
-
| `sanitizeForLog(value): unknown` | Recursive redaction pass — deep-clones `value`, replacing any redacted-key field with `REDACTED_PLACEHOLDER`. Applied on the `captureException`/`captureMessage` fan-out so a raw token nested in context never reaches an adapter (SYNC-17). | -> docs/app-bootstrap.md |
|
|
109
|
-
| `DEFAULT_REDACTED_LOG_KEYS: readonly string[]` | The built-in masked-key set (token, password, authorization, cookie, csrfToken, apiKey, secret, …) seeded into the redacted-keys registry. Widened in 0.2.0 (added `csrftoken`/`apikey`/`secret`) plus suffix matching in `isRedactedLogKey`. | -> docs/app-bootstrap.md |
|
|
110
|
-
| `REDACTED_PLACEHOLDER: string` | The constant `sanitizeForLog` substitutes for a redacted value. | -> docs/app-bootstrap.md |
|
|
111
|
-
| `initConsolelog(): void` | Monkey-patch `console.*` to render trailing color string (dev only). | -> docs/app-bootstrap.md |
|
|
112
|
-
| `registerLocaleReloader(reloader: LocaleReloader): void` | DI for the dev-only i18n hot-reload trigger. | -> docs/app-bootstrap.md |
|
|
113
|
-
| `getLocaleReloader(): LocaleReloader \| null` | Read active reloader (returns null when no project supplied one). | -> docs/app-bootstrap.md |
|
|
114
|
-
| `registerErrorTracker(tracker: ErrorTracker): void` | Single-tracker registration (replaces previous). | -> docs/error-tracker-registry.md |
|
|
115
|
-
| `registerErrorTrackers(trackers: ErrorTracker[]): void` | Multi-tracker registration (replaces the list). | -> docs/error-tracker-registry.md |
|
|
116
|
-
| `appendErrorTracker(tracker: ErrorTracker): void` | Accumulate-not-replace registration — appends a tracker, deduping by `ErrorTracker.name`. Use for async auto-registration (e.g. PostHog) so it can't clobber a consumer overlay. | -> docs/error-tracker-registry.md |
|
|
117
|
-
| `runWithErrorTrackerIdentity(user, fn): T` / `getCurrentErrorTrackerIdentity(): ErrorTrackerUser \| null` | AsyncLocalStorage per-event identity — wrap request handling in `runWithErrorTrackerIdentity(user, fn)` and capture sites read the current identity instead of a mutable global (no cross-request bleed). | -> docs/error-tracker-registry.md |
|
|
118
|
-
| `registerPreCaptureFilter(filter: PreCaptureFilter): void` | Register a filter run before every capture fan-out (drop/transform events centrally). | -> docs/error-tracker-registry.md |
|
|
119
|
-
| `startSpanHandle(name, op): SpanHandle` | Handle-style span — returns a `{ finish() }` handle (vs the callback-style `startSpanAcrossTrackers`). | -> docs/error-tracker-registry.md |
|
|
120
|
-
| `flushErrorTrackers(): Promise<void>` | Flush lifecycle — calls every adapter's optional `flush?()`; call on shutdown so buffered events aren't lost. | -> docs/error-tracker-registry.md |
|
|
121
|
-
| `getActiveErrorTrackers(): ErrorTracker[]` | Read active trackers. | -> docs/error-tracker-registry.md |
|
|
122
|
-
| `captureExceptionAcrossTrackers(error, context?): void` | Fan-out exception capture; per-tracker errors are swallowed. | -> docs/error-tracker-registry.md |
|
|
123
|
-
| `captureMessageAcrossTrackers(message, level, context?): void` | Fan-out message capture. | -> docs/error-tracker-registry.md |
|
|
124
|
-
| `setErrorTrackerUser(user: ErrorTrackerUser \| null): void` | Fan-out user identification. | -> docs/error-tracker-registry.md |
|
|
125
|
-
| `recordMetricAcrossTrackers(name, value, tags?): void` | Optional metric fan-out (skips trackers without `recordMetric`). | -> docs/error-tracker-registry.md |
|
|
126
|
-
| `startSpanAcrossTrackers<T>(name, op, fn): T` | First tracker that supports spans wins; falls back to direct `fn()`. | -> docs/error-tracker-registry.md |
|
|
127
|
-
| `initSharedSentry(instance)` | Legacy Sentry slot (kept for backwards compatibility). | -> docs/error-tracker-registry.md |
|
|
128
|
-
| `captureException(error, context?): void` | Legacy fan-out: Sentry slot + adapter list. | -> docs/error-tracker-registry.md |
|
|
129
|
-
| `captureMessage(message, level?, context?): void` | Legacy fan-out: Sentry slot + adapter list. | -> docs/error-tracker-registry.md |
|
|
130
|
-
| `setSentryUser(user): void` | Legacy fan-out: Sentry slot + adapter list. | -> docs/error-tracker-registry.md |
|
|
131
|
-
| `startSpan(name, op): unknown` | Legacy span helper. | -> docs/error-tracker-registry.md |
|
|
132
|
-
| `checkRateLimit(params: CheckRateLimitParams): Promise<RateLimitResult>` | Delegates to the active strategy's `check()`. | -> docs/rate-limit-strategy.md |
|
|
133
|
-
| `getRateLimitStatus(key, limit): Promise<RateLimitResult>` | Read-only status (does not increment). | -> docs/rate-limit-strategy.md |
|
|
134
|
-
| `clearRateLimit(key): Promise<void>` | Reset a single key. | -> docs/rate-limit-strategy.md |
|
|
135
|
-
| `clearAllRateLimits(): Promise<void>` | Reset every key under the active strategy's namespace. | -> docs/rate-limit-strategy.md |
|
|
136
|
-
| `registerRateLimitStrategy(strategy: RateLimitStrategy): void` | Swap the backend (token-bucket, edge-KV, per-tier, no-op). | -> docs/rate-limit-strategy.md |
|
|
137
|
-
| `getRateLimitStrategy(): RateLimitStrategy` | Read the currently active strategy. | -> docs/rate-limit-strategy.md |
|
|
138
|
-
| `defaultRateLimitStrategy: RateLimitStrategy` | Built-in memory-or-redis backend. | -> docs/rate-limit-strategy.md |
|
|
139
|
-
| `registerHook<TName>(name, handler): void` | Subscribe an async handler to a lifecycle event. | -> docs/hooks.md |
|
|
140
|
-
| `registerMiddlewareHandler(handler: MiddlewareHandler): void` | Register the GLOBAL route-guard fallback. Called by `<Middleware>` / `useRouter` only when no per-page middleware is registered for the visited route. Optional — framework default allows by-default. | -> docs/hooks.md |
|
|
141
|
-
| `getMiddlewareHandler(): MiddlewareHandler` | Read the active global handler. | -> docs/hooks.md |
|
|
142
|
-
| `registerPageMiddleware(path: string, fn: PageMiddleware): void` | Register a per-page route guard. Auto-called by `src/main.tsx`'s `getRoutes()` for every page that exports `middleware`. Per-page takes precedence over the global handler. | -> docs/hooks.md |
|
|
143
|
-
| `getPageMiddleware(path: string): PageMiddleware \| undefined` | Lookup the per-page guard for a given route. Framework-internal. | -> docs/hooks.md |
|
|
144
|
-
| `hasPageMiddleware(path: string): boolean` | Existence check. | -> docs/hooks.md |
|
|
145
|
-
| `MiddlewareInput`, `MiddlewareResult`, `MiddlewareOutcome`, `MiddlewareHandler`, `PageMiddleware` (types) | Route-guard contract types. Both handler shapes share the same signature. `MiddlewareResult` has three deny-capable shapes: allow (`{ success: true }`), redirect (`{ success: false, redirect }`) and deny-in-place (`{ success: false, status }` — keeps the URL, renders a status state). | -> docs/hooks.md |
|
|
146
|
-
| `resolveMiddlewareOutcome(result): MiddlewareOutcome` | Normalizes a middleware result to `allow` / `redirect` / `deny` / `fallback`. THE single interpretation, shared by `<Middleware>` and `useRouter` so programmatic navigation and direct URL hits cannot diverge. | -> docs/hooks.md |
|
|
147
|
-
| `validatePagePath(srcRelativePath, rules?): PagePathValidationResult` | Pure validator for the invisible-parent folder convention used by `src/main.tsx`'s page auto-discovery AND the `scaffold:page` CLI. Returns `{ valid, route?, reason? }`. | -> docs/hooks.md |
|
|
148
|
-
| `DEFAULT_PAGE_ROUTE_RULES: PageRouteRules` | Default config (private-folder prefix `'_'` + reserved framework folders). | -> docs/hooks.md |
|
|
149
|
-
| `dispatchHook<TName>(name, payload): Promise<DispatchResult>` | Internal: framework code invokes registered handlers in order; first stop signal aborts. | -> docs/hooks.md |
|
|
150
|
-
| `clearAllHooks(): void` | Test-only — drop every registered handler (sync + async). | -> docs/hooks.md |
|
|
151
|
-
| `registerSyncHook<TName>(name, handler): void` | Subscribe a synchronous mutator to a hot-path hook (e.g. error normalization). | -> docs/hooks.md |
|
|
152
|
-
| `dispatchSyncHook<TName>(name, payload): void` | Internal: framework code invokes sync handlers; payload is mutated in place. | -> docs/hooks.md |
|
|
153
|
-
| `BaseSessionLayout`, `SessionLocation`, `AuthProps` (types) | Foundational session-shape types; project `SessionLayout` extends `BaseSessionLayout`. | -> docs/session-types.md |
|
|
154
|
-
| `HookSessionShape`, `HookName`, `HookHandler`, `HookResult`, `HookStopSignal`, `HookPayloads` (types) | Hook contract types (augmentable via TS module augmentation). | -> docs/hooks.md |
|
|
155
|
-
| `preServerStop` hook + `PreServerStopPayload` (type) | Graceful-shutdown lifecycle hook — `@luckystack/server` dispatches it once on SIGTERM/SIGINT before the server stops accepting connections. Payload `{ reason, timeoutMs? }`. Best-effort (a stop signal does NOT abort shutdown); use to flush trackers / drain queues / close pools. | -> docs/hooks.md |
|
|
156
|
-
| `isOnline()` / `enqueueApiRequest` / `enqueueSyncRequest` / `removeApiQueueItem` / `removeSyncQueueItem` / `removeApiQueueItemsByKey` / `flushApiQueue` / `flushSyncQueue` / `getApiQueueSize` / `getSyncQueueSize` | Client-side offline queue with per-item `dropPolicy` and global max-size/max-age caps. | -> docs/socket-bootstrap.md |
|
|
157
|
-
| `getCsrfToken(origin?)`, `clearCsrfToken()`, `httpFetch(...)` | Origin-scoped CSRF-aware fetch wrapper; routed token-mode calls also attach bearer auth. | -> docs/socket-bootstrap.md |
|
|
158
|
-
| `invokeRoutedHttp(...)` (`/client`) | Browser-safe JSON/SSE invocation primitive with streaming, timeout and composed AbortSignals; used by typed API/sync routed mode. | -> docs/socket-bootstrap.md |
|
|
159
|
-
| `issueOneTimeToken(namespace, ttlSeconds, payload): OneTimeTokenHandle` | Mint a single-use Redis-backed token (returns `{ token, store() }`). HASHED AT REST — only `sha256(token)` is stored as the key, never the raw token. Used by `@luckystack/login` for password-reset + email-change links. | -> docs/app-bootstrap.md |
|
|
160
|
-
| `consumeOneTimeToken(namespace, token): Promise<string \| null>` | Atomically validate + consume a one-time token (single `MULTI` GET+DEL → at-most-once). Returns the stored payload string, or null on miss/expired/reused. | -> docs/app-bootstrap.md |
|
|
161
|
-
| `consumeOneTimeTokenJson<T>(namespace, token): Promise<T \| null>` | `consumeOneTimeToken` + JSON-parse; null on miss OR malformed payload. | -> docs/app-bootstrap.md |
|
|
162
|
-
| `OneTimeTokenHandle` (type) | `{ token: string; store(): Promise<void> }` returned by `issueOneTimeToken`. | -> docs/app-bootstrap.md |
|
|
163
|
-
| `registerCsrfConfig(input: Partial<CsrfConfig>): void` | Override the CSRF cookie name, header name, token length, or cookie options. | -> docs/csrf-config.md |
|
|
164
|
-
| `getCsrfConfig(): CsrfConfig` | Read the active CSRF config at call time (defaults to `DEFAULT_CSRF_CONFIG`). | -> docs/csrf-config.md |
|
|
165
|
-
| `DEFAULT_CSRF_CONFIG: CsrfConfig` | Built-in defaults (`csrf-token` cookie, `x-csrf-token` header, 32-byte token). | -> docs/csrf-config.md |
|
|
166
|
-
| `resetCsrfConfigForTests(): void` | Test-only — restore CSRF defaults between scenarios. | -> docs/csrf-config.md |
|
|
167
|
-
| `registerSocketMiddleware(mw: SocketMiddleware): void` | Wedge an `io.use(...)` middleware into the framework's socket bootstrap; runs before any `connect` handler. | -> docs/socket-bootstrap.md |
|
|
168
|
-
| `getSocketMiddlewares(): readonly SocketMiddleware[]` | Read the registered middleware list. | -> docs/socket-bootstrap.md |
|
|
169
|
-
| `clearSocketMiddlewares(): void` | Drop every registered middleware (test/hot-reload helper). | -> docs/socket-bootstrap.md |
|
|
170
|
-
| `applySocketMiddlewares(io: SocketIOServer): void` | Wire every registered middleware into the running Socket.io server (called from `@luckystack/server`'s `loadSocket`). | -> docs/socket-bootstrap.md |
|
|
171
|
-
| `writeBootUuid(envKey?): Promise<string>` | Write a fresh UUID to `luckystack:boot:<envKey>` with the configured TTL. | -> docs/app-bootstrap.md |
|
|
172
|
-
| `readBootUuid(envKey?): Promise<string \| null>` | Read the boot UUID (router cross-checks against `/_health`). | -> docs/app-bootstrap.md |
|
|
173
|
-
| `refreshBootUuid(envKey?): Promise<void>` | Extend the existing environment UUID without rotating it; recreate it when Redis recovered after expiry. | -> docs/app-bootstrap.md |
|
|
174
|
-
| `startBootUuidHeartbeat(envKey?): BootUuidHeartbeat` | Start a non-overlapping, unref'd TTL/3 refresh loop; `stop()` cancels it during server shutdown. | -> docs/app-bootstrap.md |
|
|
175
|
-
| `resolveRuntimeMode()` / `isProductionRuntime()` / `isTestRuntime()` | `NODE_ENV` application mode; security, route-map and dev-tool policy uses this axis. | -> docs/app-bootstrap.md |
|
|
176
|
-
| `resolveEnvKey(): string` | Deploy-topology identity: `LUCKYSTACK_ENV` -> `NODE_ENV` -> `'development'`; not a dev/prod-mode check. | -> docs/app-bootstrap.md |
|
|
177
|
-
| `BOOT_KEY_PREFIX: 'luckystack:boot:'` | Constant — single source of truth so router can't drift. | -> docs/app-bootstrap.md |
|
|
178
|
-
| `collectSynchronizedEnvKeys()` / `computeSynchronizedEnvHashes(bootUuid?)` / `hashSynchronizedValue(value, bootUuid?)` | Cross-env drift detection helpers for the router boot handshake. Both hash helpers now honour `http.healthHash` (default `'plain'` = byte-identical to before); the optional `bootUuid?` arg is only needed when `http.healthHash.salt === '@bootUuid'`. Zero-arg callers unchanged. | -> docs/app-bootstrap.md |
|
|
179
|
-
| `hashSynchronizedValueWith({ mode, salt }, value)` / `resolveHealthHashConfig(bootUuid?)` | Shared health-hash primitives so the router can hash a local value with the SAME `{mode,salt}` (+ resolved boot UUID) the backend used and the boot-handshake compare still matches. | -> docs/app-bootstrap.md |
|
|
180
|
-
| `registerRoomNameFormatter(fn)` / `getRoomNameFormatter()` / `formatRoomName(raw, ctx)` / `defaultRoomNameFormatter` | Room-name formatter registry — route a raw room name through `formatRoomName(raw, { purpose, userId })` (e.g. per-tenant prefixing). Default is identity. Types `RoomNameFormatter` / `RoomNameFormatterContext`. `userId` is context only — a formatter must NOT fold it into a content room's physical name (join and fan-out would diverge); see docs/socket-bootstrap.md. | -> docs/socket-bootstrap.md |
|
|
181
|
-
| `applyCookiePrefixConstraints(baseName, prefix, secureOverride?)` (via `cookies` barrel) | Pure `__Host-`/`__Secure-` constraint resolver for the server session-cookie builder (forces `Secure`, forbids `Domain`, pins `Path=/` per the prefix rules). Type `CookiePrefixConstraints`. | -> docs/socket-bootstrap.md |
|
|
182
|
-
| `tryCatch<T>(fn): Promise<[Error \| null, T \| null]>` | Tuple-style async error handling used everywhere in the framework. | -> docs/app-bootstrap.md |
|
|
183
|
-
| `sleep(ms): Promise<void>` | `setTimeout`-based delay. | -> docs/app-bootstrap.md |
|
|
184
|
-
| `getParams(request)` | Parse URL params from a Node request. | -> docs/app-bootstrap.md |
|
|
185
|
-
| `serveAvatar(...)` | HTTP handler for `/avatars/:fileId`; rejects path-traversal, applies `AvatarConfig`. | -> docs/app-bootstrap.md |
|
|
186
|
-
| `processUpload(input: ProcessUploadInput): Promise<ProcessUploadResult>` | Wraps a project encode/save callback with the `onUploadStart`/`onUploadComplete` hook contract. | -> docs/hooks.md |
|
|
187
|
-
| Response normalizers (`normalizeErrorResponse`, `extractLanguageFromHeader`, ...) | Shared error-shape + i18n helpers; dispatches `preErrorNormalize` / `postErrorNormalize` sync hooks. | -> docs/hooks.md |
|
|
188
|
-
| `serviceRoute` exports | Helpers to build router-routable URLs across services. | -> docs/socket-bootstrap.md |
|
|
189
|
-
| `socketEvents` exports | Wire-protocol constants for socket events. | -> docs/socket-bootstrap.md |
|
|
190
|
-
| `paths` exports (`getGeneratedApiDocsPath`, `getApiMethodMapPath`, ...) | Paths resolved through `projectConfig.paths`. | -> docs/config-registry.md |
|
|
191
|
-
| `cookies` exports | Cookie parse/serialise helpers used by the HTTP layer. | -> docs/socket-bootstrap.md |
|
|
192
|
-
| `httpApiUtils` exports (`inferHttpMethod`, `getEffectiveHttpMethod`, `isMethodAllowed`) | Shared HTTP helpers (method inference + validation). NOTE: `isMethodAllowed` returns false for `OPTIONS` — answer preflights before the route check. | -> docs/socket-bootstrap.md |
|
|
193
|
-
| `tryCatchSync<T>(fn): [Error \| null, T \| null]` | Synchronous tuple-style error handling (sync counterpart to `tryCatch`). | -> docs/app-bootstrap.md |
|
|
194
|
-
| `deepMerge<T>(base, override)` / `isPlainObject(value)` (`configUtils`) | Shared config deep-merge primitive (every registry routes through it) + plain-object guard. Skips `__proto__`/`constructor`/`prototype` keys. | -> docs/config-registry.md |
|
|
195
|
-
| `createRegistry(...)` | Generic DI-slot registry factory backing the config/client/strategy registries. | -> docs/config-registry.md |
|
|
196
|
-
| `resolveClientIp({ rawAddress, headers, trustProxy, trustedProxyHopCount })` / `UNKNOWN_CLIENT_IP` | Resolve the real client IP for per-IP rate-limit keying (XFF/x-real-ip only when `trustProxy`). Skips `trustedProxyHopCount` hops from the RIGHT of XFF (default 1 = immediate upstream proxy); never trusts the leftmost client-controlled hop (CORE-O3). | -> docs/app-bootstrap.md |
|
|
197
|
-
| `isLoopbackIp(ip: string): boolean` | True for `127.0.0.0/8` / `::1` / `localhost`. Used for `rateLimiting.skipLoopbackInDev` keying (skip the cross-route IP abuse cap for loopback in non-prod). | -> docs/app-bootstrap.md |
|
|
198
|
-
| `registerStrayPrefixCommand(...commands)` | Opt a custom single-key Redis command into the `redis` proxy's stray-prefix net. | -> docs/redis-adapter.md |
|
|
199
|
-
| `attachSocketRedisAdapter(io, options?)` | Now accepts `{ adapterOptions, pubClient, subClient }` to tune `createAdapter` / supply pre-built clients. | -> docs/redis-adapter.md |
|
|
200
|
-
|
|
201
|
-
### `/client` subpath (browser-safe React + i18n surface)
|
|
202
|
-
|
|
203
|
-
Imported from `@luckystack/core/client` (the server barrel intentionally does NOT export these): `apiRequest`, `syncRequest` + sync-callback helpers, the offline-queue API, `registerClientHook` (returns an unsubscribe) + `ClientHookPayloadMap` (`preLogin`/`postLogin`/`postLogout`/`queueItemDropped`), `useTheme`, the `TranslationProvider` + i18n registry, `SessionProvider`/session context, `registerMiddlewareHandler`/`registerPageMiddleware`, and the CSRF-aware `httpFetch`.
|
|
204
|
-
|
|
205
|
-
Shared helpers available from BOTH barrels: `sleep`, `tryCatch`, `tryCatchSync`. Note `tryCatch` resolves to a different implementation per barrel — the client gets `tryCatchClient`, which lazy-imports the capture seam so `node:async_hooks` never enters a Vite bundle. `tryCatchSync` needs no such split (it has zero imports and deliberately does not auto-capture). `barrelParity.test.ts` fails the build if another browser-shipped helper is exported server-side but forgotten on `/client`.
|
|
206
|
-
|
|
207
|
-
### `./eslint` subpath
|
|
208
|
-
|
|
209
|
-
`@luckystack/core/eslint` exposes the shared ESLint rule set (the CLAUDE.md-invariant rules). Requires the optional `eslint@^9.0.0` peer. Includes `luckystack/no-local-socket-enumeration` (error when `@luckystack/core` or `@luckystack/sync` is installed): flags `.adapter.rooms` / `.adapter.sids` and enumeration of `.sockets.sockets` (`values`/`keys`/`entries`/`forEach`/`size`, `for…of`, spread) — per-instance maps that miss the rest of the cluster; `.get(id)` is allowed. Opt out per site with `// eslint-disable-next-line luckystack/no-local-socket-enumeration -- <why>`; the cross-instance replacement is `getRoomSockets()`.
|
|
210
|
-
|
|
211
|
-
## Config keys
|
|
212
|
-
|
|
213
|
-
Env vars read directly by core (via `env.ts` and call-time helpers):
|
|
214
|
-
|
|
215
|
-
| Env var | Default | Consumer |
|
|
216
|
-
| --- | --- | --- |
|
|
217
|
-
| `NODE_ENV` | `'development'` | Zod-validated application runtime mode, mirrored to `process.env`. |
|
|
218
|
-
| `SERVER_IP` | `'127.0.0.1'` | `getBindAddress()` fallback. |
|
|
219
|
-
| `SECURE` | `'false'` | `allowedOrigin` scheme selection. |
|
|
220
|
-
| `REDIS_HOST` | `'127.0.0.1'` | `redis.ts` default client + `getRedisConnectionOptions`. |
|
|
221
|
-
| `REDIS_PORT` | `'6379'` | Same. |
|
|
222
|
-
| `REDIS_USER` | (unset) | Optional ioredis auth. |
|
|
223
|
-
| `REDIS_PASSWORD` | (unset) | Optional ioredis auth. |
|
|
224
|
-
| `PROJECT_NAME` | `'luckystack'` | Redis-prefix fallback in `getProjectName()`. |
|
|
225
|
-
| `LUCKYSTACK_ENV` | (unset) | Deploy-topology identity for boot UUID/router handshake/observability; never enables dev behavior. |
|
|
226
|
-
| `LUCKYSTACK_ENV_FILES` | `.env,.env.local` | Ambient override for `getEnvFiles()` / `loadEnvFiles()` — comma-separated list of env files to load, "later overrides earlier". |
|
|
227
|
-
|
|
228
|
-
`registerProjectConfig` slots (see `ProjectConfig` for full surface): `app.publicUrl`, `transport.invocation` (`'socket'` default or `'routed-http'`), `logging.*`, `rateLimiting.{enabled, store, redisKeyPrefix, defaultApiLimit, defaultIpLimit, windowMs, cleanupIntervalMs, onStoreError, skipLoopbackInDev, identity, auth}`, `session.{basedToken, expiryDays, perUser, maxConcurrentPerUser, onConflict, notifyOldDeviceOnRevoke, projectName}`, `http.{sessionCookie*, sessionCookieDomain, sessionCookiePrefix, sessionCookieSecure, requestBodyMaxBytes, trustProxy, trustedProxyHopCount, acceptBearerInCookieMode, healthEndpoint, liveEndpoint, readyEndpoint, testResetEndpoint, healthHash, stream, securityHeaders, cors}`, `auth.{credentials, oauthStateTtlSeconds, passwordPolicy, emailMaxLength, nameMaxLength, bcryptRounds, providerAccountStrategy, forgotPassword, passwordResetTtlSeconds, passwordResetBrand, emailChangeTtlSeconds, allowRegistration, passwordResetPath, emailChangeConfirmPath}`, `socket.{maxHttpBufferSize, pingTimeout, pingInterval, activityHeartbeatThrottleMs}`, `api.{requestTimeoutMs}`, `validation.{runtimeMode}`, `sync.{streamThrottle, fanoutYieldEvery, fanoutYieldMs, requestTimeoutMs, allowClientReceiverAll, requireRoomMembership, flushPressure}`, `offlineQueue.{maxSize, maxAgeMs, dropPolicy}`, `dev.{hotReloadDebounceMs, watcherStabilityThresholdMs, watcherPollIntervalMs, warnOnMissingInputType}`, `deploy.routing.{upstreamTimeoutMs, websocketService, routerHealthPath, maxRequestBodyBytes}` (via `registerDeployConfig`), `paths.*`, `defaultLanguage`, `defaultTheme`, `socketActivityBroadcaster`, `socketStatusIndicator`, `locationProviderEnabled`, `loginRedirectUrl`, `oauthCallbackBase`.
|
|
229
|
-
|
|
230
|
-
> **New 0.2.0 keys (all additive — a missing key keeps prior behavior EXCEPT `validation.runtimeMode`):** `validation.runtimeMode` (`'enforce'` default — prod input validation now ACTUALLY runs; set `'off'` to restore the old prod no-op). `rateLimiting.skipLoopbackInDev` (default `false`; skip the cross-route IP cap for loopback in dev), `rateLimiting.identity` (callback overriding the per-route bucket basis), `rateLimiting.auth` (`{ enabled false, maxAttempts 5, maxAttemptsPerAccount 50, windowMs 900000 }` — dual lockout counter: per-IP `maxAttempts` + cross-IP `maxAttemptsPerAccount`, ADR 0015`) — per-account login lockout slot). **BREAKING — `sync.allowClientReceiverAll` (default now `false`, was `true`) + `sync.requireRoomMembership` (default now `true`, was `false`): a client can no longer broadcast to `'all'` nor target an unjoined room by default — join the room, approve via `preSyncAuthorize`, or opt back into the permissive values.** `sync.flushPressure` (`{ highWaterMarkChunks 1000, lowWaterMarkChunks 250, maxBufferedBytes 5242880 }`). **BREAKING — `http.healthHash` (`{ mode: 'plain'|'salted'|'hmac', salt: string }`) now DEFAULTS to `{ mode: 'hmac', salt: '@bootUuid' }`: `/_health` no longer exposes a stable, unsalted `sha256(secret)` — the synchronized-env fingerprint is HMAC-keyed on the per-boot UUID (rotates each restart). When no boot UUID is available the `'@bootUuid'` sentinel collapses to `'plain'` so the boot handshake never silently diverges. Set a non-empty `salt` to pin a stable key, or `mode:'plain'` to restore legacy wire output.** `http.sessionCookieDomain`/`sessionCookiePrefix` (`'__Host-'`/`'__Secure-'`)/`sessionCookieSecure`. `http.trustedProxyHopCount` (default `1` — when `trustProxy` is on, the resolved client IP is now the entry that many hops in from the RIGHT of `X-Forwarded-For` (the rightmost hop is the immediate upstream proxy); the leftmost, client-controlled hop is never trusted — CORE-O3 leftmost-spoof fix; clamped to the list length). `http.acceptBearerInCookieMode` (default `false` — in cookie-mode (`session.basedToken:false`) the framework now reads ONLY the session cookie and IGNORES any `Authorization: Bearer` / `handshake.auth.token` fallback, closing the CORE-O10 CSRF-bypass; set `true` to restore the legacy cookie-then-bearer fallback. Token-mode is unaffected). `socket.activityHeartbeatThrottleMs` (default `10000`). `auth.allowRegistration` (default `true`), `auth.passwordResetPath` (default `'/reset-password'`), `auth.emailChangeConfirmPath` (default `'/confirm-email-change'`). `deploy.routing.{upstreamTimeoutMs, websocketService, routerHealthPath, maxRequestBodyBytes}` (all optional — undefined uses the router built-in default).
|
|
231
|
-
|
|
232
|
-
Other registries: `registerDeployConfig(DeployConfigShape)`, `registerServicesConfig(ServicesConfigShape)`, `registerAvatarConfig(AvatarConfigInput)`, `registerPortOverride(port)`, `registerBindAddress({ ip, port, configuredPort? })`.
|
|
233
|
-
|
|
234
|
-
## Peer dependencies
|
|
235
|
-
|
|
236
|
-
Required:
|
|
237
|
-
|
|
238
|
-
- `ioredis@^5.10.0` — Redis client proxied through `getRedisClient()`; backs the rate limiter, Redis-backed login/session defaults, boot-UUID, and offline-queue.
|
|
239
|
-
- `socket.io@^4.8.0` — server-side `SocketIOServer` consumed by `setIoInstance` and `attachSocketRedisAdapter`.
|
|
240
|
-
- `socket.io-client@^4.8.0` — client-side socket types used by the offline queue.
|
|
241
|
-
- `zod@^4.0.0` — `env.ts` schema parsing.
|
|
242
|
-
|
|
243
|
-
Optional (marked in `peerDependenciesMeta`):
|
|
244
|
-
|
|
245
|
-
- `@prisma/client@^6.19.0` — only for the Prisma path proxied through `getPrismaClient()`; a custom registered DB client or DB-less project does not need it.
|
|
246
|
-
- `react@^19.2.0`, `react-dom@^19.2.0`, `react-router-dom@^7.0.0` — only consumed by `@luckystack/core/client` and the `react/*` subpath helpers. Pure server boots can skip these.
|
|
247
|
-
- `sonner@^2.0.0` — only needed if you wire the default sonner-backed notifier from the project's React entry; the core notifier slot itself is library-agnostic.
|
|
248
|
-
- `eslint@^9.0.0` — only needed to consume the shared rules from the `./eslint` subpath; pure runtime usage doesn't require it.
|
|
249
|
-
|
|
250
|
-
Runtime (bundled): `@socket.io/redis-adapter`, `dotenv`.
|
|
251
|
-
|
|
252
|
-
## Related
|
|
253
|
-
|
|
254
|
-
- README: `packages/core/README.md`
|
|
255
|
-
- Architecture deep-dives: `docs/ARCHITECTURE_EXTENSION_POINTS.md`, `docs/ARCHITECTURE_PACKAGING.md`, `docs/ARCHITECTURE_SOCKET.md`, `docs/ARCHITECTURE_MULTI_INSTANCE.md`, `docs/ARCHITECTURE_API.md`, `docs/ARCHITECTURE_SYNC.md`, `docs/ARCHITECTURE_SESSION.md`, `docs/ARCHITECTURE_AUTH.md`, `docs/ARCHITECTURE_ROUTING.md`
|
|
256
|
-
- Project-side glue: `config.ts`, `server/server.ts`, `server/prod/runtimeMaps.ts`, `src/_sockets/socketInitializer.ts`
|
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import { Socket } from 'socket.io-client';
|
|
2
|
-
|
|
3
|
-
declare function sleep(ms: number): Promise<void>;
|
|
4
|
-
|
|
5
|
-
declare function tryCatchSync<T, P = void>(func: (values: P) => T, params?: P): [Error | null, T | null];
|
|
6
|
-
|
|
7
|
-
interface NotifyParam {
|
|
8
|
-
key: string;
|
|
9
|
-
value: string | number | boolean;
|
|
10
|
-
}
|
|
11
|
-
interface NotifyInput {
|
|
12
|
-
key: string;
|
|
13
|
-
params?: NotifyParam[];
|
|
14
|
-
}
|
|
15
|
-
interface Notifier {
|
|
16
|
-
success: (input: NotifyInput) => void;
|
|
17
|
-
error: (input: NotifyInput) => void;
|
|
18
|
-
info: (input: NotifyInput) => void;
|
|
19
|
-
warning: (input: NotifyInput) => void;
|
|
20
|
-
}
|
|
21
|
-
declare const registerNotifier: (notifier: Notifier) => void;
|
|
22
|
-
declare const getNotifier: () => Notifier;
|
|
23
|
-
declare const notify: Notifier;
|
|
24
|
-
|
|
25
|
-
declare let socket: Socket | null;
|
|
26
|
-
declare const setSocket: (next: Socket | null) => void;
|
|
27
|
-
declare const incrementResponseIndex: () => number;
|
|
28
|
-
declare const waitForSocket: () => Promise<Socket | null>;
|
|
29
|
-
|
|
30
|
-
/** Why a queued item was dropped (handed to its `onDrop` callback). */
|
|
31
|
-
type QueueDropReason = 'expired' | 'queue-full';
|
|
32
|
-
interface ApiQueueItem {
|
|
33
|
-
id: string;
|
|
34
|
-
key: string;
|
|
35
|
-
run: (socketInstance: Socket) => void;
|
|
36
|
-
createdAt: number;
|
|
37
|
-
/**
|
|
38
|
-
* Per-request override of `projectConfig.offlineQueue.dropPolicy`. See
|
|
39
|
-
* `SyncQueueItem.dropPolicy` for details.
|
|
40
|
-
*/
|
|
41
|
-
dropPolicy?: 'drop-oldest' | 'drop-newest' | 'reject';
|
|
42
|
-
/**
|
|
43
|
-
* Optional callback invoked when THIS item is dropped (evicted for age or
|
|
44
|
-
* because the queue was full / rejected) instead of ever running (SYNC-09).
|
|
45
|
-
* The transport wires this to settle the pending `syncRequest`/`apiRequest`
|
|
46
|
-
* promise with an error envelope, so an evicted offline request resolves
|
|
47
|
-
* rather than hanging forever. Invoked at most once; failures are swallowed.
|
|
48
|
-
*/
|
|
49
|
-
onDrop?: (reason: QueueDropReason) => void;
|
|
50
|
-
}
|
|
51
|
-
interface SyncQueueItem {
|
|
52
|
-
id: string;
|
|
53
|
-
key: string;
|
|
54
|
-
run: (socketInstance: Socket) => void;
|
|
55
|
-
createdAt: number;
|
|
56
|
-
/**
|
|
57
|
-
* Per-request override of `projectConfig.offlineQueue.dropPolicy`. Lets a
|
|
58
|
-
* specific sync ("editor cursor move") pick `'drop-oldest'` while the app
|
|
59
|
-
* default stays `'reject'` for safer sends. When omitted, falls back to
|
|
60
|
-
* the global config.
|
|
61
|
-
*/
|
|
62
|
-
dropPolicy?: 'drop-oldest' | 'drop-newest' | 'reject';
|
|
63
|
-
/** See {@link ApiQueueItem.onDrop} (SYNC-09). */
|
|
64
|
-
onDrop?: (reason: QueueDropReason) => void;
|
|
65
|
-
}
|
|
66
|
-
declare const isOnline: () => boolean;
|
|
67
|
-
declare const enqueueApiRequest: (item: ApiQueueItem) => boolean;
|
|
68
|
-
declare const enqueueSyncRequest: (item: SyncQueueItem) => boolean;
|
|
69
|
-
declare const removeApiQueueItem: (id: string) => void;
|
|
70
|
-
declare const removeSyncQueueItem: (id: string) => void;
|
|
71
|
-
declare const removeApiQueueItemsByKey: (key: string) => void;
|
|
72
|
-
declare const flushApiQueue: (canRun: () => boolean, socketInstance: Socket) => void;
|
|
73
|
-
declare const flushSyncQueue: (canRun: () => boolean, socketInstance: Socket) => void;
|
|
74
|
-
declare const getApiQueueSize: () => number;
|
|
75
|
-
declare const getSyncQueueSize: () => number;
|
|
76
|
-
|
|
77
|
-
interface ErrorParam {
|
|
78
|
-
key: string;
|
|
79
|
-
value: string | number | boolean;
|
|
80
|
-
}
|
|
81
|
-
interface ErrorResponseInput {
|
|
82
|
-
status?: unknown;
|
|
83
|
-
errorCode?: unknown;
|
|
84
|
-
errorParams?: unknown;
|
|
85
|
-
httpStatus?: unknown;
|
|
86
|
-
}
|
|
87
|
-
interface NormalizedErrorResponse {
|
|
88
|
-
status: 'error';
|
|
89
|
-
message: string;
|
|
90
|
-
errorCode: string;
|
|
91
|
-
errorParams?: ErrorParam[];
|
|
92
|
-
httpStatus?: number;
|
|
93
|
-
}
|
|
94
|
-
declare const INVALID_ERROR_RESPONSE_CODE = "error.invalidResponse";
|
|
95
|
-
declare const isErrorParamArray: (value: unknown) => value is ErrorParam[];
|
|
96
|
-
declare const normalizeErrorResponseCore: ({ response, fallbackHttpStatus, fallbackErrorCode, resolveMessage, }: {
|
|
97
|
-
response: ErrorResponseInput;
|
|
98
|
-
fallbackHttpStatus?: number;
|
|
99
|
-
fallbackErrorCode?: string;
|
|
100
|
-
resolveMessage?: (args: {
|
|
101
|
-
errorCode: string;
|
|
102
|
-
errorParams?: ErrorParam[];
|
|
103
|
-
}) => string;
|
|
104
|
-
}) => NormalizedErrorResponse;
|
|
105
|
-
declare const defaultHttpStatusForResponse: ({ status, explicitHttpStatus, fallbackErrorStatus, }: {
|
|
106
|
-
status: "success" | "error";
|
|
107
|
-
explicitHttpStatus?: number;
|
|
108
|
-
fallbackErrorStatus?: number;
|
|
109
|
-
}) => number;
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Fetch (or return the cached) CSRF token for the active session. Returns
|
|
113
|
-
* null in token mode or when no session exists. Callers should attach the
|
|
114
|
-
* returned value as the `X-CSRF-Token` request header.
|
|
115
|
-
*/
|
|
116
|
-
declare const getCsrfToken: (origin?: string) => Promise<string | null>;
|
|
117
|
-
/** Drop the cached token. Call this on logout or when a 403 csrfMismatch is seen. */
|
|
118
|
-
declare const clearCsrfToken: () => void;
|
|
119
|
-
/**
|
|
120
|
-
* Drop-in replacement for `fetch` that automatically attaches the framework's
|
|
121
|
-
* `X-CSRF-Token` and `X-Request-Id` headers to state-changing requests in
|
|
122
|
-
* cookie mode. Use this for any project-side HTTP call that hits a
|
|
123
|
-
* LuckyStack `/api/*`, `/sync/*`, or `/auth/api/*` endpoint.
|
|
124
|
-
*
|
|
125
|
-
* Behavior:
|
|
126
|
-
* - GET / OPTIONS / HEAD: passes through without CSRF (read-only).
|
|
127
|
-
* - Token mode: passes through without CSRF (immune by design).
|
|
128
|
-
* - Cookie mode + state-changing method: lazily fetches `/auth/csrf` once,
|
|
129
|
-
* caches, attaches as `x-csrf-token`. On a 403 `auth.csrfMismatch`
|
|
130
|
-
* response, clears the cache and retries once.
|
|
131
|
-
* - `credentials: 'include'` is forced unless the caller overrode it.
|
|
132
|
-
*
|
|
133
|
-
* @example
|
|
134
|
-
* import { httpFetch } from '@luckystack/core/client';
|
|
135
|
-
* const res = await httpFetch('/api/system/myRoute/v1', {
|
|
136
|
-
* method: 'POST',
|
|
137
|
-
* body: JSON.stringify({ ... }),
|
|
138
|
-
* });
|
|
139
|
-
*/
|
|
140
|
-
declare const httpFetch: typeof fetch;
|
|
141
|
-
|
|
142
|
-
type HttpMethodLiteral = 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
143
|
-
type ApiMethodMap = Record<string, Record<string, Record<string, HttpMethodLiteral>>>;
|
|
144
|
-
declare const registerApiMethodMap: (map: ApiMethodMap) => void;
|
|
145
|
-
declare const getRegisteredApiMethod: (pagePath: string, apiName: string, version: string) => HttpMethodLiteral | undefined;
|
|
146
|
-
declare const isApiMethodMapRegistered: () => boolean;
|
|
147
|
-
|
|
148
|
-
declare const ROUTED_DATA_QUERY_KEY = "__luckystack_data";
|
|
149
|
-
/**
|
|
150
|
-
* Append `data` to `path` as the reserved `__luckystack_data` query value
|
|
151
|
-
* (JSON-encoded), plus any `extraParams`. Tolerates a `path` that already
|
|
152
|
-
* carries a query string.
|
|
153
|
-
*/
|
|
154
|
-
declare const buildRoutedGetUrl: (path: string, data: unknown, extraParams?: Record<string, string>) => string;
|
|
155
|
-
|
|
156
|
-
interface ServiceRouteParseSuccess {
|
|
157
|
-
status: 'success';
|
|
158
|
-
normalizedRouteName: string;
|
|
159
|
-
service: string;
|
|
160
|
-
routeName: string;
|
|
161
|
-
}
|
|
162
|
-
interface ServiceRouteParseError {
|
|
163
|
-
status: 'error';
|
|
164
|
-
reason: string;
|
|
165
|
-
}
|
|
166
|
-
type ServiceRouteParseResult = ServiceRouteParseSuccess | ServiceRouteParseError;
|
|
167
|
-
interface TransportRouteParseSuccess {
|
|
168
|
-
status: 'success';
|
|
169
|
-
normalizedFullName: string;
|
|
170
|
-
version: string;
|
|
171
|
-
serviceRoute: ServiceRouteParseSuccess;
|
|
172
|
-
}
|
|
173
|
-
interface TransportRouteParseError {
|
|
174
|
-
status: 'error';
|
|
175
|
-
reason: string;
|
|
176
|
-
}
|
|
177
|
-
type TransportRouteParseResult = TransportRouteParseSuccess | TransportRouteParseError;
|
|
178
|
-
declare const parseServiceRouteName: (value: string) => ServiceRouteParseResult;
|
|
179
|
-
declare const parseTransportRouteName: ({ value, prefix, }: {
|
|
180
|
-
value: string;
|
|
181
|
-
prefix: "api" | "sync";
|
|
182
|
-
}) => TransportRouteParseResult;
|
|
183
|
-
|
|
184
|
-
declare const socketEventNames: {
|
|
185
|
-
readonly apiRequest: "apiRequest";
|
|
186
|
-
readonly apiCancel: "apiCancel";
|
|
187
|
-
readonly sync: "sync";
|
|
188
|
-
readonly syncCancel: "syncCancel";
|
|
189
|
-
readonly joinRoom: "joinRoom";
|
|
190
|
-
readonly leaveRoom: "leaveRoom";
|
|
191
|
-
readonly getJoinedRooms: "getJoinedRooms";
|
|
192
|
-
readonly updateLocation: "updateLocation";
|
|
193
|
-
readonly updateSession: "updateSession";
|
|
194
|
-
readonly sessionReplaced: "sessionReplaced";
|
|
195
|
-
readonly logout: "logout";
|
|
196
|
-
readonly intentionalDisconnect: "intentionalDisconnect";
|
|
197
|
-
readonly intentionalReconnect: "intentionalReconnect";
|
|
198
|
-
readonly activity: "activity";
|
|
199
|
-
readonly userAfk: "userAfk";
|
|
200
|
-
readonly userBack: "userBack";
|
|
201
|
-
readonly userLeft: "userLeft";
|
|
202
|
-
readonly connect: "connect";
|
|
203
|
-
readonly disconnect: "disconnect";
|
|
204
|
-
readonly reconnectAttempt: "reconnect_attempt";
|
|
205
|
-
readonly connectError: "connect_error";
|
|
206
|
-
readonly apiResponsePrefix: "apiResponse-";
|
|
207
|
-
readonly apiStreamPrefix: "apiStream-";
|
|
208
|
-
readonly syncResponsePrefix: "sync-";
|
|
209
|
-
readonly syncProgressPrefix: "sync-progress-";
|
|
210
|
-
readonly joinRoomResponsePrefix: "joinRoom-";
|
|
211
|
-
readonly leaveRoomResponsePrefix: "leaveRoom-";
|
|
212
|
-
readonly getJoinedRoomsResponsePrefix: "getJoinedRooms-";
|
|
213
|
-
};
|
|
214
|
-
declare const buildApiResponseEventName: (responseIndex: number | string) => string;
|
|
215
|
-
declare const buildApiStreamEventName: (responseIndex: number | string) => string;
|
|
216
|
-
declare const buildSyncResponseEventName: (responseIndex: number | string) => string;
|
|
217
|
-
declare const buildSyncProgressEventName: (responseIndex: number | string) => string;
|
|
218
|
-
declare const buildJoinRoomResponseEventName: (responseIndex: number | string) => string;
|
|
219
|
-
declare const buildLeaveRoomResponseEventName: (responseIndex: number | string) => string;
|
|
220
|
-
declare const buildGetJoinedRoomsResponseEventName: (responseIndex: number | string) => string;
|
|
221
|
-
|
|
222
|
-
type SOCKETSTATUS = "CONNECTED" | "DISCONNECTED" | "AFK" | "RECONNECTING" | "STARTUP";
|
|
223
|
-
interface statusContent {
|
|
224
|
-
status: SOCKETSTATUS;
|
|
225
|
-
reconnectAttempt?: number;
|
|
226
|
-
endTime?: number;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
type LoggerContext = Record<string, unknown>;
|
|
230
|
-
interface Logger {
|
|
231
|
-
debug: (message: string, context?: LoggerContext) => void;
|
|
232
|
-
info: (message: string, context?: LoggerContext) => void;
|
|
233
|
-
warn: (message: string, context?: LoggerContext) => void;
|
|
234
|
-
error: (message: string, error?: unknown, context?: LoggerContext) => void;
|
|
235
|
-
}
|
|
236
|
-
declare const createDevLogger: () => Logger;
|
|
237
|
-
declare const registerLogger: (logger: Logger) => void;
|
|
238
|
-
declare const getLogger: () => Logger;
|
|
239
|
-
declare const isLoggerRegistered: () => boolean;
|
|
240
|
-
declare const resetLoggerForTests: () => void;
|
|
241
|
-
|
|
242
|
-
interface PageRouteRules {
|
|
243
|
-
/**
|
|
244
|
-
* Single-character prefix that marks a folder as **invisible-parent** for
|
|
245
|
-
* page routing. Default: `'_'`.
|
|
246
|
-
*/
|
|
247
|
-
privateFolderPrefix: string;
|
|
248
|
-
/**
|
|
249
|
-
* Folder names that are reserved for framework-internal use and may NEVER
|
|
250
|
-
* host a `page.tsx`, nested or otherwise. Scaffold + page-discovery emit
|
|
251
|
-
* a hard error when a page is placed inside one.
|
|
252
|
-
*/
|
|
253
|
-
scaffoldIgnoredFolders: readonly string[];
|
|
254
|
-
}
|
|
255
|
-
declare const DEFAULT_PAGE_ROUTE_RULES: PageRouteRules;
|
|
256
|
-
interface PagePathValidationResult {
|
|
257
|
-
/** `true` when the page file produces a routeable URL. */
|
|
258
|
-
valid: boolean;
|
|
259
|
-
/** Computed URL when `valid`. Always begins with `/`. `/` for root page. */
|
|
260
|
-
route?: string;
|
|
261
|
-
/** Human-readable explanation when `!valid`. */
|
|
262
|
-
reason?: string;
|
|
263
|
-
}
|
|
264
|
-
/**
|
|
265
|
-
* Validate a `page.tsx` file path relative to `src/`. Accepts both
|
|
266
|
-
* forward and back slashes, with or without a leading `./` or `/`.
|
|
267
|
-
* The filename **must** be `page.tsx` (or `page.jsx`); other filenames
|
|
268
|
-
* return `valid: false`.
|
|
269
|
-
*
|
|
270
|
-
* Examples (defaults):
|
|
271
|
-
* admin/page.tsx -> { valid: true, route: '/admin' }
|
|
272
|
-
* _housing/renting/page.tsx -> { valid: true, route: '/renting' }
|
|
273
|
-
* _housing/page.tsx -> { valid: false, reason: 'no URL segment left after stripping underscore folders' }
|
|
274
|
-
* _api/something/page.tsx -> { valid: false, reason: 'page.tsx cannot live inside reserved folder _api' }
|
|
275
|
-
* page.tsx -> { valid: true, route: '/' }
|
|
276
|
-
* some/route.tsx -> { valid: false, reason: 'not a page file (expected page.tsx)' }
|
|
277
|
-
*/
|
|
278
|
-
declare const validatePagePath: (srcRelativePath: string, rules?: PageRouteRules) => PagePathValidationResult;
|
|
279
|
-
|
|
280
|
-
export { type ErrorResponseInput as $, type ApiMethodMap as A, isOnline as B, normalizeErrorResponseCore as C, DEFAULT_PAGE_ROUTE_RULES as D, notify as E, parseServiceRouteName as F, registerApiMethodMap as G, type HttpMethodLiteral as H, registerLogger as I, registerNotifier as J, removeApiQueueItem as K, type Logger as L, removeApiQueueItemsByKey as M, type Notifier as N, removeSyncQueueItem as O, type PagePathValidationResult as P, setSocket as Q, ROUTED_DATA_QUERY_KEY as R, type SOCKETSTATUS as S, sleep as T, socket as U, socketEventNames as V, type statusContent as W, tryCatchSync as X, validatePagePath as Y, waitForSocket as Z, type ErrorParam as _, type LoggerContext as a, type NormalizedErrorResponse as a0, INVALID_ERROR_RESPONSE_CODE as a1, type QueueDropReason as a2, type ServiceRouteParseError as a3, type ServiceRouteParseResult as a4, type ServiceRouteParseSuccess as a5, type TransportRouteParseError as a6, type TransportRouteParseResult as a7, type TransportRouteParseSuccess as a8, createDevLogger as a9, defaultHttpStatusForResponse as aa, isErrorParamArray as ab, isLoggerRegistered as ac, parseTransportRouteName as ad, resetLoggerForTests as ae, type NotifyInput as b, type NotifyParam as c, type PageRouteRules as d, buildApiResponseEventName as e, buildApiStreamEventName as f, buildGetJoinedRoomsResponseEventName as g, buildJoinRoomResponseEventName as h, buildLeaveRoomResponseEventName as i, buildRoutedGetUrl as j, buildSyncProgressEventName as k, buildSyncResponseEventName as l, clearCsrfToken as m, enqueueApiRequest as n, enqueueSyncRequest as o, flushApiQueue as p, flushSyncQueue as q, getApiQueueSize as r, getCsrfToken as s, getLogger as t, getNotifier as u, getRegisteredApiMethod as v, getSyncQueueSize as w, httpFetch as x, incrementResponseIndex as y, isApiMethodMapRegistered as z };
|