@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/dist/manifest.json
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exclusive operator-operation lock (`<state>/operation.lock`).
|
|
3
|
+
*
|
|
4
|
+
* Ported from the template's `scripts/smrt-operation-lock.mjs`: the same
|
|
5
|
+
* file, record shape, and messages. Stale-owner reclamation is serialized
|
|
6
|
+
* through `stale-reclaim.ts` so it can never remove a live lock.
|
|
7
|
+
*/
|
|
8
|
+
/** Held lock handed to the operation callback. */
|
|
9
|
+
export interface OperationLock {
|
|
10
|
+
/** Per-acquisition nonce; a managed writer presents it to join the operation. */
|
|
11
|
+
readonly instance: string;
|
|
12
|
+
/** Lock file path. */
|
|
13
|
+
readonly path: string;
|
|
14
|
+
}
|
|
15
|
+
/** True when `pid` names a live process, including one owned by another user. */
|
|
16
|
+
export declare function processExists(pid: number): boolean;
|
|
17
|
+
/** Owner pid of a lock record, or `null` when it cannot be verified. */
|
|
18
|
+
export declare function lockOwnerPid(contents: string): number | null;
|
|
19
|
+
/** True when an operation-lock record names an owner that no longer exists. */
|
|
20
|
+
export declare function isStaleOperationLock(contents: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Run `callback` while holding the application's exclusive operation lock.
|
|
23
|
+
*
|
|
24
|
+
* A lock whose owner process is gone is reclaimed; an unverifiable lock fails
|
|
25
|
+
* closed. The lock is only removed when it is still this acquisition's own.
|
|
26
|
+
*/
|
|
27
|
+
export declare function withOperationLock<T>(stateRoot: string, operation: string, callback: (lock: OperationLock) => Promise<T> | T): Promise<T>;
|
|
28
|
+
//# sourceMappingURL=operation-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operation-lock.d.ts","sourceRoot":"","sources":["../src/operation-lock.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH,kDAAkD;AAClD,MAAM,WAAW,aAAa;IAC5B,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,sBAAsB;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CASlD;AAED,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS5D;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG9D;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAChD,OAAO,CAAC,CAAC,CAAC,CAiEZ"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-owned readiness probes for deployed profiles.
|
|
3
|
+
*
|
|
4
|
+
* Ported from the template's `scripts/smrt-provider-readiness.mjs`. The
|
|
5
|
+
* module named by the environment setting is resolved from the application
|
|
6
|
+
* (not from this CLI package), because it is the application's installed
|
|
7
|
+
* provider adapter.
|
|
8
|
+
*/
|
|
9
|
+
/** Provider components with a readiness module setting. */
|
|
10
|
+
export type ReadinessComponent = 'authentication' | 'assets' | 'secrets';
|
|
11
|
+
/** Environment variable naming each component's readiness module. */
|
|
12
|
+
export declare const PROVIDER_READINESS_SETTINGS: Readonly<Record<ReadinessComponent, string>>;
|
|
13
|
+
/** Context handed to the provider's probe. */
|
|
14
|
+
export interface ReadinessContext {
|
|
15
|
+
profile: string;
|
|
16
|
+
provider: string;
|
|
17
|
+
}
|
|
18
|
+
/** Options for {@link createProviderReadinessProbe}. */
|
|
19
|
+
export interface ProviderReadinessOptions {
|
|
20
|
+
/** Application root the module is resolved from. Defaults to `process.cwd()`. */
|
|
21
|
+
sourceRoot?: string;
|
|
22
|
+
/** Environment to read the setting from. Defaults to `process.env`. */
|
|
23
|
+
environment?: Record<string, string | undefined>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Locate an installed package the way an ES module in `sourceRoot` would:
|
|
27
|
+
* the nearest `node_modules/<name>` walking up from the application. Unlike
|
|
28
|
+
* CommonJS resolution this never consults `NODE_PATH`, which package-manager
|
|
29
|
+
* bin shims point at the CLI's own dependency store.
|
|
30
|
+
*/
|
|
31
|
+
export declare function findInstalledPackage(sourceRoot: string, packageName: string): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Resolve a readiness module specifier the way the application would.
|
|
34
|
+
*
|
|
35
|
+
* URLs (`data:`, `file:`) are imported as given; absolute paths become file
|
|
36
|
+
* URLs; relative paths resolve against the application root; bare package
|
|
37
|
+
* specifiers resolve through Node's ESM resolver from the application root
|
|
38
|
+
* (as the template's in-app `import()` did) and must land inside the
|
|
39
|
+
* package installed in the application's own `node_modules` chain.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveReadinessModule(specifier: string, sourceRoot: string): Promise<string>;
|
|
42
|
+
/**
|
|
43
|
+
* Build a provider-owned readiness probe. The installed adapter module must
|
|
44
|
+
* export `checkReadiness()` (or a default function) and return `true` or
|
|
45
|
+
* `{ ready: true }` only after checking its real backing service.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createProviderReadinessProbe(component: ReadinessComponent, context: ReadinessContext, options?: ProviderReadinessOptions): () => Promise<void>;
|
|
48
|
+
//# sourceMappingURL=provider-readiness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-readiness.d.ts","sourceRoot":"","sources":["../src/provider-readiness.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,2DAA2D;AAC3D,MAAM,MAAM,kBAAkB,GAAG,gBAAgB,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEzE,qEAAqE;AACrE,eAAO,MAAM,2BAA2B,EAAE,QAAQ,CAChD,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAKlC,CAAC;AAEH,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wDAAwD;AACxD,MAAM,WAAW,wBAAwB;IACvC,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAClD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,MAAM,GAAG,IAAI,CASf;AAgDD;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAwBjB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,kBAAkB,EAC7B,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,wBAA6B,GACrC,MAAM,OAAO,CAAC,IAAI,CAAC,CAkCrB"}
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-app-runtime",
|
|
6
|
-
"packageVersion": "0.53.
|
|
6
|
+
"packageVersion": "0.53.4",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
12
|
-
"agents": "
|
|
10
|
+
"manifest": "f8a14990e08ea950957549cfbbb2be899cd072b001200577acb271db6f16be8c",
|
|
11
|
+
"packageJson": "13238017d8b75b279d6a8c11475de37d708a0b5a6b313d5a120936a11299f4e6",
|
|
12
|
+
"agents": "5c8c37ce0684124b676c28b12ef9bad2eeebfc6a6bf2ecae7cb01289a8fe4616"
|
|
13
13
|
},
|
|
14
14
|
"exports": [
|
|
15
|
-
"."
|
|
15
|
+
".",
|
|
16
|
+
"./sveltekit"
|
|
16
17
|
],
|
|
17
18
|
"dependencies": {
|
|
18
19
|
"@happyvertical/smrt-config": "workspace:*",
|
|
@@ -23,7 +24,9 @@
|
|
|
23
24
|
"@happyvertical/smrt-users": "workspace:*",
|
|
24
25
|
"@happyvertical/sql": "catalog:",
|
|
25
26
|
"@happyvertical/smrt-vitest": "workspace:*",
|
|
27
|
+
"@sveltejs/kit": "^2.20.0",
|
|
26
28
|
"@types/node": "24.13.6",
|
|
29
|
+
"tsx": "^4.23.13",
|
|
27
30
|
"typescript": "6.0.3",
|
|
28
31
|
"vite": "8.1.4",
|
|
29
32
|
"vitest": "4.1.10"
|
|
@@ -63,5 +66,5 @@
|
|
|
63
66
|
"polymorphicAssociations": 0,
|
|
64
67
|
"uuidColumns": 0
|
|
65
68
|
},
|
|
66
|
-
"agentDoc": "# @happyvertical/smrt-app-runtime\n\nApplication infrastructure composition for the validated runtime profiles in\n`@happyvertical/smrt-config`.\n\n## Local profile\n\n- `validateApplicationId()` is the canonical strict validator for explicit app\n IDs. `encodeApplicationId()` derives a length-bounded, collision-resistant\n ID from package names; generated operational surfaces must share these APIs.\n- `initializeLocalApplicationRuntime()` owns user data paths, SQLite tuning,\n local application-secret creation, and the single-use owner bootstrap flow.\n- SQLite is acquired through `@happyvertical/sql`'s explicit\n `{ driver: 'node:sqlite', custody: 'trusted-parent' }` boundary after the\n runtime establishes its user-owned mode-0700 data root.\n- Application migrations are explicit through `prepareDatabase`; runtime never\n creates application model tables implicitly. A rejected `prepareDatabase` is\n normalized into `LocalRuntimeError` with the stable `migration_failed` code\n and the fixed recovery message `MIGRATION_FAILED_MESSAGE` (\"run pnpm\n app:setup and inspect the private migration logs\"). The caller's error text\n is never surfaced — it is retained only as a non-enumerable `cause` for\n private logs — and the same application root stays retryable.\n- Standalone migration commands must call `prepareLocalDatabaseStorage()`\n before opening SQLite so custody is established without creating schema or\n bootstrap records.\n- Read-only/copy/import operator commands must call\n `validateLocalDatabaseStorage()` before opening SQLite; it verifies the\n app-bound marker, source separation, real path chain, ownership, and mode\n without creating or repairing storage.\n- Owner bootstrap creates normal `Person`, `User`, `Tenant`, owner `Role` /\n `Membership`, and `Session` records in one transaction.\n- Bootstrap is loopback-only. Only an HMAC of the short-lived token is stored.\n A token that cannot claim fails a read-only preflight before role/catalog\n seeding (#3323); the claim transaction's conditional UPDATE stays the\n authority for single use, expiry, and concurrency.\n- Background jobs and application-defined paid capabilities are default-off.\n- The embedded runner reuses `TaskRunner`; it is not a second job contract.\n\n## Deployed profiles\n\n- `initializeDeployedApplicationRuntime()` composes only `self-hosted` and\n `cloud`; it rejects local initialization and provider/binding mismatches.\n- Provider bindings own credentials, vendor clients, and readiness checks.\n Runtime diagnostics contain selectors and status only.\n- The database binding must expose a provider-owned `close` callback before\n `connect` can run. The runtime owns that cleanup boundary and runs the\n application's explicit, idempotent `prepareDatabase` hook.\n- A failed startup cleanup throws `DeployedRuntimeCleanupError`; callers retain\n and retry its idempotent `retryCleanup()` boundary until it succeeds.\n- Public authentication, asset storage, and secret bindings are mandatory and\n readiness-checked before startup succeeds.\n- `createTaskWorker()` and `createScheduleWorker()` initialize the normal jobs\n package runners against the shared PostgreSQL database. They are intended for\n separate processes; the web process does not start them automatically. The\n worker process must import the app's compiled registration\n (`.smrt/runtime/register.js`, #3117) first; runners resolve `objectType`\n through the registry and never load application source themselves.\n- `close()` drains in-flight readiness/session/worker initialization and\n serialized runner start/stop operations, stops every runner returned by the\n runtime, and then closes PostgreSQL. Returned runners must not be restarted\n after runtime shutdown begins.\n- A tracked operation may await a re-entrant `close()` request without\n deadlocking; external `close()` callers still await complete cleanup.\n- `health()` is process liveness. `readiness()` probes database/auth/assets/\n secrets; it does not claim that an external worker fleet is running.\n- Database-provider readiness is additive; a PostgreSQL-specific server-version\n probe always gates startup and live readiness.\n- Cloud must keep required tenant context and must never introduce a root or\n unscoped tenant fallback. RLS remains an explicit deployment/migration choice.\n\n## Public runtime diagnostics\n\n- `projectRuntimeDiagnostics()` is the only public diagnostic projection. It\n constructs the schema-version-1 allowlist and never serializes the private\n local or deployed diagnostics objects.\n- The allowlist is limited to active profile/coarse health, schema and\n migration readiness, six fixed capability statuses, a sorted/capped public\n tool inventory plus digest, explicit operational topology differences,\n coarse worker heartbeat state, and eight stable code/timestamp errors.\n- Callers supply an explicit clock and worker-heartbeat seam. A missing\n heartbeat is `unknown`; web-process liveness never proves worker liveness.\n- Error ingestion maps unknown codes to `runtime_error`, truncates timestamps\n to the minute, and ignores messages, stacks, logs, records, and nested state.\n- Authentication and diagnostics-read authorization belong before every call\n to the projector or its runtime probes.\n\n## Invariants\n\n- Never expose application-secret bytes or bootstrap token hashes in diagnostics.\n- Never include migration or provider error text in a local or deployed startup\n error message; migration driver text is a likely credential carrier.\n- Never include provider error text, URLs, tokens, or credentials in deployed\n errors, diagnostics, health, or readiness payloads.\n- Keep local data, assets, database, and secrets outside the source tree.\n- Do not reach into collection/database private fields. Add an upstream public\n API if composition cannot be expressed through exported surfaces.\n- Keep profile selection out of domain models, generated APIs, MCP/WebMCP\n surfaces, effects, approvals, and authorization records.\n\n## Validation\n\n```bash\npnpm --filter @happyvertical/smrt-app-runtime test\npnpm --filter @happyvertical/smrt-app-runtime typecheck\npnpm --filter @happyvertical/smrt-app-runtime build\n```\n"
|
|
69
|
+
"agentDoc": "# @happyvertical/smrt-app-runtime\n\nApplication infrastructure composition for the validated runtime profiles in\n`@happyvertical/smrt-config`.\n\n## Local profile\n\n- `validateApplicationId()` is the canonical strict validator for explicit app\n IDs. `encodeApplicationId()` derives a length-bounded, collision-resistant\n ID from package names; generated operational surfaces must share these APIs.\n- `initializeLocalApplicationRuntime()` owns user data paths, SQLite tuning,\n local application-secret creation, and the single-use owner bootstrap flow.\n- SQLite is acquired through `@happyvertical/sql`'s explicit\n `{ driver: 'node:sqlite', custody: 'trusted-parent' }` boundary after the\n runtime establishes its user-owned mode-0700 data root.\n- Application migrations are explicit through `prepareDatabase`; runtime never\n creates application model tables implicitly. A rejected `prepareDatabase` is\n normalized into `LocalRuntimeError` with the stable `migration_failed` code\n and the fixed recovery message `MIGRATION_FAILED_MESSAGE` (\"run pnpm\n app:setup and inspect the private migration logs\"). The caller's error text\n is never surfaced — it is retained only as a non-enumerable `cause` for\n private logs — and the same application root stays retryable.\n- Standalone migration commands must call `prepareLocalDatabaseStorage()`\n before opening SQLite so custody is established without creating schema or\n bootstrap records.\n- Read-only/copy/import operator commands must call\n `validateLocalDatabaseStorage()` before opening SQLite; it verifies the\n app-bound marker, source separation, real path chain, ownership, and mode\n without creating or repairing storage.\n- Owner bootstrap creates normal `Person`, `User`, `Tenant`, owner `Role` /\n `Membership`, and `Session` records in one transaction.\n- Bootstrap is loopback-only. Only an HMAC of the short-lived token is stored.\n A token that cannot claim fails a read-only preflight before role/catalog\n seeding (#3323); the claim transaction's conditional UPDATE stays the\n authority for single use, expiry, and concurrency.\n- Background jobs and application-defined paid capabilities are default-off.\n- The embedded runner reuses `TaskRunner`; it is not a second job contract.\n\n## Deployed profiles\n\n- `initializeDeployedApplicationRuntime()` composes only `self-hosted` and\n `cloud`; it rejects local initialization and provider/binding mismatches.\n- Provider bindings own credentials, vendor clients, and readiness checks.\n Runtime diagnostics contain selectors and status only.\n- The database binding must expose a provider-owned `close` callback before\n `connect` can run. The runtime owns that cleanup boundary and runs the\n application's explicit, idempotent `prepareDatabase` hook.\n- A failed startup cleanup throws `DeployedRuntimeCleanupError`; callers retain\n and retry its idempotent `retryCleanup()` boundary until it succeeds.\n- Public authentication, asset storage, and secret bindings are mandatory and\n readiness-checked before startup succeeds.\n- `createTaskWorker()` and `createScheduleWorker()` initialize the normal jobs\n package runners against the shared PostgreSQL database. They are intended for\n separate processes; the web process does not start them automatically. The\n worker process must import the app's compiled registration\n (`.smrt/runtime/register.js`, #3117) first; runners resolve `objectType`\n through the registry and never load application source themselves.\n- `close()` drains in-flight readiness/session/worker initialization and\n serialized runner start/stop operations, stops every runner returned by the\n runtime, and then closes PostgreSQL. Returned runners must not be restarted\n after runtime shutdown begins.\n- A tracked operation may await a re-entrant `close()` request without\n deadlocking; external `close()` callers still await complete cleanup.\n- `health()` is process liveness. `readiness()` probes database/auth/assets/\n secrets; it does not claim that an external worker fleet is running.\n- Database-provider readiness is additive; a PostgreSQL-specific server-version\n probe always gates startup and live readiness.\n- Cloud must keep required tenant context and must never introduce a root or\n unscoped tenant fallback. RLS remains an explicit deployment/migration choice.\n\n## SvelteKit entry (`./sveltekit`)\n\n- Server-only subpath; `@sveltejs/kit` is an optional peer and is externalized\n in `vite.config.ts` (kit recognizes `redirect`/`fail` by class identity).\n The root entry must never import kit or Svelte.\n- `createSmrtSvelteKitRuntime()` order is fixed: readiness gate → URL tenant\n selection (`locals.selectedTenant*`, never tenant context, headers ignored) →\n verified session (tenant context entered only when\n `isSessionTenantAuthorized()` accepts it, else unauthenticated; never via\n `createSessionHandler`, which re-runs `resolve` on downstream errors) →\n publish `locals.tenantContext` only for that verified tenant.\n- `database-rls` isolation forces `postgresRls: true`, including on\n `skipPaths` (anonymous principal). `resolve` runs at most once;\n session-layer failure before it is a 500 with cleared locals.\n- In an RLS request `classOptions()`/`databaseConfig()` return the\n transaction-bound request db (class `db` overrides win). Anything that\n outlives a request (session service, tenant selector) must use the base\n config, never these.\n- The writer lease, provider readiness, and onboarding file cleanup are\n injected hooks. Apps pass the root entry's `acquireWriterLease` (over\n `prepareApplicationStateRoot`) and `createProviderReadinessProbe`; deployed\n startup fails closed without `providerReadiness`.\n- Owner setup re-checks loopback peer and loopback URL host per request and\n returns only fixed `{ code, message }` failures.\n- `resolveApplicationId()` / `runtimeConfigurationFingerprint()` must stay\n byte-compatible with process managers (golden vectors in tests).\n\n## Operator state (root entry)\n\n- One owner for the state shared by the web process and `smrt app`\n (`@happyvertical/smrt-cli/app` re-exports these): `resolveApplicationStateRoot`\n / `prepareApplicationStateRoot` (private app-bound mode-0700 root and\n marker), `withOperationLock` (`operation.lock`), `acquireWriterLease` /\n `readActiveWriterLease` (`writer.lease`), `reclaimStaleRecord`, and\n `createProviderReadinessProbe` (`SMRT_*_READINESS_MODULE`, resolved from the\n app root with Node's own ESM rules). File names, record formats, and\n messages are a cross-process contract; never change them unilaterally.\n- Tests that hook `node:fs` interleavings must live in this package: from a\n consumer the package is an externalized dependency a mock cannot reach.\n\n## Public runtime diagnostics\n\n- `projectRuntimeDiagnostics()` is the only public diagnostic projection. It\n constructs the schema-version-1 allowlist and never serializes the private\n local or deployed diagnostics objects.\n- The allowlist is limited to active profile/coarse health, schema and\n migration readiness, six fixed capability statuses, a sorted/capped public\n tool inventory plus digest, explicit operational topology differences,\n coarse worker heartbeat state, and eight stable code/timestamp errors.\n- Callers supply an explicit clock and worker-heartbeat seam. A missing\n heartbeat is `unknown`; web-process liveness never proves worker liveness.\n- Error ingestion maps unknown codes to `runtime_error`, truncates timestamps\n to the minute, and ignores messages, stacks, logs, records, and nested state.\n- Authentication and diagnostics-read authorization belong before every call\n to the projector or its runtime probes.\n\n## Invariants\n\n- Never expose application-secret bytes or bootstrap token hashes in diagnostics.\n- Never include migration or provider error text in a local or deployed startup\n error message; migration driver text is a likely credential carrier.\n- Never include provider error text, URLs, tokens, or credentials in deployed\n errors, diagnostics, health, or readiness payloads.\n- Keep local data, assets, database, and secrets outside the source tree.\n- Do not reach into collection/database private fields. Add an upstream public\n API if composition cannot be expressed through exported surfaces.\n- Keep profile selection out of domain models, generated APIs, MCP/WebMCP\n surfaces, effects, approvals, and authorization records.\n\n## Validation\n\n```bash\npnpm --filter @happyvertical/smrt-app-runtime test\npnpm --filter @happyvertical/smrt-app-runtime typecheck\npnpm --filter @happyvertical/smrt-app-runtime build\n```\n"
|
|
67
70
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialized removal of stale `operation.lock` / `writer.lease` files.
|
|
3
|
+
*
|
|
4
|
+
* Deciding a lock is stale (owner pid gone) and unlinking it are two steps,
|
|
5
|
+
* and POSIX has no "unlink if unchanged". Two reclaimers that both observed
|
|
6
|
+
* the same dead owner could otherwise interleave so that the slower one
|
|
7
|
+
* unlinks the live lock the faster one just created (#3371 review R1).
|
|
8
|
+
*
|
|
9
|
+
* Every reclamation therefore runs under an exclusive SQLite transaction on
|
|
10
|
+
* a dedicated mutex database next to the lock. The kernel releases that lock
|
|
11
|
+
* when its holder dies, so the mutex itself can never go stale, and nothing
|
|
12
|
+
* on the lock path is renamed or replaced. Under the mutex the reclaimer
|
|
13
|
+
* re-reads the lock and only unlinks the exact record it judged stale, after
|
|
14
|
+
* re-checking that its owner is still gone. Acquisition stays a plain
|
|
15
|
+
* `O_EXCL` create, so record formats are unchanged and readable by the
|
|
16
|
+
* template's original scripts.
|
|
17
|
+
*/
|
|
18
|
+
/** Mutex database file created in the lock's (private) state directory. */
|
|
19
|
+
export declare const RECLAIM_MUTEX_FILE = ".smrt-lock-reclaim.sqlite";
|
|
20
|
+
/** Outcome of {@link reclaimStaleRecord}. */
|
|
21
|
+
export type ReclaimOutcome =
|
|
22
|
+
/** The observed stale record was removed by this call. */
|
|
23
|
+
'removed'
|
|
24
|
+
/** The lock no longer exists (another reclaimer removed it). */
|
|
25
|
+
| 'gone'
|
|
26
|
+
/** The lock now holds a different record, or its owner is live: untouched. */
|
|
27
|
+
| 'changed';
|
|
28
|
+
/**
|
|
29
|
+
* Remove `path` only if it still contains exactly `observed` and
|
|
30
|
+
* `isStale(observed)` still holds, serialized against every other reclaimer.
|
|
31
|
+
*/
|
|
32
|
+
export declare function reclaimStaleRecord(path: string, observed: string, isStale: (contents: string) => boolean): ReclaimOutcome;
|
|
33
|
+
//# sourceMappingURL=stale-reclaim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stale-reclaim.d.ts","sourceRoot":"","sources":["../src/stale-reclaim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB,8BAA8B,CAAC;AAK9D,6CAA6C;AAC7C,MAAM,MAAM,cAAc;AACxB,0DAA0D;AACxD,SAAS;AACX,gEAAgE;GAC9D,MAAM;AACR,8EAA8E;GAC5E,SAAS,CAAC;AAoCd;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,GACrC,cAAc,CAahB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Private, app-bound state root shared by the running web process and the
|
|
3
|
+
* `smrt app` operator commands (`writer.lease`, `operation.lock`, onboarding
|
|
4
|
+
* handoff files).
|
|
5
|
+
*
|
|
6
|
+
* The path is derived from the canonical application/data identity so every
|
|
7
|
+
* process managing one database root shares one lock domain. Moved here from
|
|
8
|
+
* `@happyvertical/smrt-cli/app` (which re-exports it) so the web process does
|
|
9
|
+
* not depend on the CLI package; files, names, and error messages are
|
|
10
|
+
* unchanged from the template's former `scripts/smrt-runtime-identity.mjs`.
|
|
11
|
+
*/
|
|
12
|
+
/** Inputs for the state-root resolvers. */
|
|
13
|
+
export interface ApplicationStateRootOptions {
|
|
14
|
+
appId: string;
|
|
15
|
+
dataDirectory?: string;
|
|
16
|
+
sourceRoot?: string;
|
|
17
|
+
/** Testable platform override. */
|
|
18
|
+
platformName?: NodeJS.Platform | string;
|
|
19
|
+
/** Testable home-directory override. */
|
|
20
|
+
homeDirectory?: string;
|
|
21
|
+
/** Testable environment override. */
|
|
22
|
+
environment?: Record<string, string | undefined>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* State is derived from the canonical application/data identity. It is not an
|
|
26
|
+
* independent override because every process and operator command must share
|
|
27
|
+
* one lock domain for a given database root.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveApplicationStateRoot(options: ApplicationStateRootOptions): string;
|
|
30
|
+
/** Owner check seam; production uses the real process uid. */
|
|
31
|
+
export interface StateCustodyOptions {
|
|
32
|
+
/**
|
|
33
|
+
* Testable uid override (e.g. to prove a foreign owner is rejected). It can
|
|
34
|
+
* only substitute a uid; it cannot disable the ownership check.
|
|
35
|
+
*/
|
|
36
|
+
currentUid?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Create or verify the private, app-bound state/lock directory.
|
|
40
|
+
*/
|
|
41
|
+
export declare function prepareApplicationStateRoot(options: ApplicationStateRootOptions & StateCustodyOptions): string;
|
|
42
|
+
//# sourceMappingURL=state-root.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-root.d.ts","sourceRoot":"","sources":["../src/state-root.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgBH,2CAA2C;AAC3C,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC;IACxC,wCAAwC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAClD;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,2BAA2B,GACnC,MAAM,CAoBR;AAWD,8DAA8D;AAC9D,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,2BAA2B,GAAG,mBAAmB,GACzD,MAAM,CAyFR"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mountable runtime routes: liveness health, authorized diagnostics, and the
|
|
3
|
+
* server-owned session summary for the root layout.
|
|
4
|
+
*/
|
|
5
|
+
import { type RequestHandler } from '@sveltejs/kit';
|
|
6
|
+
import type { RuntimeDiagnostics } from '../runtime-diagnostics.js';
|
|
7
|
+
import type { ApplicationRuntimeDiagnosticsOptions, SmrtSvelteKitRuntime } from './runtime.js';
|
|
8
|
+
/** Explicit permission that grants diagnostics read without the owner role. */
|
|
9
|
+
export declare const RUNTIME_DIAGNOSTICS_READ_PERMISSION = "runtime_diagnostics.read";
|
|
10
|
+
/**
|
|
11
|
+
* Mount as `src/routes/api/_runtime/health/+server.ts`:
|
|
12
|
+
* `export const GET = createRuntimeHealthHandler(runtime);`
|
|
13
|
+
*
|
|
14
|
+
* Local responses add the application ID, managed process instance, and the
|
|
15
|
+
* secret-free configuration fingerprint that process managers compare.
|
|
16
|
+
* Deployed responses expose only schema version, status, and profile.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createRuntimeHealthHandler(runtime: Pick<SmrtSvelteKitRuntime, 'health'>): RequestHandler;
|
|
19
|
+
/** The locals the diagnostics route authorizes against. */
|
|
20
|
+
export interface DiagnosticsPrincipalLocals {
|
|
21
|
+
readonly user?: {
|
|
22
|
+
readonly id?: unknown;
|
|
23
|
+
} | null;
|
|
24
|
+
readonly membership?: {
|
|
25
|
+
readonly userId?: unknown;
|
|
26
|
+
readonly tenantId?: unknown;
|
|
27
|
+
readonly roleId?: unknown;
|
|
28
|
+
isActive?: () => boolean;
|
|
29
|
+
} | null;
|
|
30
|
+
readonly permissions?: readonly string[];
|
|
31
|
+
readonly tenantId?: unknown;
|
|
32
|
+
readonly sessionId?: unknown;
|
|
33
|
+
}
|
|
34
|
+
type DiagnosticsInputValue<T> = T | (() => T);
|
|
35
|
+
export interface RuntimeDiagnosticsHandlerOptions {
|
|
36
|
+
/** Supplies default diagnostics reading and owner-role lookup. */
|
|
37
|
+
readonly runtime?: Pick<SmrtSvelteKitRuntime, 'readDiagnostics' | 'classOptions'>;
|
|
38
|
+
/** Replace the runtime projection (must already be the public allowlist). */
|
|
39
|
+
readonly readDiagnostics?: () => Promise<RuntimeDiagnostics>;
|
|
40
|
+
/** Replace the owner-role lookup. Thrown errors deny. */
|
|
41
|
+
readonly resolveRoleSlug?: (roleId: string) => Promise<string | null>;
|
|
42
|
+
/** Override where the principal comes from. Defaults to `event.locals`. */
|
|
43
|
+
readonly resolveLocals?: (event: {
|
|
44
|
+
readonly locals: unknown;
|
|
45
|
+
}) => DiagnosticsPrincipalLocals;
|
|
46
|
+
/** Public tool inventory (sorted/capped by the projector). */
|
|
47
|
+
readonly toolNames?: DiagnosticsInputValue<readonly string[]>;
|
|
48
|
+
readonly schemaStatus?: DiagnosticsInputValue<ApplicationRuntimeDiagnosticsOptions['schemaStatus']>;
|
|
49
|
+
readonly migrationStatus?: DiagnosticsInputValue<ApplicationRuntimeDiagnosticsOptions['migrationStatus']>;
|
|
50
|
+
readonly workerHeartbeatAt?: () => Date | string | null | undefined;
|
|
51
|
+
readonly recentErrors?: () => ApplicationRuntimeDiagnosticsOptions['recentErrors'];
|
|
52
|
+
/** Testable clock. */
|
|
53
|
+
readonly now?: () => Date;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Mount as `src/routes/api/_runtime/diagnostics/+server.ts`:
|
|
57
|
+
* `export const GET = createRuntimeDiagnosticsHandler({ runtime });`
|
|
58
|
+
*
|
|
59
|
+
* Authentication and authorization always complete before the private runtime
|
|
60
|
+
* is read. Authorized callers hold `runtime_diagnostics.read` or the `owner`
|
|
61
|
+
* role of their active, session-matching membership. Every failure is a
|
|
62
|
+
* stable `{ schemaVersion: 1, error: { code } }` body.
|
|
63
|
+
*/
|
|
64
|
+
export declare function createRuntimeDiagnosticsHandler(options: RuntimeDiagnosticsHandlerOptions): RequestHandler;
|
|
65
|
+
/** Server-owned session summary for the root layout. */
|
|
66
|
+
export interface SessionLayoutData {
|
|
67
|
+
readonly session: {
|
|
68
|
+
readonly authenticated: boolean;
|
|
69
|
+
/** Tenant authorized by the active session. */
|
|
70
|
+
readonly activeTenantId: string | null;
|
|
71
|
+
/** URL-selected tenant candidate; display only, never authorization. */
|
|
72
|
+
readonly selectedTenantSlug: string | null;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Mount as `src/routes/+layout.server.ts`:
|
|
77
|
+
* `export const load = createSessionLayoutLoad();`
|
|
78
|
+
*/
|
|
79
|
+
export declare function createSessionLayoutLoad(): (event: {
|
|
80
|
+
readonly locals: unknown;
|
|
81
|
+
}) => SessionLayoutData;
|
|
82
|
+
export {};
|
|
83
|
+
//# sourceMappingURL=handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/sveltekit/handlers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvelteKit server entry for `@happyvertical/smrt-app-runtime`.
|
|
3
|
+
*
|
|
4
|
+
* Server-only: this entry never imports Svelte components or browser code.
|
|
5
|
+
* `@sveltejs/kit` is an optional peer dependency resolved from the consumer.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* // src/hooks.server.ts
|
|
10
|
+
* import { createSmrtSvelteKitRuntime } from '@happyvertical/smrt-app-runtime/sveltekit';
|
|
11
|
+
*
|
|
12
|
+
* export const runtime = createSmrtSvelteKitRuntime();
|
|
13
|
+
* export const { handle, init } = runtime;
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export { createRuntimeDiagnosticsHandler, createRuntimeHealthHandler, createSessionLayoutLoad, type DiagnosticsPrincipalLocals, RUNTIME_DIAGNOSTICS_READ_PERMISSION, type RuntimeDiagnosticsHandlerOptions, type SessionLayoutData, } from './handlers.js';
|
|
17
|
+
export { createOwnerSetupPage, DEFAULT_OWNER_SETUP_MESSAGES, isLoopbackAddress, isLoopbackHostname, isLoopbackRequest, OWNER_SETUP_FIELDS, type OwnerSetupActionEvent, type OwnerSetupErrorCode, type OwnerSetupEvent, type OwnerSetupFailure, type OwnerSetupPage, type OwnerSetupPageData, type OwnerSetupPageOptions, } from './owner-setup.js';
|
|
18
|
+
export { type ApplicationRuntimeDiagnosticsOptions, authorizedTenantLocalsHandle, composeHandles, createSmrtSvelteKitRuntime, type ProviderReadinessComponent, type ProviderReadinessFactory, type RuntimeHealthBody, type SmrtRuntimeBoundPrincipal, type SmrtRuntimeLocals, type SmrtRuntimeSessionCookie, type SmrtRuntimeSessionOptions, type SmrtSvelteKitRuntime, type SmrtSvelteKitRuntimeOptions, type WriterLease, type WriterLeaseContext, } from './runtime.js';
|
|
19
|
+
export { createSubdomainTenantSelector, normalizeTenantSelection, type SubdomainTenantSelectorOptions, selectTenantSlug, type TenantSelection, type TenantSelectionEvent, type TenantSelector, } from './tenancy.js';
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sveltekit/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EACL,+BAA+B,EAC/B,0BAA0B,EAC1B,uBAAuB,EACvB,KAAK,0BAA0B,EAC/B,mCAAmC,EACnC,KAAK,gCAAgC,EACrC,KAAK,iBAAiB,GACvB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,KAAK,oCAAoC,EACzC,4BAA4B,EAC5B,cAAc,EACd,0BAA0B,EAC1B,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAC7B,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EACzB,KAAK,2BAA2B,EAChC,KAAK,WAAW,EAChB,KAAK,kBAAkB,GACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,6BAA6B,EAC7B,wBAAwB,EACxB,KAAK,8BAA8B,EACnC,gBAAgB,EAChB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server half of the local owner-setup page (`src/routes/setup/+page.server.ts`).
|
|
3
|
+
*
|
|
4
|
+
* Owner bootstrap is local-profile only, loopback-only, and single-use. The
|
|
5
|
+
* runtime stores only an HMAC of the short-lived token; this module never
|
|
6
|
+
* stores, logs, or returns claim errors, and it re-checks loopback custody on
|
|
7
|
+
* every request instead of trusting the bind host alone.
|
|
8
|
+
*/
|
|
9
|
+
import type { Cookies } from '@sveltejs/kit';
|
|
10
|
+
import { type ActionFailure } from '@sveltejs/kit';
|
|
11
|
+
import type { LocalOwnerClaimResult } from '../index.js';
|
|
12
|
+
import type { SmrtSvelteKitRuntime } from './runtime.js';
|
|
13
|
+
/** Data returned by the setup page `load`. */
|
|
14
|
+
export interface OwnerSetupPageData {
|
|
15
|
+
/** False when setup is claimed, disabled (deployed), or not loopback. */
|
|
16
|
+
readonly available: boolean;
|
|
17
|
+
/** One-time token from `?token=` (empty unless available). */
|
|
18
|
+
readonly token: string;
|
|
19
|
+
}
|
|
20
|
+
/** Stable failure codes returned by the setup action. */
|
|
21
|
+
export type OwnerSetupErrorCode =
|
|
22
|
+
/** Not the local profile (HTTP 404). */
|
|
23
|
+
'setup_disabled'
|
|
24
|
+
/** Request is not from a loopback client on a loopback host (HTTP 403). */
|
|
25
|
+
| 'setup_unavailable'
|
|
26
|
+
/** Missing token, blank name, or email without `@` (HTTP 400). */
|
|
27
|
+
| 'setup_invalid_input'
|
|
28
|
+
/** Token wrong, expired, or already used; owner already claimed (HTTP 400). */
|
|
29
|
+
| 'setup_invalid';
|
|
30
|
+
/** `form` data returned with `fail(status, …)`. */
|
|
31
|
+
export interface OwnerSetupFailure {
|
|
32
|
+
readonly code: OwnerSetupErrorCode;
|
|
33
|
+
/** Fixed, secret-free message; never claim error text. */
|
|
34
|
+
readonly message: string;
|
|
35
|
+
}
|
|
36
|
+
/** Form field names posted to the setup action. */
|
|
37
|
+
export declare const OWNER_SETUP_FIELDS: Readonly<{
|
|
38
|
+
readonly token: "token";
|
|
39
|
+
readonly name: "name";
|
|
40
|
+
readonly email: "email";
|
|
41
|
+
readonly tenantName: "tenantName";
|
|
42
|
+
}>;
|
|
43
|
+
export declare const DEFAULT_OWNER_SETUP_MESSAGES: Readonly<Record<OwnerSetupErrorCode, string>>;
|
|
44
|
+
/** The request fields the setup page reads. */
|
|
45
|
+
export interface OwnerSetupEvent {
|
|
46
|
+
readonly url: URL;
|
|
47
|
+
readonly locals: unknown;
|
|
48
|
+
getClientAddress(): string;
|
|
49
|
+
}
|
|
50
|
+
export interface OwnerSetupActionEvent extends OwnerSetupEvent {
|
|
51
|
+
readonly request: Request;
|
|
52
|
+
readonly cookies: Pick<Cookies, 'set'>;
|
|
53
|
+
}
|
|
54
|
+
export interface OwnerSetupPageOptions {
|
|
55
|
+
/** Where a signed-in visitor or a successful claim is sent. Default `/`. */
|
|
56
|
+
readonly redirectTo?: string;
|
|
57
|
+
/** Replace fixed failure messages (for example app-specific recovery steps). */
|
|
58
|
+
readonly messages?: Partial<Record<OwnerSetupErrorCode, string>>;
|
|
59
|
+
/** Override where the signed-in user comes from. Defaults to `locals.user`. */
|
|
60
|
+
readonly resolveUser?: (event: OwnerSetupEvent) => unknown;
|
|
61
|
+
/**
|
|
62
|
+
* Best-effort cleanup after a successful claim (for example removing local
|
|
63
|
+
* onboarding handoff files). Errors are ignored: the owner and session are
|
|
64
|
+
* already authoritative and a stale token now fails closed.
|
|
65
|
+
*/
|
|
66
|
+
readonly onOwnerClaimed?: (result: LocalOwnerClaimResult, event: OwnerSetupActionEvent) => void | Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
export interface OwnerSetupPage {
|
|
69
|
+
readonly load: (event: OwnerSetupEvent) => Promise<OwnerSetupPageData>;
|
|
70
|
+
readonly actions: {
|
|
71
|
+
readonly default: (event: OwnerSetupActionEvent) => Promise<ActionFailure<OwnerSetupFailure>>;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Mount as `src/routes/setup/+page.server.ts`:
|
|
76
|
+
*
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { createOwnerSetupPage } from '@happyvertical/smrt-app-runtime/sveltekit';
|
|
79
|
+
* import { runtime } from '../../hooks.server';
|
|
80
|
+
*
|
|
81
|
+
* export const { load, actions } = createOwnerSetupPage(runtime);
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* The form posts to the `default` action with fields `token` (hidden),
|
|
85
|
+
* `name`, `email`, and optional `tenantName`. Success sets the session cookie
|
|
86
|
+
* and redirects 303; failures return `fail(status, { code, message })`.
|
|
87
|
+
*/
|
|
88
|
+
export declare function createOwnerSetupPage(runtime: Pick<SmrtSvelteKitRuntime, 'resolvedRuntime' | 'localRuntime' | 'sessionCookie'>, options?: OwnerSetupPageOptions): OwnerSetupPage;
|
|
89
|
+
/**
|
|
90
|
+
* True only when both the peer address and the addressed host are loopback.
|
|
91
|
+
* The host check rejects tunnels and DNS-rebinding hosts that forward to a
|
|
92
|
+
* loopback listener; a throwing `getClientAddress` is treated as remote.
|
|
93
|
+
*/
|
|
94
|
+
export declare function isLoopbackRequest(event: {
|
|
95
|
+
readonly url: URL;
|
|
96
|
+
getClientAddress(): string;
|
|
97
|
+
}): boolean;
|
|
98
|
+
/** Loopback IP literal: `127.0.0.0/8`, `::1`, or IPv4-mapped `127/8`. */
|
|
99
|
+
export declare function isLoopbackAddress(value: string | null | undefined): boolean;
|
|
100
|
+
/** Loopback host name as SvelteKit reports it in `url.hostname`. */
|
|
101
|
+
export declare function isLoopbackHostname(hostname: string): boolean;
|
|
102
|
+
//# sourceMappingURL=owner-setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"owner-setup.d.ts","sourceRoot":"","sources":["../../src/sveltekit/owner-setup.ts"],"names":[],"mappings":"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"}
|