@luckystack/core 0.10.2 → 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.
Files changed (122) hide show
  1. package/AGENTS.md +11 -0
  2. package/CHANGELOG.md +7 -0
  3. package/README.md +2 -88
  4. package/dist/apiInterceptors.d.ts +36 -0
  5. package/dist/apiMethodMapRegistry.d.ts +5 -0
  6. package/dist/apiRequest.d.ts +71 -0
  7. package/dist/apiTypeStubs.d.ts +3 -5
  8. package/dist/avatarConfig.d.ts +22 -0
  9. package/dist/bindAddress.d.ts +15 -0
  10. package/dist/bootUuid.d.ts +24 -0
  11. package/dist/cancelRegistry.d.ts +7 -0
  12. package/dist/checkOrigin.d.ts +6 -0
  13. package/dist/client.d.ts +46 -335
  14. package/dist/client.js +2 -2
  15. package/dist/client.js.map +1 -1
  16. package/dist/clientHookBus.d.ts +62 -0
  17. package/dist/clients.d.ts +16 -0
  18. package/dist/config.d.ts +8 -2
  19. package/dist/configUtils.d.ts +16 -0
  20. package/dist/consoleLog.d.ts +1 -0
  21. package/dist/cookies.d.ts +28 -0
  22. package/dist/createRegistry.d.ts +39 -0
  23. package/dist/csrf.d.ts +30 -0
  24. package/dist/csrfConfig.d.ts +32 -0
  25. package/dist/db.d.ts +12 -0
  26. package/dist/dbHealthCheck.d.ts +6 -0
  27. package/dist/{config-CwW3KY9r.d.ts → deployConfigRegistry.d.ts} +8 -43
  28. package/dist/emailRegistry.d.ts +139 -0
  29. package/dist/env.d.ts +38 -0
  30. package/dist/errorFormatterRegistry.d.ts +54 -0
  31. package/dist/errorTrackerIdentity.d.ts +14 -0
  32. package/dist/errorTrackerRegistry.d.ts +85 -0
  33. package/dist/escapeHtml.d.ts +6 -0
  34. package/dist/eslint/index.d.ts +5 -8
  35. package/dist/eslint/internal/hasPackage.d.ts +1 -0
  36. package/dist/eslint/internal/ruleTypes.d.ts +4 -0
  37. package/dist/eslint/rules/no-arbitrary-tailwind-color.d.ts +3 -0
  38. package/dist/eslint/rules/no-direct-prisma-import-in-components.d.ts +3 -0
  39. package/dist/eslint/rules/no-local-socket-enumeration.d.ts +3 -0
  40. package/dist/eslint/rules/no-raw-fetch-in-src.d.ts +3 -0
  41. package/dist/eslint/rules/no-raw-try-catch.d.ts +3 -0
  42. package/dist/eslint/rules/no-unsafe-api-wrappers.d.ts +3 -0
  43. package/dist/eslint/rules/no-unsafe-sync-wrappers.d.ts +3 -0
  44. package/dist/eslint/rules/prefer-luckystack-confirm.d.ts +3 -0
  45. package/dist/eslint/rules/prefer-luckystack-dropdown.d.ts +3 -0
  46. package/dist/eslint/rules/prefer-luckystack-notify.d.ts +3 -0
  47. package/dist/extractToken.d.ts +12 -0
  48. package/dist/extractTokenFromRequest.d.ts +12 -0
  49. package/dist/getParams.d.ts +9 -0
  50. package/dist/hooks/registry.d.ts +12 -0
  51. package/dist/hooks/types.d.ts +446 -0
  52. package/dist/httpApiUtils.d.ts +54 -0
  53. package/dist/index.d.ts +110 -1892
  54. package/dist/index.js.map +1 -1
  55. package/dist/lease.d.ts +18 -0
  56. package/dist/localSocketEnumerationGuard.d.ts +22 -0
  57. package/dist/localeReloader.d.ts +3 -0
  58. package/dist/localesRegistry.d.ts +8 -0
  59. package/dist/localizedNormalizer.d.ts +50 -0
  60. package/dist/loggerRegistry.d.ts +12 -0
  61. package/dist/middlewareRegistry.d.ts +45 -0
  62. package/dist/notifier.d.ts +17 -0
  63. package/dist/offlineQueue.d.ts +48 -0
  64. package/dist/oneTimeToken.d.ts +43 -0
  65. package/dist/pageRouteValidation.d.ts +37 -0
  66. package/dist/paths.d.ts +35 -0
  67. package/dist/peerDeps.d.ts +23 -0
  68. package/dist/portOverrideRegistry.d.ts +3 -0
  69. package/dist/processUpload.d.ts +44 -0
  70. package/dist/{sessionTypes-DanjuPaE.d.ts → projectConfig.d.ts} +32 -129
  71. package/dist/rateLimiter.d.ts +90 -0
  72. package/dist/react/AvatarProvider.d.ts +11 -0
  73. package/dist/react/Middleware.d.ts +10 -0
  74. package/dist/react/Router.d.ts +1 -0
  75. package/dist/react/TranslationProvider.d.ts +22 -0
  76. package/dist/react/notify.d.ts +12 -0
  77. package/dist/react/sessionContext.d.ts +31 -0
  78. package/dist/react/useTheme.d.ts +5 -0
  79. package/dist/redactedLogKeys.d.ts +22 -0
  80. package/dist/redis.d.ts +15 -0
  81. package/dist/redisKeyFormatter.d.ts +7 -0
  82. package/dist/resolveClientIp.d.ts +51 -0
  83. package/dist/responseNormalizer.d.ts +33 -0
  84. package/dist/roomNameFormatterRegistry.d.ts +36 -0
  85. package/dist/roomSockets.d.ts +23 -0
  86. package/dist/routedDataQuery.d.ts +7 -0
  87. package/dist/routedHttpInvocation.d.ts +20 -0
  88. package/dist/runtimeMapsRegistry.d.ts +18 -0
  89. package/dist/runtimeTypeValidation.d.ts +13 -0
  90. package/dist/secretsResolved.d.ts +3 -0
  91. package/dist/sentrySetup.d.ts +21 -0
  92. package/dist/serveAvatars.d.ts +5 -0
  93. package/dist/serviceRoute.d.ts +27 -0
  94. package/dist/servicesConfigRegistry.d.ts +22 -0
  95. package/dist/sessionProviderRegistry.d.ts +46 -0
  96. package/dist/sessionTypes.d.ts +79 -0
  97. package/dist/sleep.d.ts +1 -0
  98. package/dist/socketEvents.d.ts +37 -0
  99. package/dist/socketMiddlewareRegistry.d.ts +20 -0
  100. package/dist/socketRedisAdapter.d.ts +21 -0
  101. package/dist/socketState.d.ts +5 -0
  102. package/dist/socketStatusTypes.d.ts +6 -0
  103. package/dist/socketTypes.d.ts +34 -0
  104. package/dist/synchronizedEnvHashes.d.ts +66 -0
  105. package/dist/testFileConvention.d.ts +11 -0
  106. package/dist/testResetTargets.d.ts +5 -0
  107. package/dist/tryCatch.d.ts +1 -0
  108. package/dist/tryCatchClient.d.ts +1 -0
  109. package/dist/tryCatchSync.d.ts +1 -0
  110. package/dist/validateRequest.d.ts +50 -0
  111. package/docs/app-bootstrap.md +4 -449
  112. package/docs/config-registry.md +3 -346
  113. package/docs/csrf-config.md +3 -86
  114. package/docs/error-tracker-registry.md +3 -192
  115. package/docs/hooks.md +2 -295
  116. package/docs/rate-limit-strategy.md +3 -168
  117. package/docs/redis-adapter.md +5 -110
  118. package/docs/session-types.md +3 -178
  119. package/docs/socket-bootstrap.md +5 -385
  120. package/package.json +2 -2
  121. package/CLAUDE.md +0 -256
  122. package/dist/pageRouteValidation-B4Cmtfjv.d.ts +0 -280
package/AGENTS.md ADDED
@@ -0,0 +1,11 @@
1
+ # @luckystack/core — design context
2
+
3
+ Core owns the shared transport contracts and registration seams used by framework packages. Register infrastructure at boot and read it at call time; do not copy config defaults into consumers or bypass registries. Source: `src/projectConfig.ts`, `src/createRegistry.ts`, `src/index.ts`, `src/client.ts`.
4
+
5
+ Keep the server barrel out of browser bundles; use `@luckystack/core/client`. Extend session types in the consumer rather than editing shared base types. Do not let observability adapters break request handling; redact sensitive fields before emitting context. Source: `src/errorTrackerRegistry.ts`, `src/redactedLogKeys.ts`.
6
+
7
+ Security boundaries: cookie sessions use CSRF protection; token-mode bearer credentials are supplied explicitly. Do not enable bearer fallback in cookie mode without reviewing CSRF implications (`src/extractToken.ts`). Trust forwarded IP headers only when the reverse proxy and hop count are configured (`src/resolveClientIp.ts`); rate limits depend on this identity. Never relax origin checks for convenience (`src/checkOrigin.ts`). Redis keys and socket room names must remain isolated across tenants (`src/redisKeyFormatter.ts`, `src/roomNameFormatterRegistry.ts`).
8
+
9
+ Multi-instance: the per-instance socket maps must stay unreachable from consumer code by default — use the cross-instance room helper, and take the raw server only for deliberate per-instance work with a lint opt-out that says why (ADR 0061, `src/roomSockets.ts`). A room-name formatter's `userId` is context, never part of a content room's physical name (ADR 0060). The Redis-adapter key, not the Redis server, defines the socket cluster; it separates environments but not their data (`src/socketRedisAdapter.ts`).
10
+
11
+ Design discussions: `docs/` in this package and repository `docs/ARCHITECTURE_AUTH.md`, `docs/ARCHITECTURE_MULTI_INSTANCE.md`. Signatures and defaults live in source, not this file.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,13 @@ All notable changes to `@luckystack/core` are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.11.0] - 2026-10-14
9
+
10
+ ### Changed
11
+
12
+ - Type declarations are emitted per source file by TypeScript 7 (`tsc -b`) instead of one bundled `.d.ts` per entry, so the framework build no longer runs package by package. The exported types are unchanged; relative imports inside the declarations carry `.js`, so `moduleResolution: "node16"`/`"nodenext"` projects resolve them as before. The `prisma` export carries an explicit `PrismaClient` type, so its declaration references `@prisma/client` instead of Prisma's generated `.prisma/client` folder.
13
+ - Package design guidance now ships as `AGENTS.md`, not `CLAUDE.md`. Handwritten docs focus on rationale and safety; exact API contracts remain in installed declarations/source.
14
+
8
15
  ## [0.10.2] - 2026-10-09
9
16
 
10
17
  ### Added
package/README.md CHANGED
@@ -1,91 +1,5 @@
1
1
  # @luckystack/core
2
2
 
3
- > Foundation package for [LuckyStack](https://github.com/ItsLucky23/LuckyStack-v2). Socket-first transport contracts, Redis adapter, project-config registry, hooks registry, response normalization, rate limiting, and shared session types.
3
+ Shared foundation for LuckyStack transport, configuration, session types and framework extension points. Install with `npm install @luckystack/core ioredis socket.io socket.io-client zod`. Add the optional `@prisma/client` only when using the default Prisma integration (see `package.json`). Import browser code from `@luckystack/core/client`, never the server barrel.
4
4
 
5
- Every other `@luckystack/*` package depends on this one.
6
-
7
- ## Install
8
-
9
- ```bash
10
- npm install @luckystack/core
11
- ```
12
-
13
- Required peer dependencies you must install separately:
14
-
15
- ```bash
16
- npm install ioredis socket.io socket.io-client zod
17
- ```
18
-
19
- `@prisma/client` is an optional peer. Install it only when the project uses the default Prisma client; a registered custom database client or a DB-less project does not need it.
20
-
21
- ## Quickstart
22
-
23
- The core package is mostly consumed transitively through `@luckystack/server`, `@luckystack/api`, etc. You typically only touch it directly to register your project config and to call low-level utilities.
24
-
25
- ```ts
26
- import { registerProjectConfig, getProjectConfig, notify, redis, prisma } from '@luckystack/core';
27
-
28
- registerProjectConfig({
29
- defaultLanguage: 'en',
30
- session: { expiryDays: 30, basedToken: false, perUser: 'multiple' },
31
- auth: {
32
- emailMaxLength: 254,
33
- passwordMinLength: 8,
34
- passwordMaxLength: 128,
35
- nameMaxLength: 64,
36
- oauthStateTtlSeconds: 600,
37
- },
38
- // ...see ProjectConfig type for the full surface
39
- });
40
-
41
- const config = getProjectConfig();
42
- ```
43
-
44
- ## Subpath: `@luckystack/core/client`
45
-
46
- Browser-safe surface for the React/Vite bundle. Re-exports the pieces consumed by `@luckystack/sync`'s client transport (`projectConfig`, `notifier`, `socketState`, `offlineQueue`, `responseNormalizer`, `serviceRoute`, `socketEvents`, `socketStatusTypes`, `apiTypeStubs`, `sessionTypes`).
47
-
48
- ```ts
49
- import { socket, isOnline, enqueueApiRequest } from '@luckystack/core/client';
50
- ```
51
-
52
- ## Public API (server entry)
53
-
54
- | Group | Exports |
55
- | --- | --- |
56
- | Project config | `registerProjectConfig`, `getProjectConfig`, `getProjectName`, `isProjectConfigRegistered`, `DEFAULT_PROJECT_CONFIG`. Types: `ProjectConfig`, `ProjectConfigInput`, `LoggingConfig`, `RateLimitingConfig`, `SessionConfig`, `SentryConfig`, `SentrySampleRates`, `HttpConfig`, `HttpStreamConfig`, `SecurityHeadersConfig`, `CorsConfig`, `AuthConfig`, `SocketConfig`, `SyncConfig`, `SyncStreamThrottleConfig`, `OfflineQueueConfig`, `DevConfig`, `PathsConfig`, `EmailConfig`, `EmailLoggingConfig`, `EmailEnvVarsConfig`, `EmailDefaultsConfig`. **`getProjectName()`** is the canonical way to read the project's Redis-prefix string — it resolves at call time in this order: (1) `projectConfig.session.projectName` if a consumer set it, (2) `process.env.PROJECT_NAME` (read after `dotenv` has loaded), (3) literal `'luckystack'` as the absolute fallback. Reach for it from any framework or project code that needs the prefix instead of duplicating the env-read pattern. |
57
- | Deploy + services config | `registerDeployConfig`, `getDeployConfig`, `isDeployConfigRegistered`, `registerServicesConfig`, `getServicesConfig`, `isServicesConfigRegistered`. Types: `DeployConfigShape`, `DeployResourceShape`, `DeployEnvironmentShape`, `DeployRoutingShape`, `DeployDevelopmentShape`, `ServicesConfigShape`, `ServiceDefinition`, `PresetDefinition` |
58
- | Runtime maps | `registerRuntimeMapsProvider`, `getRuntimeApiMaps`, `getRuntimeSyncMaps`. Types: `RuntimeMapsProvider`, `RuntimeApiMapsResult`, `RuntimeSyncMapsResult` |
59
- | DI clients | `registerPrismaClient`, `registerRedisClient`, `getPrismaClient`, `getRedisClient`, `isPrismaClientRegistered`, `isRedisClientRegistered` |
60
- | Notifier | `registerNotifier`, `getNotifier`, `notify`. Types: `Notifier`, `NotifyInput`, `NotifyParam` |
61
- | Email registry | `registerEmailSender`, `getEmailSender`, `isEmailSenderRegistered`. Types: `EmailSender`, `EmailMessage`, `EmailResult` (re-exported from `@luckystack/email` for convenience) |
62
- | Logger | `registerLogger`, `getLogger`, `isLoggerRegistered`, `resetLoggerForTests`, `createDevLogger`. Types: `Logger`, `LoggerContext`. Default logger is a thin wrapper over `console.{debug,info,warn,error}` with no color codes — safe in production. Call `registerLogger(createDevLogger())` from a dev-only entry to opt into the colored terminal output (which still depends on `initConsolelog()` to render the trailing color string). Plug in Pino / Winston / Datadog by registering an object that satisfies `Logger`. |
63
- | Redacted log keys | `registerRedactedLogKeys`, `getRedactedLogKeys`, `isRedactedLogKey`, `resetRedactedLogKeysForTests` |
64
- | Avatar / uploads | `registerAvatarConfig`, `getAvatarConfig`, `DEFAULT_AVATAR_CONFIG`, `serveAvatar`, `processUpload`. Types: `AvatarConfig`, `AvatarConfigInput`, `ProcessUploadInput`, `ProcessUploadResult`. **Path-traversal guard:** `serveAvatar` rejects any `fileId` that does not match `/^[A-Za-z0-9_-]{1,128}$/` with a 400 before touching the filesystem — `..`, slashes, and other path-separator characters cannot escape the configured upload directory. **`processUpload`** wraps the project-supplied encode/save callback with the framework's `onUploadStart` (stoppable) and `onUploadComplete` hook contract so consumer upload routes don't need to plumb the hooks themselves. Returns `{ status: 'success', sizeBytes }`, `{ status: 'rejected', errorCode }` (when `onUploadStart` returned a stop signal), or `{ status: 'error', reason, cause? }`. See [`src/settings/_api/updateUser_v1.ts`](../../src/settings/_api/updateUser_v1.ts) for the canonical avatar usage. |
65
- | Bind address | `registerBindAddress({ ip, port })` stores the intended pre-listen address; `registerBoundAddress({ ip, port })` stores `node:http`'s successful address; `getBindAddress() → { ip, port: string }` returns the actually-bound value. `resolveDevCallbackUrl(url)` rewrites only a stale direct loopback OAuth callback from intended → bound port, preserving explicit local router/reverse-proxy ingress. Env values remain legacy fallbacks. |
66
- | Locale reloader | `registerLocaleReloader`, `getLocaleReloader`. Type: `LocaleReloader` |
67
- | Hooks (async) | `registerHook`, `dispatchHook`, `clearAllHooks`. Types: `HookName`, `HookHandler`, `HookResult`, `HookStopSignal`, `HookPayloads`, `DispatchResult`, `HookSessionShape`. Canonical payload types: `PreApiValidatePayload`, `PostApiValidatePayload`, `PreApiExecutePayload`, `PostApiExecutePayload`, `PreApiRespondPayload`, `PostApiRespondPayload`, `ApiResponseEnvelope`, `PreSyncFanoutPayload`, `PostSyncFanoutPayload`, `ApiErrorPayload`, `SyncErrorPayload`, `RateLimitExceededPayload`, `CorsRejectedPayload`, `CsrfMismatchPayload`, `PreSessionRefreshPayload`, `PostSessionRefreshPayload`, `OnUploadStartPayload`, `OnUploadCompletePayload`. Feature-package payloads (`Pre/PostLogin`, `Pre/PostRegister`, `Pre/PostLogout`, `Pre/PostSessionCreate`, `Pre/PostSessionDelete`, `Pre/PostEmailSend`, `Pre/PostPresenceUpdate`, socket lifecycle, etc.) augment `HookPayloads` from their owning packages — see those packages' READMEs. |
68
- | Hooks (sync, mutators) | `registerSyncHook`, `dispatchSyncHook`. Types: `SyncHookName`, `SyncHookHandler`, `SyncHookPayloads`, `PreErrorNormalizePayload`, `PostErrorNormalizePayload`. Sync hook handlers must be synchronous (no `async`), mutate the payload object in place, and cannot stop the flow. Use them only for hot-path mutators like error-code remapping. |
69
- | Session types | `BaseSessionLayout`, `SessionLocation`, `AuthProps` |
70
- | Socket | `setIoInstance`, `getIoInstance`, `attachSocketRedisAdapter`, `socket`, `setSocket`, `incrementResponseIndex`, `waitForSocket`. Types: `SOCKETSTATUS`, `statusContent`, `apiMessage`, `syncMessage` |
71
- | Auth helpers | `extractTokenFromSocket`, `extractTokenFromRequest`, `validateRequest`, `isFalsy`, `allowedOrigin`. Types: `ValidationResult` |
72
- | Rate limiting | `checkRateLimit`, `getRateLimitStatus`, `clearRateLimit`, `clearAllRateLimits` |
73
- | Validation | `validateInputByType` (resolves to `@luckystack/devkit` lazily in dev) |
74
- | Persistence + env | `redis`, `prisma`, `db` exports, `env` exports |
75
- | Boot | `writeBootUuid`, `readBootUuid`, `resolveEnvKey`, `collectSynchronizedEnvKeys`, `computeSynchronizedEnvHashes`, `hashSynchronizedValue`, `initConsolelog` |
76
- | Offline queue | `isOnline`, `enqueueApiRequest`, `enqueueSyncRequest`, `removeApiQueueItem`, `removeSyncQueueItem`, `removeApiQueueItemsByKey`, `flushApiQueue`, `flushSyncQueue`, `getApiQueueSize`, `getSyncQueueSize` |
77
- | CSRF | `getCsrfToken`, `clearCsrfToken`, `httpFetch` |
78
- | Cookies + paths | All exports of `./cookies`, `./httpApiUtils`, and `./paths` (`getGeneratedApiDocsPath`, `getApiMethodMapPath`, etc.) |
79
- | Misc | `tryCatch`, `sleep`, `getParams`, response normalizers (`normalizeErrorResponse`, `extractLanguageFromHeader`, …), service-route helpers, socket-event constants |
80
-
81
- > Note: `apiRequest` is exported from `@luckystack/core/client`, not from the server entry. The server barrel does not pull React-coupled code.
82
-
83
- ## Dependencies
84
-
85
- - Runtime: `@socket.io/redis-adapter`, `dotenv`
86
- - Required peers: `ioredis@^5.10.0`, `socket.io@^4.8.0`, `socket.io-client@^4.8.0`, `zod@^4.0.0`
87
- - Optional peer: `@prisma/client@^6.19.0` — only needed when the default Prisma client is used.
88
-
89
- ## License
90
-
91
- MIT — see [LICENSE](../../LICENSE).
5
+ Register project infrastructure before starting the server; let `@luckystack/server` handle ordinary bootstrapping. Keep cookie and bearer session modes distinct: cookie-backed writes require origin and CSRF protection. See [design context](./AGENTS.md), [configuration rationale](./docs/config-registry.md), and [security rationale](./docs/socket-bootstrap.md). Types, exports and defaults are in `src/` and `package.json`.
@@ -0,0 +1,36 @@
1
+ export interface ApiRequestInterceptorContext {
2
+ /** Resolved route name (`<page>/<api>` or `system/<api>`). */
3
+ name: string;
4
+ /** Route version literal (e.g. `'v1'`). */
5
+ version: string;
6
+ /**
7
+ * Outgoing request payload. Mutate this object in place to augment the
8
+ * request (correlation id, feature-flag context, ...). Replacing the
9
+ * reference has no effect — mutate keys on the existing object.
10
+ */
11
+ data: Record<string, unknown>;
12
+ }
13
+ export interface ApiResponseInterceptorContext {
14
+ name: string;
15
+ version: string;
16
+ /** The response envelope the caller is about to receive (read-only intent). */
17
+ response: {
18
+ status: 'success' | 'error';
19
+ [key: string]: unknown;
20
+ };
21
+ }
22
+ export type ApiRequestInterceptor = (ctx: ApiRequestInterceptorContext) => void | Promise<void>;
23
+ export type ApiResponseInterceptor = (ctx: ApiResponseInterceptorContext) => void | Promise<void>;
24
+ /**
25
+ * Register an outgoing-request interceptor. Returns an unsubscribe function.
26
+ * Multiple interceptors run in registration order; each may mutate `ctx.data`.
27
+ */
28
+ export declare function registerApiRequestInterceptor(interceptor: ApiRequestInterceptor): () => void;
29
+ /**
30
+ * Register an incoming-response interceptor. Returns an unsubscribe function.
31
+ */
32
+ export declare function registerApiResponseInterceptor(interceptor: ApiResponseInterceptor): () => void;
33
+ export declare function dispatchApiRequestInterceptors(ctx: ApiRequestInterceptorContext): Promise<void>;
34
+ export declare function dispatchApiResponseInterceptors(ctx: ApiResponseInterceptorContext): void;
35
+ /** Test helper — drop every registered interceptor. Not part of the public API. */
36
+ export declare function _resetApiInterceptorsForTests(): void;
@@ -0,0 +1,5 @@
1
+ export type HttpMethodLiteral = 'GET' | 'POST' | 'PUT' | 'DELETE';
2
+ export type ApiMethodMap = Record<string, Record<string, Record<string, HttpMethodLiteral>>>;
3
+ export declare const registerApiMethodMap: (map: ApiMethodMap) => void;
4
+ export declare const getRegisteredApiMethod: (pagePath: string, apiName: string, version: string) => HttpMethodLiteral | undefined;
5
+ export declare const isApiMethodMapRegistered: () => boolean;
@@ -0,0 +1,71 @@
1
+ import type { ApiTypeMap, StreamPayload } from './apiTypeStubs.js';
2
+ export type ApiStreamEvent<T extends StreamPayload = StreamPayload> = T;
3
+ export declare const resolveApiRequestHttpMethod: (apiName: string, version: string) => "DELETE" | "GET" | "POST" | "PUT";
4
+ type DataRequired<T> = Record<string, never> extends T ? false : true;
5
+ type UnionToIntersection<U> = (U extends unknown ? (arg: U) => void : never) extends ((arg: infer I) => void) ? I : never;
6
+ type ApiRouteRecord = UnionToIntersection<{
7
+ [P in keyof ApiTypeMap]: {
8
+ [N in keyof ApiTypeMap[P] as P extends 'root' ? `system/${Extract<N, string>}` : `${Extract<P, string>}/${Extract<N, string>}`]: ApiTypeMap[P][N];
9
+ };
10
+ }[keyof ApiTypeMap]>;
11
+ type ApiFullName = Extract<keyof ApiRouteRecord, string>;
12
+ type VersionsForFullName<F extends ApiFullName> = keyof ApiRouteRecord[F] & string;
13
+ type Prettify<T> = {
14
+ [K in keyof T]: T[K];
15
+ } & {};
16
+ type InputForFullName<F extends ApiFullName, V extends VersionsForFullName<F>> = ApiRouteRecord[F][V] extends {
17
+ input: infer I;
18
+ } ? I : never;
19
+ type OutputForFullName<F extends ApiFullName, V extends VersionsForFullName<F>> = ApiRouteRecord[F][V] extends {
20
+ output: infer O;
21
+ } ? O : never;
22
+ type StreamForFullName<F extends ApiFullName, V extends VersionsForFullName<F>> = ApiRouteRecord[F][V] extends {
23
+ stream: infer S;
24
+ } ? S : never;
25
+ type ApiStreamCallbackForFullName<F extends ApiFullName, V extends VersionsForFullName<F>> = [
26
+ StreamForFullName<F, V>
27
+ ] extends [never] ? never : (event: ApiStreamEvent<Prettify<StreamForFullName<F, V> extends StreamPayload ? StreamForFullName<F, V> : StreamPayload>>) => void;
28
+ type ApiParamsForFullName<F extends ApiFullName, V extends VersionsForFullName<F>> = DataRequired<InputForFullName<F, V>> extends true ? {
29
+ name: F;
30
+ version: V;
31
+ data: Prettify<InputForFullName<F, V>>;
32
+ abortable?: boolean;
33
+ disableErrorMessage?: boolean;
34
+ onStream?: ApiStreamCallbackForFullName<F, V>;
35
+ signal?: AbortSignal;
36
+ timeoutMs?: number | false;
37
+ } : {
38
+ name: F;
39
+ version: V;
40
+ data?: Prettify<InputForFullName<F, V>>;
41
+ abortable?: boolean;
42
+ disableErrorMessage?: boolean;
43
+ onStream?: ApiStreamCallbackForFullName<F, V>;
44
+ signal?: AbortSignal;
45
+ timeoutMs?: number | false;
46
+ };
47
+ export interface ApiErrorResponse extends Record<string, unknown> {
48
+ status: 'error';
49
+ httpStatus: number;
50
+ message: string;
51
+ errorCode: string;
52
+ errorParams?: {
53
+ key: string;
54
+ value: string | number | boolean;
55
+ }[];
56
+ }
57
+ /**
58
+ * Type-safe API request function.
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * // Full name usage - includes page in the name
63
+ * const result = await apiRequest({ name: 'examples/publicApi', version: 'v1', data: { message: 'hello' } });
64
+ * // result is typed correctly for publicApi
65
+ *
66
+ * // Global APIs use service-first naming
67
+ * await apiRequest({ name: 'system/session', version: 'v1' });
68
+ * ```
69
+ */
70
+ export declare function apiRequest<F extends ApiFullName, V extends VersionsForFullName<F>>(params: ApiParamsForFullName<F, V>): Promise<Prettify<OutputForFullName<F, V>> | ApiErrorResponse>;
71
+ export {};
@@ -1,7 +1,5 @@
1
- interface ApiTypeMap {
1
+ export interface ApiTypeMap {
2
2
  }
3
- interface SyncTypeMap {
3
+ export interface SyncTypeMap {
4
4
  }
5
- type StreamPayload = Record<string, unknown>;
6
-
7
- export type { ApiTypeMap, StreamPayload, SyncTypeMap };
5
+ export type StreamPayload = Record<string, unknown>;
@@ -0,0 +1,22 @@
1
+ import { type DeepPartial } from './configUtils.js';
2
+ export interface AvatarConfig {
3
+ /**
4
+ * File format(s) the framework will look for on disk, in order. The first
5
+ * one that exists wins. Each entry is `{ extension, contentType }` so the
6
+ * `Content-Type` response header matches what the file actually is.
7
+ * Default: `[{ extension: 'webp', contentType: 'image/webp' }]`.
8
+ */
9
+ formats: {
10
+ extension: string;
11
+ contentType: string;
12
+ }[];
13
+ /**
14
+ * `Cache-Control` value sent with the avatar response.
15
+ * Default: `'public, max-age=86400'` (24 hours).
16
+ */
17
+ cacheControl: string;
18
+ }
19
+ export declare const DEFAULT_AVATAR_CONFIG: AvatarConfig;
20
+ export type AvatarConfigInput = DeepPartial<AvatarConfig>;
21
+ export declare const registerAvatarConfig: (config: AvatarConfigInput) => void;
22
+ export declare const getAvatarConfig: () => AvatarConfig;
@@ -0,0 +1,15 @@
1
+ interface BindAddress {
2
+ ip: string;
3
+ port: number;
4
+ }
5
+ interface IntendedBindAddress extends BindAddress {
6
+ configuredPort?: number;
7
+ }
8
+ export declare const registerBindAddress: (address: IntendedBindAddress) => void;
9
+ export declare const registerBoundAddress: (address: BindAddress) => void;
10
+ export declare const getBindAddress: () => {
11
+ ip: string;
12
+ port: string;
13
+ };
14
+ export declare const resolveDevCallbackUrl: (callbackUrl: string) => string;
15
+ export {};
@@ -0,0 +1,24 @@
1
+ export declare const BOOT_KEY_PREFIX = "luckystack:boot:";
2
+ export declare const resolveEnvKey: () => string;
3
+ export declare const writeBootUuid: (envKey?: string) => Promise<string>;
4
+ export declare const readBootUuid: (envKey?: string) => Promise<string | null>;
5
+ /**
6
+ * Extends the current environment boot UUID without rotating its value.
7
+ *
8
+ * Every healthy backend may renew the environment-level key, so multi-instance
9
+ * deployments do not depend on whichever process happened to write it last. If
10
+ * Redis recovered after the key expired, the first refresher writes a new UUID.
11
+ */
12
+ export declare const refreshBootUuid: (envKey?: string) => Promise<void>;
13
+ export interface BootUuidHeartbeat {
14
+ stop: () => void;
15
+ }
16
+ /**
17
+ * Keeps the Redis boot UUID alive for as long as this backend is running.
18
+ *
19
+ * The self-rescheduling timeout waits for each Redis operation to settle before
20
+ * scheduling another, so an outage cannot accumulate overlapping refreshes.
21
+ * The timer is unref'd and therefore never keeps an otherwise-finished process
22
+ * alive. Failures are logged and retried on the next interval.
23
+ */
24
+ export declare const startBootUuidHeartbeat: (envKey?: string) => BootUuidHeartbeat;
@@ -0,0 +1,7 @@
1
+ export declare const registerSyncAbortController: (socketId: string, cb: string, controller: AbortController) => string;
2
+ export declare const unregisterSyncAbortController: (key: string) => void;
3
+ export declare const abortSyncByCb: (socketId: string, cb: string) => boolean;
4
+ export declare const registerApiAbortController: (socketId: string, responseIndex: number | string, controller: AbortController) => string;
5
+ export declare const unregisterApiAbortController: (key: string) => void;
6
+ export declare const abortApiByResponseIndex: (socketId: string, responseIndex: number | string) => boolean;
7
+ export declare const abortAllForSocket: (socketId: string) => void;
@@ -0,0 +1,6 @@
1
+ export declare const normalizeOrigin: ({ value, secure }: {
2
+ value: string;
3
+ secure: boolean;
4
+ }) => string;
5
+ declare const allowedOrigin: (origin: string) => boolean;
6
+ export default allowedOrigin;