updated skills
This commit is contained in:
@@ -357,9 +357,9 @@ Game controllers, entity-component systems, and game-server logic.
|
||||
|
||||
| Package | Description |
|
||||
|---|---|
|
||||
| [`@suaveplan/controller`](./gaming/games/controller/README.md) | Universal input controller foundation. Ships the unified `ControllerInput` shape (37 normalised fields in `[0, 255]`), `BaseController` abstract base with inje… |
|
||||
| [`@suaveplan/ecs`](./gaming/games/ecs/README.md) | Archetype-based Entity Component System with `Uint32`-backed bitset queries, phase-ordered system scheduling, entity-ID recycling, Zod-validated component sche… |
|
||||
| [`@suaveplan/game-server`](./gaming/games/game-server/README.md) | Universal game-server framework. Migrated from `@suaveplan/game-server` on the reference branch `claude/review-game-engine-arch-Kaykr` (main repo `packages/uni… |
|
||||
| [`@suaveplan/controller`](./gaming/gaming/controller/README.md) | Universal input controller foundation. Ships the unified `ControllerInput` shape (37 normalised fields in `[0, 255]`), `BaseController` abstract base with inje… |
|
||||
| [`@suaveplan/ecs`](./gaming/gaming/ecs/README.md) | Archetype-based Entity Component System with `Uint32`-backed bitset queries, phase-ordered system scheduling, entity-ID recycling, Zod-validated component sche… |
|
||||
| [`@suaveplan/game-server`](./gaming/gaming/game-server/README.md) | Universal game-server framework. Migrated from `@suaveplan/game-server` on the reference branch `claude/review-game-engine-arch-Kaykr` (main repo `packages/uni… |
|
||||
|
||||
### `geospatial`
|
||||
|
||||
|
||||
@@ -106,4 +106,3 @@ Every adapter implementing a writer participant MUST run
|
||||
`tests/conformance/run-2pc-participant.test.ts` file. The harness
|
||||
pins prepare / commit / rollback semantics and the idempotency
|
||||
contract.
|
||||
|
||||
|
||||
@@ -82,4 +82,3 @@ await sink.dispose();
|
||||
## Tier
|
||||
|
||||
Server-tier — depends on `node:fs/promises`.
|
||||
|
||||
|
||||
@@ -76,4 +76,3 @@ queue wait exceeds the bound the record is dropped, the
|
||||
`api.audit.dropped_total{sink,reason}` counter increments, and
|
||||
(subject to a once-per-`warnThrottleMs` throttle) a single warning is
|
||||
logged. Drops are NEVER silent.
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ _(no submodules)_
|
||||
## Purpose
|
||||
|
||||
This module produces the HMAC-SHA-256 tag attached to every audit
|
||||
line. Signing operates on a *canonical* byte representation of the
|
||||
line. Signing operates on a _canonical_ byte representation of the
|
||||
record (keys serialised in a fixed order, `cause` omitted when
|
||||
absent) so the same logical record always produces the same MAC under
|
||||
the same key, regardless of how the source object was constructed.
|
||||
|
||||
@@ -11,7 +11,7 @@ writes. The shape is exactly what design doc §12.5 specifies for the
|
||||
admin / audit pipeline: `ts`, `type`, `principal`, `tenant`,
|
||||
`request_id`, `trace_id`, `subject`, `outcome`, and a conditional
|
||||
`cause`. Every write goes through `parseAuditRecord(...)` so a
|
||||
malformed record raises `AUDIT_STDOUT.INVALID_RECORD` *before* a line
|
||||
malformed record raises `AUDIT_STDOUT.INVALID_RECORD` _before_ a line
|
||||
is written — log pipelines downstream never see a half-finished
|
||||
record.
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ rejected synchronously without consuming the slot.
|
||||
- Boundary Zod validation — `AUDIT_STDOUT.INVALID_RECORD` on a bad
|
||||
record.
|
||||
- Bounded queue — `AUDIT_STDOUT.QUEUE_FULL` on timeout, drop counter
|
||||
+ throttled warning.
|
||||
- throttled warning.
|
||||
- HMAC-SHA-256 over canonical bytes appended as `_hmac` field.
|
||||
- `dispose()` drains the in-flight write and rejects subsequent
|
||||
writes as `closed` drops.
|
||||
|
||||
@@ -57,4 +57,3 @@ The resolver passes `runAuthSessionConformance` (null-only mode) from
|
||||
`AUTH_ANON.INVALID_OPTIONS` — thrown only when the factory is handed a
|
||||
non-object or unknown-key options bag. No per-request error paths exist by
|
||||
design.
|
||||
|
||||
|
||||
@@ -152,4 +152,3 @@ Both surfaces pass `runAuthSessionConformance` from
|
||||
`@suaveplan/api-auth`. Spec-conformance tests live alongside the source
|
||||
under `src/factory/*.spec-conformance.test.ts` and
|
||||
`src/proxy/*.spec-conformance.test.ts`.
|
||||
|
||||
|
||||
@@ -169,4 +169,3 @@ createBetterAuthSession createBetterAuthProvider
|
||||
▼ ▼
|
||||
Principal auth.handler(rewrittenRequest)
|
||||
```
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ provider, not this package.
|
||||
`JWT_BEARER_SCHEME` and `createJwtBearerSecurityScheme(name)` for
|
||||
OpenAPI emission.
|
||||
- [`./error-codes`](src/error-codes/error-codes.md) — `AUTH_JWT_*` codes
|
||||
+ `AuthJwtError` class.
|
||||
- `AuthJwtError` class.
|
||||
- [`./options`](src/options/options.md) — Zod-validated options
|
||||
boundary.
|
||||
|
||||
@@ -132,4 +132,3 @@ returns a non-Principal value.
|
||||
## License
|
||||
|
||||
MIT — see the repository.
|
||||
|
||||
|
||||
@@ -65,4 +65,3 @@ package.
|
||||
## Stage / status
|
||||
|
||||
Concrete providers ship in Stages 6 / 8.
|
||||
|
||||
|
||||
@@ -57,11 +57,11 @@ ownership.register(replacementProvider);
|
||||
## API Reference
|
||||
|
||||
- `class PathOwnership` — runtime registry.
|
||||
- `constructor(options?: PathOwnershipOptions)`
|
||||
- `register(provider): PathOwnershipClaim`
|
||||
- `release(): void`
|
||||
- `current(): ProviderClaim | null`
|
||||
- `hasProvider(): boolean`
|
||||
- `constructor(options?: PathOwnershipOptions)`
|
||||
- `register(provider): PathOwnershipClaim`
|
||||
- `release(): void`
|
||||
- `current(): ProviderClaim | null`
|
||||
- `hasProvider(): boolean`
|
||||
- `interface PathOwnershipOptions { clock? }`
|
||||
- `type OwnershipClock = () => number`
|
||||
- `interface PathOwnershipClaim { providerId; prefix; claimedAt }`
|
||||
|
||||
@@ -4,6 +4,7 @@ Stage-8.12 rolled session-token resolver for `@suaveplan/api`. Issues
|
||||
RS256-signed JWTs with rotating `kid` slots, verifies tokens through the
|
||||
rotator's active + retired-non-revoked key set, projects DB rows onto
|
||||
the `@suaveplan/api-types` `Principal` contract, and contributes cookie
|
||||
|
||||
+ bearer OpenAPI security schemes.
|
||||
|
||||
This milestone ships the **session-resolver half** of the eventual dual
|
||||
@@ -14,20 +15,20 @@ a successor change `api-auth-rolled-provider`.
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [composition](./src/composition/composition.md) — Top-level factory `createRolledAuthAdapter` that wires the four sub-modules (`token-rotator`, `session`, `principal-pro…
|
||||
- [composition](./src/composition/composition.md) — Top-level factory `createRolledAuthAdapter` that wires the four sub-modules (`token-rotator`, `session`, `principal-pro…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Stable error-code registry for `@suaveplan/api-auth-rolled`. Every code is namespaced under `AUTH_ROLLED.*` so cross-pa…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Stable error-code registry for `@suaveplan/api-auth-rolled`. Every code is namespaced under `AUTH_ROLLED.*` so cross-pa…
|
||||
- [options](./src/options/options.md) — Defines the configuration shape accepted by `createRolledAuthAdapter` and the Zod schema that validates it. Operators w…
|
||||
- [options](./src/options/options.md) — Defines the configuration shape accepted by `createRolledAuthAdapter` and the Zod schema that validates it. Operators w…
|
||||
- [principal-projection](./src/principal-projection/principal-projection.md) — Projects the package's internal `UserRow` storage shape onto the abstract `Principal` contract published by `@suaveplan…
|
||||
- [principal-projection](./src/principal-projection/principal-projection.md) — Projects the package's internal `UserRow` storage shape onto the abstract `Principal` contract published by `@suaveplan…
|
||||
- [security-schemes](./src/security-schemes/security-schemes.md) — Contributes OpenAPI 3.1 `securitySchemes` entries describing both transports the rolled session resolver accepts — `Aut…
|
||||
- [security-schemes](./src/security-schemes/security-schemes.md) — Contributes OpenAPI 3.1 `securitySchemes` entries describing both transports the rolled session resolver accepts — `Aut…
|
||||
- [session](./src/session/session.md) — The rolling-JWT session resolver — the keystone of the rolled auth adapter. Builds a `SessionResolver` (per `@suaveplan…
|
||||
- [session](./src/session/session.md) — The rolling-JWT session resolver — the keystone of the rolled auth adapter. Builds a `SessionResolver` (per `@suaveplan…
|
||||
- [token-rotator](./src/token-rotator/token-rotator.md) — Owns the rotating-keyset state for the rolled session-token resolver. The active key signs new JWTs; retired-but-non-re…
|
||||
- [token-rotator](./src/token-rotator/token-rotator.md) — Owns the rotating-keyset state for the rolled session-token resolver. The active key signs new JWTs; retired-but-non-re…
|
||||
+ [composition](./src/composition/composition.md) — Top-level factory `createRolledAuthAdapter` that wires the four sub-modules (`token-rotator`, `session`, `principal-pro…
|
||||
+ [composition](./src/composition/composition.md) — Top-level factory `createRolledAuthAdapter` that wires the four sub-modules (`token-rotator`, `session`, `principal-pro…
|
||||
+ [error-codes](./src/error-codes/error-codes.md) — Stable error-code registry for `@suaveplan/api-auth-rolled`. Every code is namespaced under `AUTH_ROLLED.*` so cross-pa…
|
||||
+ [error-codes](./src/error-codes/error-codes.md) — Stable error-code registry for `@suaveplan/api-auth-rolled`. Every code is namespaced under `AUTH_ROLLED.*` so cross-pa…
|
||||
+ [options](./src/options/options.md) — Defines the configuration shape accepted by `createRolledAuthAdapter` and the Zod schema that validates it. Operators w…
|
||||
+ [options](./src/options/options.md) — Defines the configuration shape accepted by `createRolledAuthAdapter` and the Zod schema that validates it. Operators w…
|
||||
+ [principal-projection](./src/principal-projection/principal-projection.md) — Projects the package's internal `UserRow` storage shape onto the abstract `Principal` contract published by `@suaveplan…
|
||||
+ [principal-projection](./src/principal-projection/principal-projection.md) — Projects the package's internal `UserRow` storage shape onto the abstract `Principal` contract published by `@suaveplan…
|
||||
+ [security-schemes](./src/security-schemes/security-schemes.md) — Contributes OpenAPI 3.1 `securitySchemes` entries describing both transports the rolled session resolver accepts — `Aut…
|
||||
+ [security-schemes](./src/security-schemes/security-schemes.md) — Contributes OpenAPI 3.1 `securitySchemes` entries describing both transports the rolled session resolver accepts — `Aut…
|
||||
+ [session](./src/session/session.md) — The rolling-JWT session resolver — the keystone of the rolled auth adapter. Builds a `SessionResolver` (per `@suaveplan…
|
||||
+ [session](./src/session/session.md) — The rolling-JWT session resolver — the keystone of the rolled auth adapter. Builds a `SessionResolver` (per `@suaveplan…
|
||||
+ [token-rotator](./src/token-rotator/token-rotator.md) — Owns the rotating-keyset state for the rolled session-token resolver. The active key signs new JWTs; retired-but-non-re…
|
||||
+ [token-rotator](./src/token-rotator/token-rotator.md) — Owns the rotating-keyset state for the rolled session-token resolver. The active key signs new JWTs; retired-but-non-re…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Why
|
||||
@@ -80,4 +81,3 @@ setInterval(async () => {
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -122,4 +122,3 @@ const host = createTestHost({
|
||||
|
||||
Until then, callers wire the adapter directly via
|
||||
`createStaticPrincipalAdapter()` as shown above.
|
||||
|
||||
|
||||
@@ -93,9 +93,9 @@ const principal = await chain.resolve(new Request("https://example.test"));
|
||||
|
||||
## Versioning
|
||||
|
||||
1.0`. Graduates to `1.0.0` when the Stage 1
|
||||
contract bundle (`api-types`, `api-auth`, `api-tenant-resolver`,
|
||||
`api-secrets`, `api-db-adapter`) all reach 1.0.
|
||||
1.0`. Graduates to`1.0.0`when the Stage 1
|
||||
contract bundle (`api-types`,`api-auth`,`api-tenant-resolver`,
|
||||
`api-secrets`,`api-db-adapter`) all reach 1.0.
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -106,4 +106,3 @@ contract bundle (`api-types`, `api-auth`, `api-tenant-resolver`,
|
||||
| `@suaveplan/types` | Cross-package contracts (logger, clock, etc.). |
|
||||
| `tslib` | TypeScript runtime helpers (catalog). |
|
||||
| `zod` | Boundary validation for security-scheme contributions. |
|
||||
|
||||
|
||||
@@ -125,4 +125,3 @@ a no-op.
|
||||
factory.
|
||||
- `DB_ADAPTER.INVALID_OPTIONS` — Zod validation failed; issues attached
|
||||
to `error.context.issues`.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ The result object exposes:
|
||||
## Features
|
||||
|
||||
- Options are parsed once at factory time; the rest of the API is typed
|
||||
against the *post-parse* `DbAdapterOptions`.
|
||||
against the _post-parse_ `DbAdapterOptions`.
|
||||
- Engine lookup is delegated to `buildEngineAdapter`, so any
|
||||
declaration-merged engine works automatically.
|
||||
- Tenant scoping config (`tenantColumn`, `autoInjectInserts`) flows from
|
||||
|
||||
@@ -24,7 +24,7 @@ payloads.
|
||||
- `parseDbAdapterOptions(input)` — convenience wrapper that converts every Zod
|
||||
failure into a typed `DbAdapterError` with code `INVALID_OPTIONS`. Issues
|
||||
are surfaced through `error.context.issues`.
|
||||
- `engine` is `z.string().min(1)` — the *literal value* check happens against
|
||||
- `engine` is `z.string().min(1)` — the _literal value_ check happens against
|
||||
the open `DatabaseEngineRegistry` so declaration-merged engines pass.
|
||||
- `pool` is an optional sub-schema with its own defaults; absent values are
|
||||
forwarded as `undefined` to the engine layer rather than guessed.
|
||||
|
||||
@@ -10,7 +10,7 @@ This module is the load-bearing safety boundary between a plugin route handler
|
||||
and the underlying admin database surface. Every SELECT/UPDATE/DELETE that the
|
||||
route handler issues is rewritten in flight to carry a `WHERE tenant_id = ?`
|
||||
predicate; every INSERT either carries the tenant column or is rejected (or
|
||||
auto-injected, configurable). The contract is enforced *at the wrapper*, so a
|
||||
auto-injected, configurable). The contract is enforced _at the wrapper_, so a
|
||||
route handler literally cannot read another tenant's rows even if the plugin
|
||||
author is malicious.
|
||||
|
||||
|
||||
@@ -75,4 +75,3 @@ The final merged pragma map is `{ ...DEFAULT_PRAGMAS, ...options.pragmas }`, so
|
||||
## License
|
||||
|
||||
MIT.
|
||||
|
||||
|
||||
@@ -109,4 +109,3 @@ parentPort?.once("message", (boot) => {
|
||||
- [`src/tenant-scoped/tenant-scoped.md`](./src/tenant-scoped/tenant-scoped.md)
|
||||
- [`src/error-codes/error-codes.md`](./src/error-codes/error-codes.md)
|
||||
- [`src/testing/testing.md`](./src/testing/testing.md)
|
||||
|
||||
|
||||
@@ -102,4 +102,3 @@ underlying `z.ZodError` on `cause`.
|
||||
## License
|
||||
|
||||
MIT — Suaveplan Team.
|
||||
|
||||
|
||||
@@ -87,4 +87,3 @@ console.log(result.delivered);
|
||||
- [`payload-codec`](./src/payload-codec/payload-codec.md)
|
||||
- [`error-codes`](./src/error-codes/error-codes.md)
|
||||
- [`testing`](./src/testing/testing.md)
|
||||
|
||||
|
||||
@@ -66,4 +66,3 @@ slimmer dependency closure.
|
||||
Original. Owned by the `api-stack` super-category.
|
||||
Passes the abstract conformance harness with the same invariants Yoga
|
||||
satisfies — see `src/engine/apollo-engine.spec-conformance.test.ts`.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ boundary.
|
||||
|
||||
The module also re-exports the unmodified `runGraphqlEngineConformance`
|
||||
so downstream packages that want to run the harness against a
|
||||
*different* factory (e.g. a federated subgraph composing Apollo + a
|
||||
_different_ factory (e.g. a federated subgraph composing Apollo + a
|
||||
custom transport) keep a single import surface.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -94,4 +94,3 @@ the `ui` label.
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -104,4 +104,3 @@ bunx --bun turbo lint typecheck test:coverage --filter=@suaveplan/api-graphql-ui
|
||||
|
||||
100% statements / branches / functions / lines required on touched
|
||||
modules (`provider: istanbul`).
|
||||
|
||||
|
||||
@@ -61,4 +61,4 @@ if (cspAllowsInlineScripts(myCspValue)) {
|
||||
Inline-style allowance is documented in the module header so future
|
||||
maintainers don't tighten the policy without understanding why. The
|
||||
predicate operates on the raw header string rather than parsing the CSP
|
||||
grammar — sufficient for the conformance check.
|
||||
grammar — sufficient for the conformance check.
|
||||
|
||||
@@ -87,4 +87,3 @@ const result = await engine.executeQuery({ query: "{ ping }" }, ctx);
|
||||
## License
|
||||
|
||||
MIT — Suaveplan Team.
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ Bridges the `WsBroker` contract from `@suaveplan/api-ws` to GraphQL
|
||||
`Subscription` resolvers via `AsyncIterable<Payload>`. The Yoga engine
|
||||
wires `graphql-ws` over the host's already-mounted WebSocket transport;
|
||||
this module is the per-subscription helper that converts a broker `topic`
|
||||
|
||||
+ `tenant` pair into an iterator the engine can consume.
|
||||
|
||||
Tenant filtering is enforced at the bridge: only `BrokerEnvelope` items
|
||||
@@ -20,10 +21,10 @@ within one tick.
|
||||
|
||||
## Features
|
||||
|
||||
- Returns `AsyncIterable<Payload>` with an explicit `close()` accessor.
|
||||
- Queues payloads when no consumer is awaiting; resolves immediately when
|
||||
+ Returns `AsyncIterable<Payload>` with an explicit `close()` accessor.
|
||||
+ Queues payloads when no consumer is awaiting; resolves immediately when
|
||||
a consumer is parked.
|
||||
- Calls `GraphqlYogaError("SUBSCRIPTION_TRANSPORT_UNAVAILABLE")` when the
|
||||
+ Calls `GraphqlYogaError("SUBSCRIPTION_TRANSPORT_UNAVAILABLE")` when the
|
||||
host hands a `null` broker, satisfying the design doc §10.4 contract.
|
||||
|
||||
## Usage: Basic
|
||||
@@ -59,7 +60,7 @@ const subscription = {
|
||||
|
||||
## API Reference
|
||||
|
||||
- `subscribeBrokerTopic(broker, topic, tenant)` — returns
|
||||
+ `subscribeBrokerTopic(broker, topic, tenant)` — returns
|
||||
`AsyncIterable<Payload> & { close(): Promise<void> }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
@@ -78,4 +78,3 @@ Altair packages land .
|
||||
- `@suaveplan/error` — `defineCodes` + `SuaveplanError` base.
|
||||
- `zod` — subscription protocol envelope validation.
|
||||
- `tslib` — TypeScript helper functions.
|
||||
|
||||
|
||||
@@ -115,4 +115,3 @@ bun add @suaveplan/api-host
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -65,14 +65,14 @@ await registry.dispose();
|
||||
## API Reference
|
||||
|
||||
- `class CapabilityRegistry`
|
||||
- `register(reg)`: void
|
||||
- `lookup<Id>(id): CapabilityRegistration<Id> | undefined`
|
||||
- `require<Id>(id): CapabilityRegistration<Id>`
|
||||
- `has(id): boolean`
|
||||
- `ids(): readonly CapabilityId[]`
|
||||
- `size(): number`
|
||||
- `assertSatisfies(ranges: Record<string, string>): void`
|
||||
- `dispose(): Promise<void>`
|
||||
- `register(reg)`: void
|
||||
- `lookup<Id>(id): CapabilityRegistration<Id> | undefined`
|
||||
- `require<Id>(id): CapabilityRegistration<Id>`
|
||||
- `has(id): boolean`
|
||||
- `ids(): readonly CapabilityId[]`
|
||||
- `size(): number`
|
||||
- `assertSatisfies(ranges: Record<string, string>): void`
|
||||
- `dispose(): Promise<void>`
|
||||
- `defineCapability<const Id>(reg): CapabilityRegistration<Id>`
|
||||
- `CapabilityScope`: `"host" | "plugin" | "request"`
|
||||
- `CapabilityRegistration<Id>`: shape consumed by `register`.
|
||||
|
||||
@@ -73,12 +73,12 @@ for (const name of HOST_COUNTER_NAMES) {
|
||||
- `HOST_COUNTER_NAMES` — `readonly` tuple of every counter name.
|
||||
- `HOST_HISTOGRAM_NAMES` — `readonly` tuple of every histogram name.
|
||||
- `HostInstruments`:
|
||||
- `meter: Meter`
|
||||
- `tenantLabelOptIn: boolean`
|
||||
- `counters: Readonly<Record<HostCounterName, Counter>>`
|
||||
- `histograms: Readonly<Record<HostHistogramName, Histogram>>`
|
||||
- `forPlugin(name): PluginInstruments`
|
||||
- `dispose(): void`
|
||||
- `meter: Meter`
|
||||
- `tenantLabelOptIn: boolean`
|
||||
- `counters: Readonly<Record<HostCounterName, Counter>>`
|
||||
- `histograms: Readonly<Record<HostHistogramName, Histogram>>`
|
||||
- `forPlugin(name): PluginInstruments`
|
||||
- `dispose(): void`
|
||||
- `PluginInstruments`: `{ meter: Meter; pluginName: string }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
@@ -112,7 +112,7 @@ const PIPELINE_STEPS: readonly PipelineStepName[];
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The pipeline runs route-match *first* (technically step 2 in §6) so
|
||||
The pipeline runs route-match _first_ (technically step 2 in §6) so
|
||||
later step gates (`auth`, `csrf`, `rateLimit`, `idempotent`) can
|
||||
consult per-route policy. The §6 ordering of "intake → match" is
|
||||
preserved at the engine adapter; this driver assumes the intake has
|
||||
|
||||
@@ -54,7 +54,7 @@ package adds only the jobs-specific verbs.
|
||||
## Local conformance harness
|
||||
|
||||
The proposal originally referenced `runJobsConformance` from
|
||||
`@suaveplan/job-scheduler/testing`. That harness does not yet exist;
|
||||
`@suaveplan/job-scheduler/testing`. That harness does not yet exist;
|
||||
this package therefore provides its own
|
||||
`JOBS_CONFORMANCE_CASES` + `runJobsConformance` under `./testing`. The
|
||||
surface matches what the upstream harness is intended to be — a
|
||||
@@ -67,4 +67,3 @@ in-repo substitute can be swapped out without changing consumers.
|
||||
bunx turbo lint typecheck test --filter=@suaveplan/api-jobs-worker-proxy
|
||||
bunx turbo test:coverage --filter=@suaveplan/api-jobs-worker-proxy
|
||||
```
|
||||
|
||||
|
||||
@@ -67,8 +67,8 @@ client.registerQueued("send-email", async (payload) => {
|
||||
- `JobsProxyClient extends JobRegistrationHandle` — adds `close()` and a
|
||||
`sizes` accessor reporting registration counts.
|
||||
- `JobsProxyClientOptions`:
|
||||
- `handlerTimeoutMs?: number` — per-dispatch timeout, `0` disables.
|
||||
- `onHandlerError?: (err, ctx) => void` — observer hook.
|
||||
- `handlerTimeoutMs?: number` — per-dispatch timeout, `0` disables.
|
||||
- `onHandlerError?: (err, ctx) => void` — observer hook.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
|
||||
@@ -48,11 +48,11 @@ try {
|
||||
## API Reference
|
||||
|
||||
- `JOBS_PROXY_CODES` — readonly registry of five string codes:
|
||||
- `JOB_NOT_DECLARED` — name absent from manifest whitelist.
|
||||
- `HANDLER_THREW` — worker-side handler threw or rejected.
|
||||
- `PROXY_DISPOSED` — operation attempted after `close()`.
|
||||
- `JOB_TIMEOUT` — dispatch exceeded `handlerTimeoutMs`.
|
||||
- `DUPLICATE_REGISTRATION` — same name registered twice.
|
||||
- `JOB_NOT_DECLARED` — name absent from manifest whitelist.
|
||||
- `HANDLER_THREW` — worker-side handler threw or rejected.
|
||||
- `PROXY_DISPOSED` — operation attempted after `close()`.
|
||||
- `JOB_TIMEOUT` — dispatch exceeded `handlerTimeoutMs`.
|
||||
- `DUPLICATE_REGISTRATION` — same name registered twice.
|
||||
- `JobsProxyCode` — union of every literal value in the registry.
|
||||
- `JobsProxyError(code, message, options?)` — typed error class.
|
||||
|
||||
|
||||
@@ -63,12 +63,12 @@ server.close("plugin-unload");
|
||||
|
||||
- `createJobsProxyServer(port, mainHandle, opts) -> JobsProxyServer`.
|
||||
- `JobsProxyServer`:
|
||||
- `close(reason?: string): void` — idempotent disposer.
|
||||
- `registeredCount: number` — count of accepted registrations.
|
||||
- `close(reason?: string): void` — idempotent disposer.
|
||||
- `registeredCount: number` — count of accepted registrations.
|
||||
- `JobsProxyServerOptions`:
|
||||
- `declaredJobs: ReadonlyArray<{name, kind}>` — manifest whitelist.
|
||||
- `runRpcTimeoutMs?: number` — optional per-tick RPC timeout.
|
||||
- `onDispatchError?: (err, ctx) => void` — telemetry hook.
|
||||
- `declaredJobs: ReadonlyArray<{name, kind}>` — manifest whitelist.
|
||||
- `runRpcTimeoutMs?: number` — optional per-tick RPC timeout.
|
||||
- `onDispatchError?: (err, ctx) => void` — telemetry hook.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
|
||||
@@ -79,4 +79,3 @@ await fsm.transition("startCompleted");
|
||||
| `LIFECYCLE.MUTEX_HELD` | `assertAcquired` called on a null `tryAcquire` result |
|
||||
| `LIFECYCLE.RETRY_BUDGET_EXHAUSTED` | start_failed × 3 OR crashed × 3 in window |
|
||||
| `LIFECYCLE.REINSTALL_REQUIRED_VERSION_DOWNGRADE` | `next.version < prev.version` |
|
||||
|
||||
|
||||
@@ -73,4 +73,4 @@ sections 8 (Hot reload), 8.1a (RollbackJournal), 8.2 (cache busting).
|
||||
|
||||
- Version: `0.1.0`
|
||||
- Wave: Stage 1 (Contracts)
|
||||
- Consumers: `@suaveplan/api-host` (Stage 2) implements the contracts here.
|
||||
- Consumers: `@suaveplan/api-host` (Stage 2) implements the contracts here.
|
||||
|
||||
@@ -88,7 +88,7 @@ export type ModuleResolverFactory<M = unknown> = () => ModuleResolver<M>;
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`resolve` returns the *evaluated namespace*, not a thenable wrapping it —
|
||||
`resolve` returns the _evaluated namespace_, not a thenable wrapping it —
|
||||
the host's lifecycle FSM treats a successful return as a synchronous
|
||||
"module loaded" signal even though the call itself is async. Concrete
|
||||
implementations are free to memoise nothing (the cache-bust strategy in
|
||||
|
||||
@@ -95,4 +95,3 @@ server.close();
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ proxy).
|
||||
`WireLogRecord` with defaults.
|
||||
- `runLoggerProxyConformance(plugin, options?)` — full paired
|
||||
harness: client + server + captured-output array + control sender
|
||||
+ `dispose()`.
|
||||
- `dispose()`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
|
||||
@@ -108,4 +108,3 @@ Every code is namespaced under `MANIFEST.*` and produced by `defineCodes`. The e
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -123,4 +123,3 @@ import { MIGRATION_CODES } from "@suaveplan/api-migrations/error-codes/error-cod
|
||||
|
||||
Server (Bun + Node). Uses `node:crypto` for SHA-256 and `node:fs/promises`
|
||||
for directory scanning and file reads.
|
||||
|
||||
|
||||
@@ -80,4 +80,3 @@ const res = handler();
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -127,4 +127,3 @@ bunx --bun turbo lint typecheck test:coverage --filter=@suaveplan/api-openapi-ui
|
||||
|
||||
100% statements / branches / functions / lines required on touched
|
||||
modules (`provider: istanbul`).
|
||||
|
||||
|
||||
@@ -78,4 +78,3 @@ for (const name of listBundledAssetNames()) {
|
||||
- `OPENAPI_UI_SWAGGER.ASSETS_MISSING` — bundled mode requested without `assetBasePath`, or a bundled asset name is not registered.
|
||||
|
||||
Both surfaces raise at factory-call time (eager validation), so misconfiguration is caught at boot rather than at first request.
|
||||
|
||||
|
||||
@@ -220,4 +220,3 @@ Server-tier (Hono-typed). Runs in Bun + Node.
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -85,4 +85,3 @@ app.post("/users/:id/delete", async (c) => {
|
||||
- `auditService` registered with `ctx.registerService("auditService", ...)` and exposed through the typed `ServiceRegistry` slot.
|
||||
- Hono `ContextVariableMap` augmented with `audit: AuditContext` so handlers fire explicit events without import boilerplate.
|
||||
- Four OpenTelemetry instruments: `audit.requests_recorded_total{outcome,sampled}`, `audit.requests_excluded_total`, `audit.sink_failures_total`, `audit.middleware_duration_ms`.
|
||||
|
||||
|
||||
@@ -192,4 +192,3 @@ createAuthPlugin({
|
||||
- `better-auth ^1.4` (resolves to 1.6.x via the catalog).
|
||||
- `hono`, `@hono/zod-openapi`, `zod` — supplied by the host
|
||||
application; the plugin imports them directly.
|
||||
|
||||
|
||||
@@ -65,4 +65,3 @@ each ships green on its own:
|
||||
`bunx turbo lint typecheck test --filter=@suaveplan/api-plugin-campaigns`
|
||||
plus `bunx turbo test:coverage --filter=@suaveplan/api-plugin-campaigns`
|
||||
at 100/100/100/100 are required for every chunk to land.
|
||||
|
||||
|
||||
@@ -94,4 +94,3 @@ app.onError((err, c) => respondProblemJson(c, err));
|
||||
## License
|
||||
|
||||
MIT — © Suaveplan Team
|
||||
|
||||
|
||||
@@ -108,4 +108,3 @@ Migration metrics are emitted by `@suaveplan/api-migrations` under its own meter
|
||||
## Errors
|
||||
|
||||
Every failure path raises `DatabaseError` carrying one of eight `DATABASE_*` codes: `CONNECTION_FAILED`, `POOL_EXHAUSTED`, `POOL_CLOSED`, `QUERY_FAILED`, `MIGRATION_FAILED`, `LOCK_CONTENDED`, `TENANT_INVALID`, `INVALID_CONFIG`. See [`error-codes/error-codes.md`](./src/error-codes/error-codes.md).
|
||||
|
||||
|
||||
@@ -193,4 +193,3 @@ table and `context.statusCode` mapping.
|
||||
application.
|
||||
- `@suaveplan/api-plugin-auth` — used by `requireAuth()` route gate
|
||||
when `requireAuthForRoutes: true` (default).
|
||||
|
||||
|
||||
@@ -109,4 +109,3 @@ for the full list. Highlights:
|
||||
`0.1.0` — feature parity with upstream `@suaveplan/api-plugin-events`
|
||||
modulo the explicit scope-outs above. 100% line / branch / function /
|
||||
statement coverage. Real SurrealDB testcontainer integration.
|
||||
|
||||
|
||||
@@ -102,4 +102,3 @@ bunx --bun turbo lint typecheck test:coverage --filter=@suaveplan/api-plugin-exa
|
||||
|
||||
100% Istanbul coverage is enforced; the manifest `errors.statusMap`
|
||||
is verified end-to-end against the runtime by the integration suite.
|
||||
|
||||
|
||||
@@ -86,4 +86,3 @@ DELETE /v1/exports/{id} → { success: true }
|
||||
## Scope notes
|
||||
|
||||
This package ships the upstream §2 functional surface (CSV / JSON / SpreadsheetML XML exporters + in-memory job store + four-route Hono group). The upstream §3–§4 enhancements (streaming exporters, S3-backed blob store, auth/permissions/rate-limit/idempotency middleware, OOXML/ZIP writer, scheduled exports, signed download URLs, Parquet/Markdown formats) are deferred to follow-up changes — they require downstream plugins that have not yet landed .
|
||||
|
||||
|
||||
@@ -55,4 +55,4 @@ const result = new XlsxExporter().export(rows, cols, { sheetName: "WideExport" }
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The output is *SpreadsheetML 2006* — a worksheet XML payload, not the ZIP package most consumers think of when they hear "xlsx". This is a deliberate carry-over from the upstream package which chose XML over the `xlsx` npm dependency. The MIME type is set to the OOXML sheet content-type so HTTP consumers can still dispatch by `Content-Type`; the filename suffix is `.xlsx.xml` to signal the format honestly. The sheet name is escaped into the `<worksheet sheetName="…">` attribute even though the bare-worksheet form is not strictly required to carry one — keeping the attribute lets downstream upgrades wrap this document in an OOXML/ZIP package without re-emitting the worksheet. The `columnIndexToLetter` loop uses `Math.floor(n / 26) - 1` so the multi-letter overflow is exactly Excel-compatible: `26 → "AA"`, `51 → "AZ"`, `52 → "BA"`, `676 → "ZA"`, `701 → "ZZ"`, `702 → "AAA"`. The boolean cell uses `1`/`0` (not `true`/`false`) because OOXML requires the integer form.
|
||||
The output is _SpreadsheetML 2006_ — a worksheet XML payload, not the ZIP package most consumers think of when they hear "xlsx". This is a deliberate carry-over from the upstream package which chose XML over the `xlsx` npm dependency. The MIME type is set to the OOXML sheet content-type so HTTP consumers can still dispatch by `Content-Type`; the filename suffix is `.xlsx.xml` to signal the format honestly. The sheet name is escaped into the `<worksheet sheetName="…">` attribute even though the bare-worksheet form is not strictly required to carry one — keeping the attribute lets downstream upgrades wrap this document in an OOXML/ZIP package without re-emitting the worksheet. The `columnIndexToLetter` loop uses `Math.floor(n / 26) - 1` so the multi-letter overflow is exactly Excel-compatible: `26 → "AA"`, `51 → "AZ"`, `52 → "BA"`, `676 → "ZA"`, `701 → "ZZ"`, `702 → "AAA"`. The boolean cell uses `1`/`0` (not `true`/`false`) because OOXML requires the integer form.
|
||||
|
||||
@@ -53,4 +53,4 @@ const inst = getExportInstruments(); // rebinds against the new provider
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The bundle is held in a module-scope `let cached: ExportInstruments | undefined` rather than in a class instance, because the function is the only documented entry point and there is no per-plugin state to keep — every plugin instance in a process shares the same meter. Counters use `add(value, attributes?)`; the `outcome` attribute is set by the call site rather than declared on the instrument because OTel attributes are runtime keys, not instrument metadata. The histogram unit is set to `"ms"` (lower-case) per OTel semantic conventions for latency. Resetting between tests is mandatory: meter handles cache internally inside `@opentelemetry/api`, so without `resetExportInstruments()` a test would emit to whichever provider was installed when the *first* `getExportInstruments()` call ran — usually the global default provider, which loses every metric.
|
||||
The bundle is held in a module-scope `let cached: ExportInstruments | undefined` rather than in a class instance, because the function is the only documented entry point and there is no per-plugin state to keep — every plugin instance in a process shares the same meter. Counters use `add(value, attributes?)`; the `outcome` attribute is set by the call site rather than declared on the instrument because OTel attributes are runtime keys, not instrument metadata. The histogram unit is set to `"ms"` (lower-case) per OTel semantic conventions for latency. Resetting between tests is mandatory: meter handles cache internally inside `@opentelemetry/api`, so without `resetExportInstruments()` a test would emit to whichever provider was installed when the _first_ `getExportInstruments()` call ran — usually the global default provider, which loses every metric.
|
||||
|
||||
@@ -56,4 +56,4 @@ const routes = createExportRoutes({
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `runPostExport` helper in `post-handler.ts` owns the bulk of the work — the route definitions stay in this file so that the OpenAPI schema is one click away from the route name. Failure paths set `job.status = "failed"` and persist `job.error` *before* returning, so `GET /:id` immediately afterwards sees the failure record (mirroring upstream behaviour). The `downloadUrl` response is `/${jobId}/download` — the actual streaming endpoint is upstream §3 future work, deferred in this migration. The downstream consumer is expected to combine this `downloadUrl` with the `basePath` themselves (e.g. `${basePath}${downloadUrl}`) — the upstream package made the same choice and the route layer cannot know the full mount path. Every route is also registered against an explicit `createRoute(...)` descriptor so an OpenAPI document generated by `OpenAPIHono.getOpenAPIDocument()` includes the export routes with full Zod-derived schemas.
|
||||
The `runPostExport` helper in `post-handler.ts` owns the bulk of the work — the route definitions stay in this file so that the OpenAPI schema is one click away from the route name. Failure paths set `job.status = "failed"` and persist `job.error` _before_ returning, so `GET /:id` immediately afterwards sees the failure record (mirroring upstream behaviour). The `downloadUrl` response is `/${jobId}/download` — the actual streaming endpoint is upstream §3 future work, deferred in this migration. The downstream consumer is expected to combine this `downloadUrl` with the `basePath` themselves (e.g. `${basePath}${downloadUrl}`) — the upstream package made the same choice and the route layer cannot know the full mount path. Every route is also registered against an explicit `createRoute(...)` descriptor so an OpenAPI document generated by `OpenAPIHono.getOpenAPIDocument()` includes the export routes with full Zod-derived schemas.
|
||||
|
||||
@@ -77,4 +77,4 @@ try {
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The two guards run in a specific order: row count is checked *before* the exporter runs so a runaway provider doesn't burn CPU encoding fields the route layer is about to reject. File size is checked *after* encoding because cell-level transforms and XML/JSON overhead can push a "small" row count over the byte cap. The `default:` branch in `getExporter` is reachable only if a caller bypasses the Zod boundary (the schema enumerates `csv|json|xlsx`); the `format as string` cast in the error message is intentional — we want the actual value the caller passed to surface in logs, not the post-narrowing `never`. `provider.fetch` errors are wrapped rather than thrown raw because the route layer needs to distinguish a *bad provider* (502-class) from a *bad request* (400-class), and the `ExportError` code is the contract that drives that branching upstream.
|
||||
The two guards run in a specific order: row count is checked _before_ the exporter runs so a runaway provider doesn't burn CPU encoding fields the route layer is about to reject. File size is checked _after_ encoding because cell-level transforms and XML/JSON overhead can push a "small" row count over the byte cap. The `default:` branch in `getExporter` is reachable only if a caller bypasses the Zod boundary (the schema enumerates `csv|json|xlsx`); the `format as string` cast in the error message is intentional — we want the actual value the caller passed to surface in logs, not the post-narrowing `never`. `provider.fetch` errors are wrapped rather than thrown raw because the route layer needs to distinguish a _bad provider_ (502-class) from a _bad request_ (400-class), and the `ExportError` code is the contract that drives that branching upstream.
|
||||
|
||||
@@ -41,4 +41,4 @@ providers.set("orders", myOrderProvider);
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The augmentation pattern is identical to `@suaveplan/api-plugin-database`'s `registry-augmentation.ts` — both files exist purely to add slots to the shared registry interface defined by `@suaveplan/api-plugin/service-registry`. Without this file, every call site would have to cast the `getService("exportProviders")` return value, defeating the type safety that the `ServiceRegistry` interface was designed to provide. The two slots cover the two distinct concerns the plugin owns: the *registry* of data providers (writable by every consumer that wants to expose a resource for export) and the *store* of jobs (mutated by the route layer, observable by anyone tracking export progress). Exposing both is a deliberate departure from upstream, which exposed them via `ctx.registerService(...)` at runtime but never declared the corresponding types.
|
||||
The augmentation pattern is identical to `@suaveplan/api-plugin-database`'s `registry-augmentation.ts` — both files exist purely to add slots to the shared registry interface defined by `@suaveplan/api-plugin/service-registry`. Without this file, every call site would have to cast the `getService("exportProviders")` return value, defeating the type safety that the `ServiceRegistry` interface was designed to provide. The two slots cover the two distinct concerns the plugin owns: the _registry_ of data providers (writable by every consumer that wants to expose a resource for export) and the _store_ of jobs (mutated by the route layer, observable by anyone tracking export progress). Exposing both is a deliberate departure from upstream, which exposed them via `ctx.registerService(...)` at runtime but never declared the corresponding types.
|
||||
|
||||
@@ -97,4 +97,3 @@ await api.start();
|
||||
- [`schemas/routes/initiate`](./src/schemas/routes/initiate.md) — chunked-init request/response.
|
||||
- [`schemas/routes/status`](./src/schemas/routes/status.md) — chunk / complete / status / abort schemas.
|
||||
- [`types/types`](./src/types/types.md) — public data model.
|
||||
|
||||
|
||||
@@ -105,4 +105,3 @@ This package is functional-parity with upstream `@suaveplan/api-plugin-graphql`
|
||||
## License
|
||||
|
||||
MIT.
|
||||
|
||||
|
||||
@@ -138,4 +138,3 @@ upstream import success/failure).
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -106,4 +106,3 @@ const service = new ApnService({ config, transport });
|
||||
|
||||
See `src/transport/apn-transport/apn-transport.md` for the full
|
||||
contract.
|
||||
|
||||
|
||||
@@ -157,4 +157,3 @@ const plugin = createPaymentPlugin({
|
||||
- [`./src/otel/instruments.md`](./src/otel/instruments.md) — 5 counters + 1 histogram.
|
||||
- [`./src/error-codes/error-codes.md`](./src/error-codes/error-codes.md) — 15 `PAYMENT_PLUGIN_*` codes.
|
||||
- [`./src/types/types.md`](./src/types/types.md) — `PaymentPluginOptions` / `PaymentPluginConfig`.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# @suaveplan/api-plugin-rate-limit - Rate-limit plugin for @suaveplan/api. Wraps @suaveplan/rate-limiter as a definePlugin descriptor: per-route + per-tenant configuration, parameterised path matcher, IP whitelist, tenant multiplier applied to numeric limit, Hono middleware factory, 429 + Retry-After + X-RateLimit-* / RateLimit-* headers, OTel counters, RFC 7807 Problem+JSON body. Registers `rateLimitService` in the host ServiceRegistry.
|
||||
# @suaveplan/api-plugin-rate-limit - Rate-limit plugin for @suaveplan/api. Wraps @suaveplan/rate-limiter as a definePlugin descriptor: per-route + per-tenant configuration, parameterised path matcher, IP whitelist, tenant multiplier applied to numeric limit, Hono middleware factory, 429 + Retry-After + X-RateLimit-*/ RateLimit-* headers, OTel counters, RFC 7807 Problem+JSON body. Registers `rateLimitService` in the host ServiceRegistry.
|
||||
|
||||
Rate-limit plugin for `@suaveplan/api`. Wraps `@suaveplan/rate-limiter`
|
||||
as a `definePlugin` descriptor exposing per-route + per-tenant
|
||||
@@ -130,4 +130,3 @@ readiness).
|
||||
## Status
|
||||
|
||||
`0.1.0` — initial ship. Surface is final for the 0.1.x line.
|
||||
|
||||
|
||||
@@ -107,10 +107,9 @@ await api.start();
|
||||
|
||||
## Status
|
||||
|
||||
`0.1.0` — first migration wave shipping` with full upstream
|
||||
`0.1.0` — first migration wave shipping`with full upstream
|
||||
parity. Persistent backends (`SearchAdapter` implementations against
|
||||
SurrealDB FTS / Typesense / Meilisearch), tenant scoping, auth gates,
|
||||
rate limiting, and the enterprise-audit §4 enhancements (vector search,
|
||||
synonyms, query DSL, saved searches, learning-to-rank) are tracked as
|
||||
follow-up changes.
|
||||
|
||||
|
||||
@@ -98,4 +98,3 @@ The site plugin ships **zero runtime dependencies** on `@suaveplan/api-plugin-ra
|
||||
- See [`docs/middleware-integration.md`](./docs/middleware-integration.md) for the canonical end-to-end wiring example and the recommended layering order (audit OUTERMOST → rate-limit MIDDLE → site routes INNERMOST).
|
||||
- See [`src/integration/middleware-recommendations.md`](./src/integration/middleware-recommendations.md) for the `MIDDLEWARE_RECOMMENDATIONS` constant exposing recommended rate-limit windows and audit categories per route group.
|
||||
- The pattern is proven by [`src/integration/middleware-composition.integration.test.ts`](./src/integration/middleware-composition.integration.test.ts) — a runnable composition asserting site behaviour, rate-limit headers, and audit event emission.
|
||||
|
||||
|
||||
@@ -106,4 +106,3 @@ This package follows the canonical API-plugin pattern pioneered by `@suaveplan/a
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -118,4 +118,3 @@ const app = defineApp({
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -200,4 +200,3 @@ The plugin emits three counters:
|
||||
Counters bind lazily to the global `MeterProvider`; tests
|
||||
re-bind by calling `resetUserInstruments()` after swapping the
|
||||
provider.
|
||||
|
||||
|
||||
@@ -163,4 +163,3 @@ Operators can:
|
||||
- [`services/registry-augmentation`](./src/services/registry-augmentation.md)
|
||||
— typed `ServiceRegistry` slot.
|
||||
- [`types/types`](./src/types/types.md) — public TS surface.
|
||||
|
||||
|
||||
@@ -118,4 +118,3 @@ Status label values: `delivered`, `exhausted`, `not_found`, `inactive`, `invalid
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ ctx.getService("nope"); // type error — not augmented
|
||||
|
||||
`PLUGIN_CODES` defines ten codes (`PLUGIN.NOT_FOUND`, `PLUGIN.ALREADY_REGISTERED`, `PLUGIN.INIT_FAILED`, `PLUGIN.DEP_MISSING`, `PLUGIN.CIRCULAR_DEPENDENCY`, `PLUGIN.INIT_TIMEOUT`, `PLUGIN.SHUTDOWN_FAILED`, `PLUGIN.INVALID_CONFIG`, `PLUGIN.HOOK_FAILED`, `PLUGIN.ABORTED`). Every code is raised by at least one production call site — the spec-conformance test pins this invariant.
|
||||
|
||||
## design decisions
|
||||
## design decisions
|
||||
|
||||
- **Unified lifecycle runtime** — `PluginRegistry` calls `onRegister`/`onInit`/`onReady`/`onClose` directly; the `HookExecutor` rejects registration of these four names. Upstream's dual-runtime hazard is eliminated by construction.
|
||||
- **`initTimeout`/`shutdownTimeout` are enforced** — every hook runs with an `AbortController`-bound `ctx.signal` that fires when the budget is exceeded.
|
||||
@@ -116,4 +116,3 @@ ctx.getService("nope"); // type error — not augmented
|
||||
- **Topological sort is deterministic** — alphabetical tie-break for equal-in-degree nodes makes boot logs reproducible across runs.
|
||||
- **No `applyMigrations`** — migrations live in `@suaveplan/api-migrations`. The plugin registry knows nothing about databases.
|
||||
- **No ALS singletons** (`getCurrentApp`, etc.). Callers thread `ctx` explicitly.
|
||||
|
||||
|
||||
@@ -98,4 +98,3 @@ bunx --bun turbo lint typecheck test:coverage --filter=@suaveplan/api-rate-limit
|
||||
```
|
||||
|
||||
100% statements/branches/functions/lines (Istanbul).
|
||||
|
||||
|
||||
@@ -82,4 +82,3 @@ Reason label values: `"missing-scope" | "rule-deny" | "no-principal"`.
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -59,4 +59,3 @@ await slot.dispose(draining.slotId, { timeoutMs: 1_000 });
|
||||
## Version
|
||||
|
||||
`0.1.0` — initial Stage-4 ship. Graduates to `1.0.0` when the plugin system as a whole is declared production-ready.
|
||||
|
||||
|
||||
@@ -98,12 +98,12 @@ r.registerPipelineRoute({
|
||||
## API Reference
|
||||
|
||||
- `class PipelineRouteTable` —
|
||||
- `register(reg: HonoPipelineRegistration): void`
|
||||
- `unregister(method: string, path: string): void`
|
||||
- `unregisterPlugin(plugin: string): void`
|
||||
- `list(): readonly HonoPipelineRoute[]`
|
||||
- `match(method: string, url: string): HonoPipelineRoute | null`
|
||||
- `size(): number`
|
||||
- `register(reg: HonoPipelineRegistration): void`
|
||||
- `unregister(method: string, path: string): void`
|
||||
- `unregisterPlugin(plugin: string): void`
|
||||
- `list(): readonly HonoPipelineRoute[]`
|
||||
- `match(method: string, url: string): HonoPipelineRoute | null`
|
||||
- `size(): number`
|
||||
- `interface HonoPipelineRoute` — `{ plugin, method, path, options, handler, params, statusMap }`. Structurally compatible with `@suaveplan/api-types/pipeline-router`'s `PipelineRoute`.
|
||||
- `interface HonoPipelineRegistration` — `{ plugin, method, path, handler, options?, statusMap? }`.
|
||||
- `interface HonoPipelineRouteOptions` — narrow subset of the host's `PipelineRouteOptions`: `auth`, `csrf`, `streaming`, `idempotent`, `runtimeValidation`, `auditable`, `rateLimit`, `request`, `responses`.
|
||||
|
||||
@@ -71,4 +71,3 @@ await slot.dispose(draining.slotId, { timeoutMs: 100 });
|
||||
- Foundation tests that need a real `Router`/`PipelineRouter` without a Hono peer.
|
||||
- Plugin tests that previously hand-rolled their own router shim (`api-router-hono` carries the Hono peer-dep cost).
|
||||
- The default `router: "in-memory"` slot in the Stage-5 `@suaveplan/api-host/testing` adapter table.
|
||||
|
||||
|
||||
@@ -75,4 +75,3 @@ const { dispatch } = createMainBridge(channel.port1 as never);
|
||||
const res = await dispatch(new Request("https://api.local/world"));
|
||||
console.log(await res.text()); // "hi /world"
|
||||
```
|
||||
|
||||
|
||||
@@ -95,4 +95,3 @@ import { runRouterConformance } from "@suaveplan/api-router/testing";
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ values via wrapping responses and inspecting the request shape.
|
||||
list; order is preserved.
|
||||
- `next()` enforcement: a middleware that calls `next()` twice in a row
|
||||
has its dispatch rejected. (Multiple sequential `next()` calls across
|
||||
*different* middlewares are correct; only same-middleware repeats fail.)
|
||||
_different_ middlewares are correct; only same-middleware repeats fail.)
|
||||
- Synchronous return values are accepted (wrapped in `Promise.resolve`).
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
@@ -72,7 +72,7 @@ isolated copies and never accidentally share state across branches. The
|
||||
runtime cost is one object-spread per call; modern V8 inlines this away
|
||||
for short builder chains.
|
||||
|
||||
The `body` setter is typed as a *re-parameterising* method — calling
|
||||
The `body` setter is typed as a _re-parameterising_ method — calling
|
||||
`builder.body<T>(value)` returns a `ResponseBuilder<T>`, picking up the
|
||||
new body type. This lets adapters call `.body(zodValidatedShape)` and the
|
||||
inferred `RouterResponse.body` lines up with the validation output. The
|
||||
|
||||
@@ -104,7 +104,7 @@ keeps the steps-7-8 insertion contract honest: middleware only enters
|
||||
through `use()`, and only at the post-validation boundary when supplied
|
||||
by plugins.
|
||||
|
||||
Collision detection compares the *compiled* path key (`":id"` vs.
|
||||
Collision detection compares the _compiled_ path key (`":id"` vs.
|
||||
`":userId"` both normalise to `:`-form). Adapters that need parameter-name
|
||||
preservation can introspect `RouteMatch.path` for the originally registered
|
||||
form.
|
||||
|
||||
@@ -90,8 +90,8 @@ runPipelineRouterConformance({
|
||||
## API Reference
|
||||
|
||||
- `interface PipelineRouterConformanceFactory` —
|
||||
- `label: string`
|
||||
- `build(): { router: PipelineRouter; register: (reg: PipelineRouteRegistration) => void; reload?: () => void }`
|
||||
- `label: string`
|
||||
- `build(): { router: PipelineRouter; register: (reg: PipelineRouteRegistration) => void; reload?: () => void }`
|
||||
- `interface PipelineRouteRegistration` — `{ plugin, method, path, handler, options?, statusMap? }`.
|
||||
- `function runPipelineRouterConformance(factory): void` — declares one
|
||||
`describe()` block with six required `it()` cases.
|
||||
|
||||
@@ -84,4 +84,3 @@ participant whose `actions[name]` is missing fails at install with
|
||||
`0.1.0` — bottom-up rebuild stage. Will graduate to `1.0.0` per the
|
||||
foundation umbrella's Stage 6 acceptance gate (`a multi-step saga in
|
||||
api-plugin-example runs end-to-end including a compensation path`).
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ router.post("/webhook/saga", async (req) => {
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The emitter validates *outbound* payloads before pushing them onto the
|
||||
The emitter validates _outbound_ payloads before pushing them onto the
|
||||
bus. This is deliberate — `bus.emit` is fire-and-forget; if the
|
||||
emitter trusted the caller and pushed a bad payload, every listener
|
||||
would see corrupt data with no callback to recover. By failing at the
|
||||
|
||||
@@ -96,4 +96,3 @@ if (result.outcome === "deny") {
|
||||
Runtime: `@suaveplan/api-types`, `@suaveplan/error`, `@suaveplan/types`,
|
||||
`@suaveplan/opentelemetry`, `@opentelemetry/api`, `tslib`, `zod`. No
|
||||
`@suaveplan/api-*` runtime beyond `api-types`.
|
||||
|
||||
|
||||
@@ -92,4 +92,3 @@ lifecycle delegation (`LIFECYCLE_FAILED`).
|
||||
the spec mandates: route registration, request round-trip, idempotent
|
||||
lifecycle, error propagation with `cause` chain, and capability
|
||||
passthrough including denied capability surfacing `SAND_PERM.DENIED`.
|
||||
|
||||
|
||||
@@ -89,4 +89,3 @@ import { runVmSandboxConformance } from "@suaveplan/api-sandbox-vm/testing";
|
||||
|
||||
runVmSandboxConformance("my-host", makeMyHandle);
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# @suaveplan/api-sandbox-wasm-host-bridge - Host bridge wrapping a @suaveplan/api-sandbox-wasm WasmSandboxHandle as a @suaveplan/api-host PluginModule. Validates the WASM ABI surface (__suaveplan_alloc / free / handle_request / routes / start / dispose + memory) at bridge-init, marshals SerializedRequest bytes into linear memory via __suaveplan_alloc, invokes __suaveplan_handle_request, reads the packed (responsePtr, responseLen) result, JSON-decodes the SerializedResponse, and unmarshals it back to a host Response. Traps surface as SAND_WASM_BRIDGE.HANDLER_TRAPPED with classified trapKind; denied capabilities surface as SAND_PERM.DENIED.
|
||||
# @suaveplan/api-sandbox-wasm-host-bridge - Host bridge wrapping a @suaveplan/api-sandbox-wasm WasmSandboxHandle as a @suaveplan/api-host PluginModule. Validates the WASM ABI surface (__suaveplan_alloc / free / handle_request / routes / start / dispose + memory) at bridge-init, marshals SerializedRequest bytes into linear memory via__suaveplan_alloc, invokes __suaveplan_handle_request, reads the packed (responsePtr, responseLen) result, JSON-decodes the SerializedResponse, and unmarshals it back to a host Response. Traps surface as SAND_WASM_BRIDGE.HANDLER_TRAPPED with classified trapKind; denied capabilities surface as SAND_PERM.DENIED.
|
||||
|
||||
Host-integration layer that wraps a `@suaveplan/api-sandbox-wasm` `WasmSandboxHandle` as a `@suaveplan/api-host` `PluginModule`. Sandboxed-tier plugins compiled to WebAssembly export the `__suaveplan_*` ABI surface (`memory`, `__suaveplan_alloc`, `__suaveplan_free`, `__suaveplan_handle_request`, `__suaveplan_routes`, `__suaveplan_start`, `__suaveplan_dispose`); this bridge validates that surface, discovers the guest's routes once at bridge-init time, and emits per-route closures that marshal `SerializedRequest` records through the guest's linear memory.
|
||||
|
||||
|
||||
@@ -15,18 +15,19 @@ The split from `bridge.ts` is structural rather than mechanical: every
|
||||
direct-variant import (`abi-direct/wasm-abi`, `abi-direct/memory`,
|
||||
`abi-direct/marshal`) is referenced from this file only, so a future
|
||||
removal of the direct variant collapses to deleting `bridge-direct.ts`
|
||||
|
||||
+ `abi-direct/` and trimming the dispatcher.
|
||||
|
||||
## Features
|
||||
|
||||
- `buildDirectInstall(options)` — accepts a `DirectVariantOptions`,
|
||||
+ `buildDirectInstall(options)` — accepts a `DirectVariantOptions`,
|
||||
returns the bridge's `SandboxedWasmPluginInstall`.
|
||||
- Trap classification, capability-gate routing through
|
||||
+ Trap classification, capability-gate routing through
|
||||
`SAND_PERM.DENIED`, idempotent lifecycle — preserved exactly from
|
||||
the shipped contract.
|
||||
- Instrument registry plumbed with `abi_variant: "direct-i32-i64"` on
|
||||
+ Instrument registry plumbed with `abi_variant: "direct-i32-i64"` on
|
||||
every counter and histogram emission.
|
||||
- Route closures are pure functions of the manifest + ABI exports;
|
||||
+ Route closures are pure functions of the manifest + ABI exports;
|
||||
re-instantiating the bridge against the same sandbox produces
|
||||
identical `PipelineRoute[]` records.
|
||||
|
||||
@@ -81,7 +82,7 @@ try {
|
||||
Internal entrypoint called by the dispatcher. Public consumers should
|
||||
use `createSandboxedWasmPluginModule` from the package root.
|
||||
|
||||
- `options: DirectVariantOptions` — `sandbox`, `manifest`, optional
|
||||
+ `options: DirectVariantOptions` — `sandbox`, `manifest`, optional
|
||||
`instruments`.
|
||||
|
||||
Throws `SandboxWasmBridgeError` with code `INVALID_PLUGIN_SURFACE` if
|
||||
@@ -89,14 +90,14 @@ ABI validation fails BEFORE any guest call.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The function never touches the sandbox's `instance` directly past
|
||||
+ The function never touches the sandbox's `instance` directly past
|
||||
the ABI validator — the typed `WasmAbiExports` view is the single
|
||||
hand-off point.
|
||||
- `freeBuffer` is called in a `finally` block so the request pointer
|
||||
+ `freeBuffer` is called in a `finally` block so the request pointer
|
||||
is released even when the response unmarshal throws. The response
|
||||
pointer is released BEFORE unmarshal runs because Zod parsing may
|
||||
allocate additional bytes that would shadow the original buffer.
|
||||
- Capability denials surface through the canonical
|
||||
+ Capability denials surface through the canonical
|
||||
`SAND_PERM.DENIED` code, not the bridge-local `CAPABILITY_DENIED`.
|
||||
Operators see one code for "permission denied" regardless of which
|
||||
bridge raised it.
|
||||
|
||||
@@ -98,4 +98,3 @@ bunx turbo lint typecheck test:coverage --filter=@suaveplan/api-sandbox-wasm
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -26,7 +26,9 @@ Shared Zod schemas describing Suaveplan API request/response shapes. The single
|
||||
- [metadata](./src/events/metadata.md) — Defines the `EventMetadata` Zod schema for the distributed tracing and actor context attached to every domain event in…
|
||||
- [helpers](./src/helpers/to-suaveplan-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [to-suaveplan-error](./src/helpers/to-suaveplan-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
|
||||
<!-- end:auto-docs --> - [to-suaveplan-error](./src/helpers/to-suaveplan-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [to-suaveplan-error](./src/helpers/to-suaveplan-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [to-suaveplan-error](./src/helpers/to-suaveplan-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [to-suaveplan-error](./src/helpers/to-suaveplan-error.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Description |
|
||||
|
||||
@@ -133,4 +133,3 @@ unchanged — see `src/adapter/adapter.spec-conformance.test.ts`.
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user