@happyvertical/smrt-app-runtime 0.53.2 → 0.53.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/AGENTS.md +40 -0
- package/README.md +101 -0
- package/dist/application-identity.d.ts +43 -0
- package/dist/application-identity.d.ts.map +1 -0
- package/dist/chunks/src-DZGOVGsU.js +2258 -0
- package/dist/chunks/src-DZGOVGsU.js.map +1 -0
- package/dist/deployed-runtime.d.ts +11 -0
- package/dist/deployed-runtime.d.ts.map +1 -1
- package/dist/error-code.d.ts +3 -0
- package/dist/error-code.d.ts.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1831
- package/dist/manifest.json +1 -1
- package/dist/operation-lock.d.ts +28 -0
- package/dist/operation-lock.d.ts.map +1 -0
- package/dist/provider-readiness.d.ts +48 -0
- package/dist/provider-readiness.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +9 -6
- package/dist/stale-reclaim.d.ts +33 -0
- package/dist/stale-reclaim.d.ts.map +1 -0
- package/dist/state-root.d.ts +42 -0
- package/dist/state-root.d.ts.map +1 -0
- package/dist/sveltekit/handlers.d.ts +83 -0
- package/dist/sveltekit/handlers.d.ts.map +1 -0
- package/dist/sveltekit/index.d.ts +20 -0
- package/dist/sveltekit/index.d.ts.map +1 -0
- package/dist/sveltekit/owner-setup.d.ts +102 -0
- package/dist/sveltekit/owner-setup.d.ts.map +1 -0
- package/dist/sveltekit/runtime.d.ts +277 -0
- package/dist/sveltekit/runtime.d.ts.map +1 -0
- package/dist/sveltekit/tenancy.d.ts +59 -0
- package/dist/sveltekit/tenancy.d.ts.map +1 -0
- package/dist/sveltekit.d.ts +2 -0
- package/dist/sveltekit.d.ts.map +1 -0
- package/dist/sveltekit.js +704 -0
- package/dist/sveltekit.js.map +1 -0
- package/dist/writer-lease.d.ts +36 -0
- package/dist/writer-lease.d.ts.map +1 -0
- package/package.json +22 -8
- package/dist/index.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -68,6 +68,46 @@ Application infrastructure composition for the validated runtime profiles in
|
|
|
68
68
|
- Cloud must keep required tenant context and must never introduce a root or
|
|
69
69
|
unscoped tenant fallback. RLS remains an explicit deployment/migration choice.
|
|
70
70
|
|
|
71
|
+
## SvelteKit entry (`./sveltekit`)
|
|
72
|
+
|
|
73
|
+
- Server-only subpath; `@sveltejs/kit` is an optional peer and is externalized
|
|
74
|
+
in `vite.config.ts` (kit recognizes `redirect`/`fail` by class identity).
|
|
75
|
+
The root entry must never import kit or Svelte.
|
|
76
|
+
- `createSmrtSvelteKitRuntime()` order is fixed: readiness gate → URL tenant
|
|
77
|
+
selection (`locals.selectedTenant*`, never tenant context, headers ignored) →
|
|
78
|
+
verified session (tenant context entered only when
|
|
79
|
+
`isSessionTenantAuthorized()` accepts it, else unauthenticated; never via
|
|
80
|
+
`createSessionHandler`, which re-runs `resolve` on downstream errors) →
|
|
81
|
+
publish `locals.tenantContext` only for that verified tenant.
|
|
82
|
+
- `database-rls` isolation forces `postgresRls: true`, including on
|
|
83
|
+
`skipPaths` (anonymous principal). `resolve` runs at most once;
|
|
84
|
+
session-layer failure before it is a 500 with cleared locals.
|
|
85
|
+
- In an RLS request `classOptions()`/`databaseConfig()` return the
|
|
86
|
+
transaction-bound request db (class `db` overrides win). Anything that
|
|
87
|
+
outlives a request (session service, tenant selector) must use the base
|
|
88
|
+
config, never these.
|
|
89
|
+
- The writer lease, provider readiness, and onboarding file cleanup are
|
|
90
|
+
injected hooks. Apps pass the root entry's `acquireWriterLease` (over
|
|
91
|
+
`prepareApplicationStateRoot`) and `createProviderReadinessProbe`; deployed
|
|
92
|
+
startup fails closed without `providerReadiness`.
|
|
93
|
+
- Owner setup re-checks loopback peer and loopback URL host per request and
|
|
94
|
+
returns only fixed `{ code, message }` failures.
|
|
95
|
+
- `resolveApplicationId()` / `runtimeConfigurationFingerprint()` must stay
|
|
96
|
+
byte-compatible with process managers (golden vectors in tests).
|
|
97
|
+
|
|
98
|
+
## Operator state (root entry)
|
|
99
|
+
|
|
100
|
+
- One owner for the state shared by the web process and `smrt app`
|
|
101
|
+
(`@happyvertical/smrt-cli/app` re-exports these): `resolveApplicationStateRoot`
|
|
102
|
+
/ `prepareApplicationStateRoot` (private app-bound mode-0700 root and
|
|
103
|
+
marker), `withOperationLock` (`operation.lock`), `acquireWriterLease` /
|
|
104
|
+
`readActiveWriterLease` (`writer.lease`), `reclaimStaleRecord`, and
|
|
105
|
+
`createProviderReadinessProbe` (`SMRT_*_READINESS_MODULE`, resolved from the
|
|
106
|
+
app root with Node's own ESM rules). File names, record formats, and
|
|
107
|
+
messages are a cross-process contract; never change them unilaterally.
|
|
108
|
+
- Tests that hook `node:fs` interleavings must live in this package: from a
|
|
109
|
+
consumer the package is an externalized dependency a mock cannot reach.
|
|
110
|
+
|
|
71
111
|
## Public runtime diagnostics
|
|
72
112
|
|
|
73
113
|
- `projectRuntimeDiagnostics()` is the only public diagnostic projection. It
|
package/README.md
CHANGED
|
@@ -102,6 +102,107 @@ explicitly enabled. With embedded job topology, `createEmbeddedJobRunner()`
|
|
|
102
102
|
returns the normal s-m-r-t `TaskRunner`, so the application keeps one enqueue
|
|
103
103
|
and execution contract without requiring a separate worker service.
|
|
104
104
|
|
|
105
|
+
## SvelteKit entry
|
|
106
|
+
|
|
107
|
+
`@happyvertical/smrt-app-runtime/sveltekit` composes the profile runtime,
|
|
108
|
+
tenant selection, the signed session, and authorized-tenant locals so an app's
|
|
109
|
+
server hooks are a few lines. `@sveltejs/kit` is an optional peer dependency;
|
|
110
|
+
the root entry never imports it.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
// src/hooks.server.ts
|
|
114
|
+
import { createSmrtSvelteKitRuntime } from '@happyvertical/smrt-app-runtime/sveltekit';
|
|
115
|
+
|
|
116
|
+
export const runtime = createSmrtSvelteKitRuntime({
|
|
117
|
+
prepareDatabase: runApplicationMigrations, // optional, idempotent
|
|
118
|
+
});
|
|
119
|
+
export const { handle, init } = runtime;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
// src/app.d.ts
|
|
124
|
+
import type { SmrtRuntimeLocals } from '@happyvertical/smrt-app-runtime/sveltekit';
|
|
125
|
+
declare global {
|
|
126
|
+
namespace App {
|
|
127
|
+
interface Locals extends SmrtRuntimeLocals {}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`init` is the fail-closed startup gate (local runtime or deployed bindings) and
|
|
133
|
+
`handle` waits for it, then runs, in order: URL tenant selection into
|
|
134
|
+
`locals.selectedTenant*` (never tenant context; tenant headers are ignored),
|
|
135
|
+
the signed session, and publication of `locals.tenantContext` only when the
|
|
136
|
+
active context matches the verified session tenant. A session's tenant is
|
|
137
|
+
accepted only for an active direct membership or a legitimately inherited
|
|
138
|
+
one (`isSessionTenantAuthorized()`, the same rule deployed `restoreSession()`
|
|
139
|
+
applies); otherwise the request is unauthenticated (no user, permissions,
|
|
140
|
+
tenant, or tenant context). `database-rls` isolation always runs the request
|
|
141
|
+
RLS transaction (`session.postgresRls` cannot disable it); a
|
|
142
|
+
`session.skipPaths` prefix skips session loading only and still runs inside
|
|
143
|
+
that transaction as an anonymous principal. During such a request
|
|
144
|
+
`runtime.classOptions()` / `runtime.databaseConfig()` return the
|
|
145
|
+
transaction-bound request database (unless the class has its own `db`
|
|
146
|
+
override), so call them per request and never retain the result. Downstream code runs
|
|
147
|
+
at most once: its error propagates unchanged, and a session-layer failure
|
|
148
|
+
before it returns 500 with no authenticated locals. Defaults read `SMRT_APP_ID`, `SMRT_DATA_DIR`, `HOST`, `DATABASE_URL`,
|
|
149
|
+
`TENANT_BASE_DOMAIN`, and `SMRT_BACKGROUND_JOBS`; `smrt.config` `runtime`
|
|
150
|
+
selects the profile (local when absent). Deployed profiles additionally require
|
|
151
|
+
`providerReadiness` probes and fail closed without them. Optional hooks:
|
|
152
|
+
`acquireWriterLease` (local single-writer lease), `onBootstrapInvitation`
|
|
153
|
+
(present a newly issued setup token), `selectTenant`, `session`, and
|
|
154
|
+
`classOverrides`. `runtime.classOptions(className)` returns collection options
|
|
155
|
+
for application code.
|
|
156
|
+
|
|
157
|
+
Mountable routes:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
// src/routes/api/_runtime/health/+server.ts
|
|
161
|
+
export const GET = createRuntimeHealthHandler(runtime);
|
|
162
|
+
// src/routes/api/_runtime/diagnostics/+server.ts
|
|
163
|
+
export const GET = createRuntimeDiagnosticsHandler({ runtime, toolNames });
|
|
164
|
+
// src/routes/setup/+page.server.ts
|
|
165
|
+
export const { load, actions } = createOwnerSetupPage(runtime);
|
|
166
|
+
// src/routes/+layout.server.ts
|
|
167
|
+
export const load = createSessionLayoutLoad();
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Diagnostics authorize (owner role or `runtime_diagnostics.read` on an active,
|
|
171
|
+
session-matching membership) before reading the runtime and return only stable
|
|
172
|
+
`{ schemaVersion: 1, error: { code } }` failures. Owner setup is local-only and
|
|
173
|
+
re-checks on every request that both the peer address and the URL host are
|
|
174
|
+
loopback; its `default` action reads `token`, `name`, `email`, and optional
|
|
175
|
+
`tenantName`, sets the session cookie, and redirects 303, or returns
|
|
176
|
+
`fail(status, { code, message })` with `setup_disabled` (404),
|
|
177
|
+
`setup_unavailable` (403), `setup_invalid_input` (400), or `setup_invalid`
|
|
178
|
+
(400). Claim error text is never returned.
|
|
179
|
+
|
|
180
|
+
`resolveApplicationId()` and `runtimeConfigurationFingerprint()` (root entry)
|
|
181
|
+
are the canonical app ID and secret-free configuration fingerprint shared by
|
|
182
|
+
the web health route and process managers.
|
|
183
|
+
|
|
184
|
+
The root entry also owns the operator state shared with `smrt app`:
|
|
185
|
+
`prepareApplicationStateRoot()` (private, app-bound state directory),
|
|
186
|
+
`withOperationLock()`, `acquireWriterLease()` / `readActiveWriterLease()`,
|
|
187
|
+
and `createProviderReadinessProbe()`. A SvelteKit app wires the last two into
|
|
188
|
+
its runtime without depending on the CLI:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import {
|
|
192
|
+
acquireWriterLease,
|
|
193
|
+
createProviderReadinessProbe,
|
|
194
|
+
prepareApplicationStateRoot,
|
|
195
|
+
} from '@happyvertical/smrt-app-runtime';
|
|
196
|
+
|
|
197
|
+
export const runtime = createSmrtSvelteKitRuntime({
|
|
198
|
+
acquireWriterLease: (context) =>
|
|
199
|
+
acquireWriterLease(prepareApplicationStateRoot(context), {
|
|
200
|
+
operationInstance: process.env.SMRT_OPERATION_INSTANCE,
|
|
201
|
+
}),
|
|
202
|
+
providerReadiness: createProviderReadinessProbe,
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
105
206
|
## Self-hosted and cloud applications
|
|
106
207
|
|
|
107
208
|
The deployed initializer validates the selected profile against concrete,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical application identity shared by the web process, operator commands,
|
|
3
|
+
* and generated operational surfaces.
|
|
4
|
+
*
|
|
5
|
+
* Every process that manages the same application must derive the same app ID
|
|
6
|
+
* and the same configuration fingerprint, so these helpers live next to
|
|
7
|
+
* `validateApplicationId()` / `encodeApplicationId()` rather than in copied
|
|
8
|
+
* template scripts.
|
|
9
|
+
*/
|
|
10
|
+
export interface ResolveApplicationIdOptions {
|
|
11
|
+
/** Directory containing the application's `package.json`. Defaults to cwd. */
|
|
12
|
+
sourceRoot?: string;
|
|
13
|
+
/** Package name to encode instead of reading `package.json`. */
|
|
14
|
+
packageName?: string;
|
|
15
|
+
/** Explicit, strictly validated app ID (for example `SMRT_APP_ID`). */
|
|
16
|
+
explicitId?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolve one stable application identity.
|
|
20
|
+
*
|
|
21
|
+
* An explicit ID is validated with {@link validateApplicationId}; otherwise
|
|
22
|
+
* the package name is encoded with {@link encodeApplicationId}.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveApplicationId(options?: ResolveApplicationIdOptions): string;
|
|
25
|
+
/**
|
|
26
|
+
* Runtime selection the fingerprint covers. Structural so operator tooling
|
|
27
|
+
* can fingerprint a resolved runtime it loaded itself; a
|
|
28
|
+
* {@link ResolvedApplicationRuntime} always satisfies it.
|
|
29
|
+
*/
|
|
30
|
+
export interface FingerprintRuntime {
|
|
31
|
+
readonly profile: string;
|
|
32
|
+
readonly providers: unknown;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Secret-safe identity used to reject stale managed processes after profile,
|
|
36
|
+
* provider, database-target, or listener configuration changes.
|
|
37
|
+
*
|
|
38
|
+
* Credentials, query strings, and fragments of `DATABASE_URL` are excluded;
|
|
39
|
+
* only `sslmode` survives. The digest is stable for identical inputs so a
|
|
40
|
+
* process manager and the web process can compare it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function runtimeConfigurationFingerprint(runtime: FingerprintRuntime, environment?: Readonly<Record<string, string | undefined>>): string;
|
|
43
|
+
//# sourceMappingURL=application-identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"application-identity.d.ts","sourceRoot":"","sources":["../src/application-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,MAAM,WAAW,2BAA2B;IAC1C,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,2BAAgC,GACxC,MAAM,CAeR;AAkBD;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,kBAAkB,EAC3B,WAAW,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAe,GACtE,MAAM,CAyBR"}
|