@luckystack/api 0.8.2 → 0.8.4
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/CHANGELOG.md +6 -0
- package/CLAUDE.md +3 -5
- package/README.md +6 -6
- package/docs/api-request-lifecycle.md +0 -2
- package/docs/auth-flow.md +0 -2
- package/docs/error-handling.md +2 -4
- package/docs/generated-types.md +3 -5
- package/docs/rate-limiting.md +0 -2
- package/docs/routing-conventions.md +1 -3
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.4] - 2026-08-17
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Corrected consumer documentation for optional Prisma/error-tracking dependencies, registered-tracker capture, generated-type rule links, and stale freshness metadata.
|
|
15
|
+
|
|
10
16
|
## [0.8.1] - 2026-07-27
|
|
11
17
|
|
|
12
18
|
### Fixed
|
package/CLAUDE.md
CHANGED
|
@@ -82,10 +82,8 @@ No project env vars or secrets are read directly by this package — only the am
|
|
|
82
82
|
## Peer dependencies
|
|
83
83
|
|
|
84
84
|
- **Required (runtime deps)**: `@luckystack/core`, `@luckystack/error-tracking`. **`@luckystack/login` is NOT a dependency** (0.2.0 decoupling) — sessions resolve through core's `readSession` / session-provider registry, with login as the default *provider* (optional package), not a hard runtime dep.
|
|
85
|
-
- **Required (
|
|
86
|
-
|
|
87
|
-
- `socket.io@^4.8.0` (only consumed for the `Socket` type — the HTTP entry point does not need a live socket.io instance at call time).
|
|
88
|
-
- **No optional peers.** Streaming uses an injected callback, not an external SSE library.
|
|
85
|
+
- **Required peer**: `socket.io@^4.8.0` (only consumed for the `Socket` type — the HTTP entry point does not need a live socket.io instance at call time).
|
|
86
|
+
- **Optional peer**: `@prisma/client@^6.19.0` — only needed when the active session/user provider uses the default Prisma client. Streaming uses an injected callback, not an external SSE library.
|
|
89
87
|
|
|
90
88
|
## Hooks consumed
|
|
91
89
|
|
|
@@ -108,6 +106,6 @@ Deep behavior for each hook lives in `@luckystack/core`'s hook registry doc.
|
|
|
108
106
|
|
|
109
107
|
- Architecture deep dives: `/docs/ARCHITECTURE_API.md`, `/docs/ARCHITECTURE_ROUTING.md`.
|
|
110
108
|
- Consumer quickstart: `./README.md`.
|
|
111
|
-
- Generated-type contract: `/docs/ARCHITECTURE_ROUTING.md` (see "Generated maps") and root `CLAUDE.md` rule
|
|
109
|
+
- Generated-type contract: `/docs/ARCHITECTURE_ROUTING.md` (see "Generated maps") and repository-root `CLAUDE.md` rule 21 (no `unknown`/`any` casts).
|
|
112
110
|
- Session source: `@luckystack/core`'s session-provider registry — `readSession(token)` / `performLogout(...)` (see `docs/ARCHITECTURE_SESSION.md`). `@luckystack/login` is the DEFAULT provider when installed, not a hard dep. Shared types `BaseSessionLayout` / `AuthProps` come from `@luckystack/core`.
|
|
113
111
|
- Runtime map source: `@luckystack/core` `getRuntimeApiMaps()` + `@luckystack/devkit` emitter.
|
package/README.md
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @luckystack/api
|
|
8
|
+
npm install @luckystack/api socket.io
|
|
9
|
+
# Optional Prisma-backed route code: npm install @prisma/client
|
|
9
10
|
```
|
|
10
11
|
|
|
11
12
|
## Quickstart
|
|
@@ -59,7 +60,7 @@ You typically don't call these yourself — `createLuckyStackServer` does. Use t
|
|
|
59
60
|
2. **Rate-limits** with two buckets via `checkRateLimit` from `@luckystack/core`: a per-route bucket keyed on the validated `user.id` (anonymous callers fall back to the resolved IP — never the raw session token, so re-login can't reset the limit; the basis is overridable via `rateLimiting.identity`) plus a global per-IP abuse bucket.
|
|
60
61
|
3. **Validates** the inbound payload against the Zod schema generated from your `ApiParams['data']` interface (via `@luckystack/devkit`).
|
|
61
62
|
4. **Dispatches** the `preApiExecute` hook (may abort with a stop signal).
|
|
62
|
-
5. **Calls** your `main(...)` and captures errors via `tryCatch` (
|
|
63
|
+
5. **Calls** your `main(...)` and captures thrown errors via `tryCatch` (fanned out to every registered error tracker).
|
|
63
64
|
6. **Dispatches** the `postApiExecute` hook with the result + duration.
|
|
64
65
|
7. **Returns** the response via socket `ack` or HTTP body / SSE stream.
|
|
65
66
|
|
|
@@ -79,7 +80,7 @@ if (result.status === 'success') {
|
|
|
79
80
|
}
|
|
80
81
|
```
|
|
81
82
|
|
|
82
|
-
Do not wrap `apiRequest` in `unknown` / `any` shims (see
|
|
83
|
+
Do not wrap `apiRequest` in `unknown` / `any` shims (see repository-root [`CLAUDE.md`](../../CLAUDE.md) rule 21). If inference fails, fix the typing source or regenerate maps instead.
|
|
83
84
|
|
|
84
85
|
## Public API
|
|
85
86
|
|
|
@@ -97,9 +98,8 @@ Do not wrap `apiRequest` in `unknown` / `any` shims (see `.claude/CLAUDE.md` rul
|
|
|
97
98
|
## Dependencies
|
|
98
99
|
|
|
99
100
|
- Runtime: `@luckystack/core`, `@luckystack/error-tracking` (`@luckystack/login` is NOT a runtime dependency — sessions resolve through core's session-provider registry; login is the optional default provider)
|
|
100
|
-
-
|
|
101
|
-
|
|
102
|
-
- `socket.io@^4.8.0`
|
|
101
|
+
- Required peer: `socket.io@^4.8.0`
|
|
102
|
+
- Optional peer: `@prisma/client@^6.19.0` — only needed when the active session/user provider uses the default Prisma client.
|
|
103
103
|
|
|
104
104
|
## License
|
|
105
105
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# API Request Lifecycle
|
|
2
2
|
|
|
3
|
-
> Last updated: 2026-05-20
|
|
4
|
-
|
|
5
3
|
## Overview
|
|
6
4
|
|
|
7
5
|
`@luckystack/api` exposes two transport adapters — `handleApiRequest` (socket.io) and `handleHttpApiRequest` (raw HTTP) — that share an identical 13-step pipeline. The pipeline takes a transport payload, resolves it to a registered route in the runtime maps emitted by `@luckystack/devkit`, runs authentication / rate-limit / input-validation gates, dispatches a fixed set of lifecycle hooks, executes the route's `main(...)` function under a `tryCatch` boundary, then normalizes the result into a localized envelope before emitting it on the wire.
|
package/docs/auth-flow.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# Auth Flow
|
|
2
2
|
|
|
3
|
-
> Last updated: 2026-05-20
|
|
4
|
-
|
|
5
3
|
## Overview
|
|
6
4
|
|
|
7
5
|
`@luckystack/api` does not own authentication — it consumes `getSession` from `@luckystack/login` and `validateRequest` from `@luckystack/core`. The handler's job is to translate the per-route `AuthProps` declaration into the right gate (require login + optional predicate checks) and to attribute every framework error tracker call that follows to the right user.
|
package/docs/error-handling.md
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# Error Handling
|
|
2
2
|
|
|
3
|
-
> Last updated: 2026-05-20
|
|
4
|
-
|
|
5
3
|
## Overview
|
|
6
4
|
|
|
7
5
|
`@luckystack/api` produces a strict, two-shape response envelope. Every code path through the handler — malformed input, auth failure, rate-limit rejection, handler throw, handler return — ends in either `{ status: 'success', ... }` or `{ status: 'error', errorCode, errorParams?, httpStatus, message }`. The transformation from raw failure to wire envelope flows through three layers:
|
|
8
6
|
|
|
9
|
-
1. **`tryCatch`** wraps the handler's `main(...)` call.
|
|
7
|
+
1. **`tryCatch`** wraps the handler's `main(...)` call. Thrown errors are captured (fanned out to every registered tracker with context), and the tuple `[error, result]` is returned to the pipeline. Raw exceptions never reach the client.
|
|
10
8
|
2. **`buildApiResponseEnvelope`** (socket) / **`buildNetworkError`** (HTTP) maps the raw handler result to a wire envelope, inferring `httpStatus` via `defaultHttpStatusForResponse` and routing the error fields through `normalizeErrorResponse`.
|
|
11
9
|
3. **`normalizeErrorResponse`** renders the localized `message` field from the `errorCode` + `errorParams`, using the project-registered translate function. Locale is extracted in priority order: `x-language` header → `accept-language` header → `user.language`.
|
|
12
10
|
|
|
@@ -199,7 +197,7 @@ The error tracker (registered via `@luckystack/error-tracking`) receives every `
|
|
|
199
197
|
|
|
200
198
|
## What is NOT handled here
|
|
201
199
|
|
|
202
|
-
- **Business-logic errors inside `main(...)`.** Routes return `{ status: 'error', errorCode: '...' }` directly. The envelope normalizer renders them — no thrown exception, no
|
|
200
|
+
- **Business-logic errors inside `main(...)`.** Routes return `{ status: 'error', errorCode: '...' }` directly. The envelope normalizer renders them — no thrown exception, no error-tracker capture. This is by design: business outcomes (validation failures in domain code, "user already exists") are not "errors" in the tracking sense.
|
|
203
201
|
- **Localization of `errorCode` strings.** That is the consumer's responsibility — `registerLocalizedNormalizer` from the project's `server/utils/responseNormalizer.ts` provides the translate function backed by JSON locale files in `src/_locales/`.
|
|
204
202
|
- **Frontend error rendering.** The client's `apiRequest` returns the normalized envelope. UI code consumes `result.errorCode` + `result.message` and decides how to render (toast via `notify`, inline form error, redirect).
|
|
205
203
|
- **Hook handler errors.** Failures inside a `dispatchHook` handler are isolated by the hook bus — they log via `getLogger()` + the tracker but do not affect the main pipeline.
|
package/docs/generated-types.md
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# Generated Types
|
|
2
2
|
|
|
3
|
-
> Last updated: 2026-05-20
|
|
4
|
-
|
|
5
3
|
## Overview
|
|
6
4
|
|
|
7
5
|
LuckyStack's API layer is fully type-safe end-to-end. Every `src/{page}/_api/{name}_v{N}.ts` file is read by `@luckystack/devkit` via the TypeScript compiler API, its `ApiParams['data']` and `main` return types are extracted, and three artifacts are emitted: `apiTypes.generated.ts` (the static type map), `apiInputSchemas.generated.ts` (runtime Zod schemas), and `apiDocs.generated.json` (introspection JSON used by `@luckystack/docs-ui`). `@luckystack/api` reads the runtime map at request time and uses the input-type text to drive `validateInputByType`. The client-side `apiRequest` from `@luckystack/core` reads the static type map to infer payload + response shapes from the route-name + version literals you pass it.
|
|
8
6
|
|
|
9
|
-
This means: any change to an API file's `ApiParams['data']` interface or `main` return type propagates to the client at the next regeneration. The hot-reload watcher (devkit) triggers regeneration on save. The strict typing policy in the
|
|
7
|
+
This means: any change to an API file's `ApiParams['data']` interface or `main` return type propagates to the client at the next regeneration. The hot-reload watcher (devkit) triggers regeneration on save. The strict typing policy in the repository-root contract (`CLAUDE.md` rule 21) requires this inference chain to remain intact — `unknown` / `any` casts around `apiRequest` defeat the whole pipeline.
|
|
10
8
|
|
|
11
9
|
This doc covers the generated type surface, the runtime validation flow that consumes it, and the per-route opt-out for cases where strict input typing is impossible (third-party webhooks).
|
|
12
10
|
|
|
@@ -159,7 +157,7 @@ The HTTP transport does **not** honor the flag — relax-mode endpoints typicall
|
|
|
159
157
|
|
|
160
158
|
## Strict typing policy
|
|
161
159
|
|
|
162
|
-
Repository rule (root
|
|
160
|
+
Repository rule (root `CLAUDE.md` §21): **NEVER** cast typed API/sync payloads to `unknown`, `any`, or `unsafe*` wrappers when calling `apiRequest`, `syncRequest`, or `upsertSyncEventCallback`.
|
|
163
161
|
|
|
164
162
|
```ts
|
|
165
163
|
// Bad — defeats the entire generated-types pipeline
|
|
@@ -261,4 +259,4 @@ This package does not own hooks specific to type generation. The runtime validat
|
|
|
261
259
|
- Error handling: [`./error-handling.md`](./error-handling.md)
|
|
262
260
|
- Architecture: [`/docs/ARCHITECTURE_ROUTING.md`](../../../docs/ARCHITECTURE_ROUTING.md) (see "Generated maps")
|
|
263
261
|
- Devkit type-map generation: `@luckystack/devkit/docs/type-map-generation.md`
|
|
264
|
-
- Root rule on casts: [
|
|
262
|
+
- Root rule on casts: [`CLAUDE.md`](../../../CLAUDE.md) §21
|
package/docs/rate-limiting.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# Rate Limiting
|
|
2
2
|
|
|
3
|
-
> Last updated: 2026-05-20
|
|
4
|
-
|
|
5
3
|
## Overview
|
|
6
4
|
|
|
7
5
|
Every API request runs through two rate-limit buckets, in order. The per-route bucket protects an individual endpoint from a single caller; the global per-IP bucket protects the server as a whole from a single source hitting many endpoints. Both buckets use the same strategy backend (configurable via `registerRateLimitStrategy` in `@luckystack/core`; defaults to memory-or-redis depending on `rateLimiting.store`), share the same `windowMs`, and emit the same `rateLimitExceeded` hook on rejection.
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# Routing Conventions
|
|
2
2
|
|
|
3
|
-
> Last updated: 2026-05-20
|
|
4
|
-
|
|
5
3
|
## Overview
|
|
6
4
|
|
|
7
5
|
`@luckystack/api` is transport-agnostic — it does not own file-based routing. Discovery of `_api/<name>_v<N>.ts` files lives in `@luckystack/devkit`, which walks `src/`, parses each file's exports, and emits two artifacts the API handler reads at runtime: the route map (`getRuntimeApiMaps()`) and the generated type map (`apiTypes.generated.ts`). This document describes the route-name conventions both handlers enforce, the per-route exports the runtime map carries, and the HTTP-method inference contract the HTTP transport applies.
|
|
@@ -67,7 +65,7 @@ export const main = async ({ data, user, functions }: ApiParams): Promise<ApiRes
|
|
|
67
65
|
|
|
68
66
|
| Export | Type | Default | Effect |
|
|
69
67
|
| --- | --- | --- | --- |
|
|
70
|
-
| `main` | `(params) => Promise<ApiResponse>` | required | Handler invoked by `executeApiHandler` inside the `tryCatch` +
|
|
68
|
+
| `main` | `(params) => Promise<ApiResponse>` | required | Handler invoked by `executeApiHandler` inside the `tryCatch` + registered error-tracker span. |
|
|
71
69
|
| `auth` | `AuthProps` | required | Auth gate (see [`./auth-flow.md`](./auth-flow.md)). |
|
|
72
70
|
| `rateLimit` | `number \| false` | `rateLimiting.defaultApiLimit` from config | Per-route requests-per-window cap. `false` disables the per-route bucket; the global IP bucket still applies. See [`./rate-limiting.md`](./rate-limiting.md). |
|
|
73
71
|
| `httpMethod` | `'GET' \| 'POST' \| 'PUT' \| 'DELETE'` | inferred from name | Overrides `inferHttpMethod` for the HTTP transport. Socket transport ignores this field. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luckystack/api",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"test": "vitest run"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@luckystack/core": "^0.8.
|
|
60
|
-
"@luckystack/error-tracking": "^0.8.
|
|
59
|
+
"@luckystack/core": "^0.8.4",
|
|
60
|
+
"@luckystack/error-tracking": "^0.8.4"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"@prisma/client": "^6.19.0",
|