@happyvertical/smrt-template-sveltekit 0.53.4 → 0.53.5
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 +18 -12
- package/README.md +1 -1
- package/mcp-apps-template/package.dependencies.json +2 -2
- package/mcp-apps-template/src/routes/.well-known/oauth-protected-resource/api/mcp/+server.ts +1 -5
- package/mcp-apps-template/src/routes/api/mcp/+server.ts +10 -11
- package/package.json +19 -17
- package/template/AGENTS.md +8 -4
- package/template/README.md +36 -15
- package/template/package.json +11 -11
- package/template/smrt.config.ts +4 -35
- package/template/src/lib/server/smrt.ts +12 -96
- package/template/src/routes/+page.server.ts +4 -4
- package/template/src/routes/setup/+page.server.ts +5 -19
- package/template.config.js +11 -11
- package/mcp-apps-template/src/lib/server/mcp-hosted-principal.ts +0 -12
package/AGENTS.md
CHANGED
|
@@ -22,11 +22,17 @@ It is the ground-up alternative to `smrt-saas-starter`.
|
|
|
22
22
|
Never reintroduce copied operator scripts: fix the CLI command instead.
|
|
23
23
|
- Server composition is `createSmrtSvelteKitRuntime()` from
|
|
24
24
|
`@happyvertical/smrt-app-runtime/sveltekit`, built once in
|
|
25
|
-
`src/lib/server/smrt.ts`
|
|
26
|
-
(`
|
|
27
|
-
|
|
28
|
-
the
|
|
29
|
-
|
|
25
|
+
`src/lib/server/smrt.ts`, which holds only `runtime` and its options
|
|
26
|
+
(provider readiness via `createProviderReadinessProbe`), so the web process
|
|
27
|
+
never imports the CLI. The runtime takes the local writer lease by default,
|
|
28
|
+
the `smrt()` plugin injects the generated registration, and each generated
|
|
29
|
+
route embeds a prelude that imports `$lib/server/smrt` and resolves
|
|
30
|
+
collections through its exported `runtime.getCollection()` (#3416). A legacy
|
|
31
|
+
`getCollection`/`getSmrtConfig` export would take precedence (deprecated,
|
|
32
|
+
honoured for one release): never re-add one, a register import, guard, or
|
|
33
|
+
manifest hydration.
|
|
34
|
+
Health, diagnostics, layout session, and owner setup routes mount that
|
|
35
|
+
package's handlers.
|
|
30
36
|
- The production baseline uses adapter-node with separate web, task-worker, and
|
|
31
37
|
schedule-worker processes. Workers import the build-compiled
|
|
32
38
|
`.smrt/runtime/register.js` (#3117) before creating runners so app-defined
|
|
@@ -74,9 +80,7 @@ It is the ground-up alternative to `smrt-saas-starter`.
|
|
|
74
80
|
- `src/hooks.server.ts` re-exports the runtime's `handle`/`init`: URL tenant
|
|
75
81
|
selection stays a separate candidate and only the verified session
|
|
76
82
|
establishes tenant context. Never turn an untrusted header into authority.
|
|
77
|
-
-
|
|
78
|
-
generated manifest metadata, creates the runtime, and exposes
|
|
79
|
-
`getCollection()`/`getSmrtConfig()` over `runtime.classOptions()`, which is
|
|
83
|
+
- App code uses `runtime.getCollection()`/`runtime.classOptions()`, which are
|
|
80
84
|
request-scoped (RLS transaction) and must never be retained.
|
|
81
85
|
- `Item` is the single example object and demonstrates optional tenant scope,
|
|
82
86
|
a REST writable allowlist, shared CRUD action metadata, and the explicit
|
|
@@ -100,10 +104,12 @@ It is the ground-up alternative to `smrt-saas-starter`.
|
|
|
100
104
|
(`createRuntimeDiagnosticsHandler`) requires a direct active tenant membership
|
|
101
105
|
plus the owner role or `runtime_diagnostics.read` before any projection or
|
|
102
106
|
probe; it never calls principal-bound server tools.
|
|
103
|
-
- The opt-in `mcp-apps-template/` overlay is one `mountMcpAppRoute()` route
|
|
104
|
-
the protected-resource metadata route,
|
|
105
|
-
`
|
|
106
|
-
|
|
107
|
+
- The opt-in `mcp-apps-template/` overlay is one `mountMcpAppRoute()` route
|
|
108
|
+
and the protected-resource metadata route, both using
|
|
109
|
+
`createHostedMcpResourceAuth({ profile, runtime })` (local owner tokens,
|
|
110
|
+
hosted membership-backed principals); `pnpm typecheck` checks it against
|
|
111
|
+
the template. `__tests__/localMcpToken.test.ts` proves mint → real
|
|
112
|
+
`smrt-mcp-bridge` stdio → route against the real runtime.
|
|
107
113
|
|
|
108
114
|
## Tests
|
|
109
115
|
|
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ fixtures.
|
|
|
53
53
|
## 2. Understand the generated files
|
|
54
54
|
|
|
55
55
|
The checked-in template contains authored objects, one server module
|
|
56
|
-
(`src/lib/server/smrt.ts`, the runtime and
|
|
56
|
+
(`src/lib/server/smrt.ts`, the runtime and its options), thin hooks and
|
|
57
57
|
routes that mount package handlers, pages, and configuration. It ships no
|
|
58
58
|
operator scripts. Vite generates `.smrt/manifest.json`,
|
|
59
59
|
`.smrt/smrt-knowledge.json`, `.smrt/register.js`,
|
package/mcp-apps-template/src/routes/.well-known/oauth-protected-resource/api/mcp/+server.ts
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { createHostedMcpResourceAuth } from '@happyvertical/smrt-app-mcp/auth';
|
|
2
2
|
import { mountMcpProtectedResourceMetadataRoute } from '@happyvertical/smrt-app-mcp/sveltekit';
|
|
3
3
|
|
|
4
|
-
import { resolveHostedMcpPrincipal } from '$lib/server/mcp-hosted-principal';
|
|
5
4
|
import { runtime } from '$lib/server/smrt';
|
|
6
5
|
|
|
7
6
|
const { profile } = await runtime.resolvedRuntime();
|
|
8
7
|
|
|
9
8
|
/** RFC 9728 metadata at exactly the URL hosted bearer challenges advertise (404 locally). */
|
|
10
9
|
export const GET = mountMcpProtectedResourceMetadataRoute(
|
|
11
|
-
createHostedMcpResourceAuth({
|
|
12
|
-
profile,
|
|
13
|
-
resolvePrincipal: resolveHostedMcpPrincipal,
|
|
14
|
-
}),
|
|
10
|
+
createHostedMcpResourceAuth({ profile, runtime }),
|
|
15
11
|
);
|
|
@@ -4,7 +4,6 @@ import { mountMcpAppRoute } from '@happyvertical/smrt-app-mcp/sveltekit';
|
|
|
4
4
|
import { openAiDisplayMetadata, withOpenAiEntrypoints } from '@happyvertical/smrt-mcp-openai';
|
|
5
5
|
|
|
6
6
|
import { Item } from '$lib/objects/Item';
|
|
7
|
-
import { resolveHostedMcpPrincipal } from '$lib/server/mcp-hosted-principal';
|
|
8
7
|
import { runtime } from '$lib/server/smrt';
|
|
9
8
|
|
|
10
9
|
const ITEM_RESOURCE_URI = 'ui://smrt-app/v1/items.html';
|
|
@@ -41,22 +40,22 @@ const { profile } = await runtime.resolvedRuntime();
|
|
|
41
40
|
/**
|
|
42
41
|
* Stateless SDK-v2 Streamable HTTP MCP endpoint for this app's own `Item`.
|
|
43
42
|
* Locally the principal is the signed session (user, authorized tenant, and
|
|
44
|
-
* permission slugs)
|
|
45
|
-
* the
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
43
|
+
* permission slugs), or an owner-minted bearer token from `smrt app token`
|
|
44
|
+
* for a local MCP client such as the `smrt-mcp-bridge` stdio bridge. Hosted
|
|
45
|
+
* profiles verify a bearer access token and map it through the runtime's
|
|
46
|
+
* membership-backed `resolveMcpPrincipal` (pass `resolvePrincipal` to
|
|
47
|
+
* override it). A bearer principal is bound into the request permission
|
|
48
|
+
* context (under `database-rls`, its own RLS transaction) with its scopes
|
|
49
|
+
* capped to live permissions. Every principal needs `items.read`, so only
|
|
50
|
+
* read-only tools are published until per-operation authorization exists;
|
|
51
|
+
* browser requests from a foreign `Origin` are refused.
|
|
50
52
|
*/
|
|
51
53
|
export const POST = mountMcpAppRoute({
|
|
52
54
|
models: [Item],
|
|
53
55
|
requiredScopes: ['items.read'],
|
|
54
56
|
effects: ['read'],
|
|
55
57
|
smrtOptions: () => ({ db: runtime.databaseConfig() }),
|
|
56
|
-
auth: createHostedMcpResourceAuth({
|
|
57
|
-
profile,
|
|
58
|
-
resolvePrincipal: resolveHostedMcpPrincipal,
|
|
59
|
-
}),
|
|
58
|
+
auth: createHostedMcpResourceAuth({ profile, runtime }),
|
|
60
59
|
bindPrincipal: runtime.runAsPrincipal,
|
|
61
60
|
workflowTools: [itemsOverview],
|
|
62
61
|
resources: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-template-sveltekit",
|
|
3
|
-
"version": "0.53.
|
|
3
|
+
"version": "0.53.5",
|
|
4
4
|
"description": "Minimal SvelteKit project template with s-m-r-t framework integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -32,25 +32,27 @@
|
|
|
32
32
|
"directory": "packages/template-sveltekit"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@happyvertical/smrt-core": "0.53.
|
|
35
|
+
"@happyvertical/smrt-core": "0.53.5"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@happyvertical/smrt-app-
|
|
39
|
-
"@happyvertical/smrt-app-
|
|
40
|
-
"@happyvertical/smrt-
|
|
41
|
-
"@happyvertical/smrt-
|
|
42
|
-
"@happyvertical/smrt-
|
|
43
|
-
"@happyvertical/smrt-
|
|
44
|
-
"@happyvertical/smrt-
|
|
45
|
-
"@happyvertical/smrt-
|
|
46
|
-
"@happyvertical/smrt-
|
|
47
|
-
"@happyvertical/smrt-
|
|
48
|
-
"@happyvertical/smrt-
|
|
49
|
-
"@happyvertical/smrt-
|
|
50
|
-
"@happyvertical/smrt-
|
|
51
|
-
"@happyvertical/smrt-
|
|
52
|
-
"@happyvertical/smrt-
|
|
38
|
+
"@happyvertical/smrt-app-cli": "0.53.5",
|
|
39
|
+
"@happyvertical/smrt-app-mcp": "0.53.5",
|
|
40
|
+
"@happyvertical/smrt-app-runtime": "0.53.5",
|
|
41
|
+
"@happyvertical/smrt-assets": "0.53.5",
|
|
42
|
+
"@happyvertical/smrt-cli": "0.53.5",
|
|
43
|
+
"@happyvertical/smrt-config": "0.53.5",
|
|
44
|
+
"@happyvertical/smrt-jobs": "0.53.5",
|
|
45
|
+
"@happyvertical/smrt-mcp-openai": "0.53.5",
|
|
46
|
+
"@happyvertical/smrt-playbooks": "0.53.5",
|
|
47
|
+
"@happyvertical/smrt-profiles": "0.53.5",
|
|
48
|
+
"@happyvertical/smrt-scanner": "0.53.5",
|
|
49
|
+
"@happyvertical/smrt-svelte": "0.53.5",
|
|
50
|
+
"@happyvertical/smrt-tenancy": "0.53.5",
|
|
51
|
+
"@happyvertical/smrt-ui": "0.53.5",
|
|
52
|
+
"@happyvertical/smrt-users": "0.53.5",
|
|
53
|
+
"@happyvertical/smrt-web": "0.53.5",
|
|
53
54
|
"@happyvertical/sql": "^0.89.12",
|
|
55
|
+
"@modelcontextprotocol/client": "2.0.0",
|
|
54
56
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
55
57
|
"@playwright/test": "^1.63.0",
|
|
56
58
|
"@sveltejs/adapter-node": "^5.5.7",
|
package/template/AGENTS.md
CHANGED
|
@@ -24,10 +24,14 @@ tools, WebMCP definitions, and agent/developer knowledge artifacts.
|
|
|
24
24
|
- Server composition (profile, storage custody, owner bootstrap, tenant
|
|
25
25
|
selection, session, health, diagnostics) comes from
|
|
26
26
|
`@happyvertical/smrt-app-runtime/sveltekit`, configured once in
|
|
27
|
-
`src/lib/server/smrt.ts
|
|
28
|
-
(`selectTenant`, `classOverrides`,
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
`src/lib/server/smrt.ts`, which holds only `runtime` and its options. Pass
|
|
28
|
+
app-specific behaviour as options (`selectTenant`, `classOverrides`,
|
|
29
|
+
`session`, `prepareDatabase`); do not reimplement hooks, re-add the
|
|
30
|
+
`smrt-register` import (the `smrt()` plugin injects it), or export
|
|
31
|
+
`getCollection` (generated routes import this module and use its
|
|
32
|
+
`runtime.getCollection()`; a legacy export would win, deprecated).
|
|
33
|
+
`runtime.getCollection()`/`runtime.classOptions()` are request-scoped: never
|
|
34
|
+
keep their result beyond the request.
|
|
31
35
|
- Recover an interrupted unclaimed owner invitation with `pnpm app:stop`, then
|
|
32
36
|
`pnpm app:recover`, `pnpm app:start`, and `pnpm app:open`; never print or copy
|
|
33
37
|
its token.
|
package/template/README.md
CHANGED
|
@@ -157,8 +157,14 @@ changes. That helper checks active membership and rotates the session ID; never
|
|
|
157
157
|
copy an untrusted header directly into `locals.tenantId` or
|
|
158
158
|
`enterTenantContext()`.
|
|
159
159
|
|
|
160
|
-
`
|
|
161
|
-
|
|
160
|
+
`src/lib/server/smrt.ts` holds only the runtime and its options. The `smrt()`
|
|
161
|
+
plugin registers the generated objects before it runs, and each generated API
|
|
162
|
+
route imports this module and resolves collections through its exported
|
|
163
|
+
`runtime.getCollection()` (a `getCollection` export here would take precedence,
|
|
164
|
+
deprecated for one release, so do not add one). In the local profile
|
|
165
|
+
the runtime holds the single-writer lease (`acquireWriterLease: false` opts
|
|
166
|
+
out). `runtime.getCollection()` and `runtime.classOptions()` are
|
|
167
|
+
request-scoped (under `database-rls` isolation they carry the request
|
|
162
168
|
transaction): call them inside a request and never keep the result.
|
|
163
169
|
|
|
164
170
|
Set `TENANT_BASE_DOMAIN` for deployed subdomain routing. The fallback parser is
|
|
@@ -185,10 +191,10 @@ import {
|
|
|
185
191
|
RoleCollection,
|
|
186
192
|
syncPermissionCatalog,
|
|
187
193
|
} from '@happyvertical/smrt-users';
|
|
188
|
-
import {
|
|
194
|
+
import { runtime } from '$lib/server/smrt';
|
|
189
195
|
|
|
190
|
-
await syncPermissionCatalog(
|
|
191
|
-
const roles = await RoleCollection.create(
|
|
196
|
+
await syncPermissionCatalog(runtime.classOptions('Permission'));
|
|
197
|
+
const roles = await RoleCollection.create(runtime.classOptions('Role'));
|
|
192
198
|
await roles.seedSystemRoles({ seedPermissions: true });
|
|
193
199
|
```
|
|
194
200
|
|
|
@@ -219,7 +225,7 @@ export const load: PageServerLoad = async ({ depends, locals }) => {
|
|
|
219
225
|
return { items: [] };
|
|
220
226
|
}
|
|
221
227
|
|
|
222
|
-
const items = await getCollection<Item>('Item');
|
|
228
|
+
const items = await runtime.getCollection<Item>('Item');
|
|
223
229
|
const rows = await items.list({ limit: 50 });
|
|
224
230
|
return {
|
|
225
231
|
items: rows.flatMap((item) =>
|
|
@@ -427,15 +433,30 @@ web/workers and set `SMRT_MAINTENANCE_MODE=true`. For domain-specific
|
|
|
427
433
|
transformations, add a `scripts/smrt-portability.mjs` adapter; when present it
|
|
428
434
|
replaces `smrt app export`/`import`'s built-in one.
|
|
429
435
|
|
|
430
|
-
###
|
|
436
|
+
### MCP authorization
|
|
437
|
+
|
|
438
|
+
In the `local` profile, the opt-in MCP route accepts two credentials. One is
|
|
439
|
+
the signed browser session. The other is an owner-minted bearer token for a
|
|
440
|
+
local MCP client such as Claude Desktop through `smrt-mcp-bridge`:
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
pnpm exec smrt app token --scopes items.read --label "Claude Desktop"
|
|
444
|
+
pnpm exec smrt app token list
|
|
445
|
+
pnpm exec smrt app token revoke <id>
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
The token is printed once and only its hash is stored. It is bound to the
|
|
449
|
+
owner and the owner's workspace. It expires after 30 days unless you pass
|
|
450
|
+
`--expires`, up to 365 days. It never carries a permission the owner no longer
|
|
451
|
+
holds. Point the bridge at `/api/mcp` with `<PREFIX>_SERVER_URL` and
|
|
452
|
+
`<PREFIX>_TOKEN`; see the `@happyvertical/smrt-app-cli` README.
|
|
431
453
|
|
|
432
|
-
The opt-in MCP route uses the local signed session only in the `local` profile.
|
|
433
454
|
For `self-hosted` and `cloud`, configure HTTPS `SMRT_MCP_RESOURCE`,
|
|
434
455
|
`SMRT_MCP_ISSUER`, `SMRT_MCP_JWKS_URI`, and space-separated
|
|
435
|
-
`SMRT_MCP_SCOPES` values.
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
dispatch.
|
|
456
|
+
`SMRT_MCP_SCOPES` values. On every request, the runtime maps the verified
|
|
457
|
+
issuer and subject to the user linked through OIDC login. That user must have
|
|
458
|
+
exactly one active tenant membership. Disabled, unmapped, or multi-tenant
|
|
459
|
+
identities are denied. To use a different lookup, pass `resolvePrincipal` to
|
|
460
|
+
`createHostedMcpResourceAuth`. The route never derives tenant authority from
|
|
461
|
+
JWT claims, request headers, or tool arguments. Missing configuration fails
|
|
462
|
+
closed before MCP dispatch.
|
package/template/package.json
CHANGED
|
@@ -39,19 +39,19 @@
|
|
|
39
39
|
"svelte-check": "^4.7.6",
|
|
40
40
|
"typescript": "6.0.3",
|
|
41
41
|
"vite": "8.3.0",
|
|
42
|
-
"@happyvertical/smrt-cli": "^0.53.
|
|
42
|
+
"@happyvertical/smrt-cli": "^0.53.5"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@happyvertical/smrt-app-runtime": "^0.53.
|
|
46
|
-
"@happyvertical/smrt-config": "^0.53.
|
|
47
|
-
"@happyvertical/smrt-core": "^0.53.
|
|
48
|
-
"@happyvertical/smrt-jobs": "^0.53.
|
|
49
|
-
"@happyvertical/smrt-profiles": "^0.53.
|
|
50
|
-
"@happyvertical/smrt-svelte": "^0.53.
|
|
51
|
-
"@happyvertical/smrt-tenancy": "^0.53.
|
|
52
|
-
"@happyvertical/smrt-ui": "^0.53.
|
|
53
|
-
"@happyvertical/smrt-users": "^0.53.
|
|
54
|
-
"@happyvertical/smrt-web": "^0.53.
|
|
45
|
+
"@happyvertical/smrt-app-runtime": "^0.53.5",
|
|
46
|
+
"@happyvertical/smrt-config": "^0.53.5",
|
|
47
|
+
"@happyvertical/smrt-core": "^0.53.5",
|
|
48
|
+
"@happyvertical/smrt-jobs": "^0.53.5",
|
|
49
|
+
"@happyvertical/smrt-profiles": "^0.53.5",
|
|
50
|
+
"@happyvertical/smrt-svelte": "^0.53.5",
|
|
51
|
+
"@happyvertical/smrt-tenancy": "^0.53.5",
|
|
52
|
+
"@happyvertical/smrt-ui": "^0.53.5",
|
|
53
|
+
"@happyvertical/smrt-users": "^0.53.5",
|
|
54
|
+
"@happyvertical/smrt-web": "^0.53.5",
|
|
55
55
|
"@happyvertical/sql": "^0.89.12",
|
|
56
56
|
"@modelcontextprotocol/server": "^2.0.0"
|
|
57
57
|
}
|
package/template/smrt.config.ts
CHANGED
|
@@ -5,27 +5,10 @@
|
|
|
5
5
|
* See: https://github.com/happyvertical/smrt
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
resolveApplicationId,
|
|
10
|
-
resolveLocalRuntimePaths,
|
|
11
|
-
} from '@happyvertical/smrt-app-runtime';
|
|
12
|
-
|
|
13
8
|
const profile = (process.env.SMRT_RUNTIME_PROFILE || 'local') as
|
|
14
9
|
| 'local'
|
|
15
10
|
| 'self-hosted'
|
|
16
11
|
| 'cloud';
|
|
17
|
-
const appId = resolveApplicationId({
|
|
18
|
-
sourceRoot: process.cwd(),
|
|
19
|
-
explicitId: process.env.SMRT_APP_ID,
|
|
20
|
-
});
|
|
21
|
-
const localDatabase =
|
|
22
|
-
profile === 'local'
|
|
23
|
-
? resolveLocalRuntimePaths({
|
|
24
|
-
appId,
|
|
25
|
-
dataDirectory: process.env.SMRT_DATA_DIR,
|
|
26
|
-
sourceRoot: process.cwd(),
|
|
27
|
-
}).database
|
|
28
|
-
: undefined;
|
|
29
12
|
|
|
30
13
|
export default {
|
|
31
14
|
// This is the single infrastructure selector for the application. Domain
|
|
@@ -58,22 +41,8 @@ export default {
|
|
|
58
41
|
},
|
|
59
42
|
},
|
|
60
43
|
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
database: {
|
|
66
|
-
type:
|
|
67
|
-
profile === 'local' ? 'sqlite' : 'postgres',
|
|
68
|
-
// `smrt app` sets DATABASE_URL to the profile-owned data path before
|
|
69
|
-
// migrations. Deployed profiles require an operator-owned URL.
|
|
70
|
-
url:
|
|
71
|
-
profile === 'local'
|
|
72
|
-
? process.env.DATABASE_URL || localDatabase
|
|
73
|
-
: process.env.DATABASE_URL,
|
|
74
|
-
},
|
|
75
|
-
verbose: false,
|
|
76
|
-
},
|
|
77
|
-
|
|
78
|
-
},
|
|
44
|
+
// No `packages.cli.database`: `smrt app setup` / `smrt app migrate` hand
|
|
45
|
+
// `smrt db:migrate` the profile-owned database through DATABASE_URL and
|
|
46
|
+
// DATABASE_TYPE (local: the SQLite file in the user data root; deployed: the
|
|
47
|
+
// operator's URL). See the smrt-cli README for the precedence.
|
|
79
48
|
};
|
|
@@ -1,87 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The application's SMRT runtime
|
|
2
|
+
* The application's SMRT runtime: app options only.
|
|
3
3
|
*
|
|
4
4
|
* `runtime` composes the configured profile (local SQLite or deployed
|
|
5
|
-
* PostgreSQL), storage custody, the owner bootstrap, tenant selection,
|
|
6
|
-
* signed session
|
|
7
|
-
*
|
|
5
|
+
* PostgreSQL), storage custody, the owner bootstrap, tenant selection, the
|
|
6
|
+
* signed session, and (local profile) the single-writer lease shared with
|
|
7
|
+
* `smrt app` operations; `src/hooks.server.ts` mounts its `handle` and `init`.
|
|
8
|
+
* The smrt() Vite plugin registers the generated objects before this module
|
|
9
|
+
* runs, and generated API routes resolve collections through
|
|
10
|
+
* `runtime.getCollection()`.
|
|
11
|
+
*
|
|
12
|
+
* `runtime.getCollection()` and `runtime.classOptions()` are request-scoped:
|
|
13
|
+
* under `database-rls` isolation they carry the request's transaction, so
|
|
14
|
+
* call them per request and never keep the result.
|
|
8
15
|
*/
|
|
9
16
|
|
|
10
|
-
import {
|
|
11
|
-
import { join } from 'node:path';
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
acquireWriterLease,
|
|
15
|
-
createProviderReadinessProbe,
|
|
16
|
-
prepareApplicationStateRoot,
|
|
17
|
-
} from '@happyvertical/smrt-app-runtime';
|
|
17
|
+
import { createProviderReadinessProbe } from '@happyvertical/smrt-app-runtime';
|
|
18
18
|
import { createSmrtSvelteKitRuntime } from '@happyvertical/smrt-app-runtime/sveltekit';
|
|
19
|
-
import {
|
|
20
|
-
ObjectRegistry,
|
|
21
|
-
type SmrtClassOptions,
|
|
22
|
-
type SmrtObject,
|
|
23
|
-
} from '@happyvertical/smrt-core';
|
|
24
|
-
import { loadManifestFromPathSync } from '@happyvertical/smrt-core/manifest';
|
|
25
|
-
|
|
26
|
-
// Import all SMRT objects to register them.
|
|
27
|
-
import '../objects/index.js';
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Whether `error` reports that the generated register module itself is
|
|
31
|
-
* absent. Only the missing module named by the error counts (Node's `url`,
|
|
32
|
-
* or `Cannot find module '<path>'` from Node and Vite's module runner); a
|
|
33
|
-
* package or file that an existing generated module imports is a different
|
|
34
|
-
* missing module and is never matched.
|
|
35
|
-
*/
|
|
36
|
-
function isMissingRegisterModule(error: unknown): boolean {
|
|
37
|
-
const { code, url, message } = (error ?? {}) as {
|
|
38
|
-
code?: unknown;
|
|
39
|
-
url?: unknown;
|
|
40
|
-
message?: unknown;
|
|
41
|
-
};
|
|
42
|
-
if (code !== 'ERR_MODULE_NOT_FOUND') return false;
|
|
43
|
-
const missing =
|
|
44
|
-
typeof url === 'string'
|
|
45
|
-
? url
|
|
46
|
-
: typeof message === 'string'
|
|
47
|
-
? /^Cannot find module '([^']+)'/.exec(message)?.[1]
|
|
48
|
-
: undefined;
|
|
49
|
-
if (!missing) return false;
|
|
50
|
-
const path = missing.replace(/^file:\/\//, '').replace(/[?#].*$/, '');
|
|
51
|
-
return /\/lib\/server\/smrt-register\.(?:js|ts)$/.test(path);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Upgrade those registrations to package-qualified identities
|
|
55
|
-
// (`<package>:<Class>`) — required to link each class to its scanned field
|
|
56
|
-
// metadata below. The `smrt()` Vite plugin generates this module on every
|
|
57
|
-
// dev/build run (it is gitignored, like the generated API routes); guard the
|
|
58
|
-
// import so a fresh checkout that has not run the plugin yet still boots.
|
|
59
|
-
try {
|
|
60
|
-
// @ts-ignore generated by the smrt() Vite plugin before a build starts
|
|
61
|
-
await import('./smrt-register.js');
|
|
62
|
-
} catch (error) {
|
|
63
|
-
// Not generated yet — the first `pnpm dev` / `pnpm build` creates it. Any
|
|
64
|
-
// other failure, including a missing dependency of an existing generated
|
|
65
|
-
// module (an uninstalled extension package), must fail startup.
|
|
66
|
-
if (!isMissingRegisterModule(error)) throw error;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Hydrate field metadata for this app's own objects from the local manifest
|
|
70
|
-
// (`.smrt/manifest.json`). Without it the runtime only knows the framework
|
|
71
|
-
// base fields and server-side writes would silently drop your domain columns.
|
|
72
|
-
const localManifestPath = join(process.cwd(), '.smrt', 'manifest.json');
|
|
73
|
-
if (existsSync(localManifestPath)) {
|
|
74
|
-
loadManifestFromPathSync(localManifestPath);
|
|
75
|
-
}
|
|
76
19
|
|
|
77
20
|
export const runtime = createSmrtSvelteKitRuntime({
|
|
78
|
-
// Every local web writer (`pnpm dev`, `pnpm app:start`) holds the same
|
|
79
|
-
// single-writer lease as `smrt app` operations, so imports and backups
|
|
80
|
-
// fail closed while the app is running.
|
|
81
|
-
acquireWriterLease: (context) =>
|
|
82
|
-
acquireWriterLease(prepareApplicationStateRoot(context), {
|
|
83
|
-
operationInstance: process.env.SMRT_OPERATION_INSTANCE,
|
|
84
|
-
}),
|
|
85
21
|
// Deployed profiles prove authentication, asset, and secret readiness
|
|
86
22
|
// through the installed modules named by SMRT_*_READINESS_MODULE.
|
|
87
23
|
providerReadiness: createProviderReadinessProbe,
|
|
@@ -89,23 +25,3 @@ export const runtime = createSmrtSvelteKitRuntime({
|
|
|
89
25
|
// AuditLog: { db: { type: 'postgres', url: process.env.AUDIT_DB_URL! } }
|
|
90
26
|
classOverrides: {},
|
|
91
27
|
});
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Collection options for a class. Call per request and never keep the result:
|
|
95
|
-
* under `database-rls` isolation it carries the request's transaction.
|
|
96
|
-
*/
|
|
97
|
-
export function getSmrtConfig(className: string): SmrtClassOptions {
|
|
98
|
-
return runtime.classOptions(className);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Get a collection for a SMRT class, scoped to the current request.
|
|
103
|
-
*
|
|
104
|
-
* const items = await getCollection<Item>('Item');
|
|
105
|
-
*/
|
|
106
|
-
export async function getCollection<T extends SmrtObject>(className: string) {
|
|
107
|
-
return await ObjectRegistry.getCollection<T>(
|
|
108
|
-
className,
|
|
109
|
-
runtime.classOptions(className),
|
|
110
|
-
);
|
|
111
|
-
}
|
|
@@ -2,7 +2,7 @@ import { assertOperationPermission } from '@happyvertical/smrt-users';
|
|
|
2
2
|
import { fail } from '@sveltejs/kit';
|
|
3
3
|
|
|
4
4
|
import type { Item } from '$lib/objects/Item';
|
|
5
|
-
import {
|
|
5
|
+
import { runtime } from '$lib/server/smrt';
|
|
6
6
|
import type { Actions, PageServerLoad } from './$types';
|
|
7
7
|
|
|
8
8
|
const READ_PERMISSION = 'items.read';
|
|
@@ -38,7 +38,7 @@ export const load: PageServerLoad = async ({ depends, locals }) => {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
try {
|
|
41
|
-
const items = await getCollection<Item>('Item');
|
|
41
|
+
const items = await runtime.getCollection<Item>('Item');
|
|
42
42
|
const rows = await items.list({
|
|
43
43
|
orderBy: 'created_at DESC',
|
|
44
44
|
limit: 50,
|
|
@@ -72,7 +72,7 @@ export const actions: Actions = {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const decision = await assertOperationPermission({
|
|
75
|
-
...
|
|
75
|
+
...runtime.classOptions('Item'),
|
|
76
76
|
action: 'create',
|
|
77
77
|
collection: 'items',
|
|
78
78
|
onDeny: 'return',
|
|
@@ -91,7 +91,7 @@ export const actions: Actions = {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
try {
|
|
94
|
-
const items = await getCollection<Item>('Item');
|
|
94
|
+
const items = await runtime.getCollection<Item>('Item');
|
|
95
95
|
await items.create({ title });
|
|
96
96
|
return { created: title };
|
|
97
97
|
} catch (error) {
|
|
@@ -1,23 +1,9 @@
|
|
|
1
|
-
import { rmSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
|
|
4
|
-
import { resolveApplicationStateRoot } from '@happyvertical/smrt-app-runtime';
|
|
5
1
|
import { createOwnerSetupPage } from '@happyvertical/smrt-app-runtime/sveltekit';
|
|
6
2
|
|
|
7
3
|
import { runtime } from '$lib/server/smrt';
|
|
8
4
|
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const stateRoot = resolveApplicationStateRoot({
|
|
15
|
-
appId: runtime.applicationId(),
|
|
16
|
-
dataDirectory: process.env.SMRT_DATA_DIR,
|
|
17
|
-
sourceRoot: process.cwd(),
|
|
18
|
-
});
|
|
19
|
-
for (const file of ['onboarding.json', 'onboarding-launch.html']) {
|
|
20
|
-
rmSync(join(stateRoot, file), { force: true });
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
});
|
|
5
|
+
/**
|
|
6
|
+
* Local-profile, loopback-only, single-use owner bootstrap. The runtime
|
|
7
|
+
* removes the `pnpm app:setup` onboarding hand-off files once the claim commits.
|
|
8
|
+
*/
|
|
9
|
+
export const { load, actions } = createOwnerSetupPage(runtime);
|
package/template.config.js
CHANGED
|
@@ -19,22 +19,22 @@ export default {
|
|
|
19
19
|
|
|
20
20
|
// Dependencies to add to generated project
|
|
21
21
|
dependencies: {
|
|
22
|
-
'@happyvertical/smrt-app-runtime': '^0.53.
|
|
23
|
-
'@happyvertical/smrt-config': '^0.53.
|
|
24
|
-
'@happyvertical/smrt-core': '^0.53.
|
|
25
|
-
'@happyvertical/smrt-jobs': '^0.53.
|
|
26
|
-
'@happyvertical/smrt-profiles': '^0.53.
|
|
27
|
-
'@happyvertical/smrt-svelte': '^0.53.
|
|
28
|
-
'@happyvertical/smrt-tenancy': '^0.53.
|
|
29
|
-
'@happyvertical/smrt-ui': '^0.53.
|
|
30
|
-
'@happyvertical/smrt-users': '^0.53.
|
|
31
|
-
'@happyvertical/smrt-web': '^0.53.
|
|
22
|
+
'@happyvertical/smrt-app-runtime': '^0.53.5',
|
|
23
|
+
'@happyvertical/smrt-config': '^0.53.5',
|
|
24
|
+
'@happyvertical/smrt-core': '^0.53.5',
|
|
25
|
+
'@happyvertical/smrt-jobs': '^0.53.5',
|
|
26
|
+
'@happyvertical/smrt-profiles': '^0.53.5',
|
|
27
|
+
'@happyvertical/smrt-svelte': '^0.53.5',
|
|
28
|
+
'@happyvertical/smrt-tenancy': '^0.53.5',
|
|
29
|
+
'@happyvertical/smrt-ui': '^0.53.5',
|
|
30
|
+
'@happyvertical/smrt-users': '^0.53.5',
|
|
31
|
+
'@happyvertical/smrt-web': '^0.53.5',
|
|
32
32
|
'@happyvertical/sql': '^0.89.12',
|
|
33
33
|
'@modelcontextprotocol/server': '^2.0.0',
|
|
34
34
|
},
|
|
35
35
|
|
|
36
36
|
devDependencies: {
|
|
37
|
-
'@happyvertical/smrt-cli': '^0.53.
|
|
37
|
+
'@happyvertical/smrt-cli': '^0.53.5',
|
|
38
38
|
'@sveltejs/adapter-node': '^5.5.7',
|
|
39
39
|
'@sveltejs/kit': '2.70.3',
|
|
40
40
|
'@sveltejs/vite-plugin-svelte': '^7.3.0',
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { McpVerifiedIdentity, McpPrincipalMapping } from '@happyvertical/smrt-app-mcp/auth';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Deployments must replace this binding with a database-backed current-account
|
|
5
|
-
* and active-membership lookup. Token claims never select a tenant.
|
|
6
|
-
*/
|
|
7
|
-
export type HostedMcpPrincipalResolver = (
|
|
8
|
-
identity: McpVerifiedIdentity,
|
|
9
|
-
) => Promise<McpPrincipalMapping | null>;
|
|
10
|
-
|
|
11
|
-
export const resolveHostedMcpPrincipal: HostedMcpPrincipalResolver | undefined =
|
|
12
|
-
undefined;
|