@proveanything/smartlinks 2.0.3 → 2.0.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.
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.0.3 | Generated: 2026-09-20T09:34:31.596Z
3
+ Version: 2.0.4 | Generated: 2026-09-20T10:21:15.062Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -312,6 +312,24 @@ See the **[Executor Model guide](executor.md)** for the full build setup, SEO co
312
312
  | `llmContent.function` | string | Name of the `getLLMContent` export |
313
313
  | `llmContent.timeout` | number | Timeout in ms (default 500) |
314
314
 
315
+ #### `functions`
316
+
317
+ Declares app-authored **server functions** — server-side handlers `(ctx, event) ⇒ result`
318
+ (http / event / cron), shipped in a self-contained UMD bundle. Omit if the app has no server
319
+ functions. The security model (`visibility`/`authority`/`capabilities`), the runtime surface, how
320
+ to build the bundle, and how to invoke are all in the **[Server Functions guide](server-functions.md)** —
321
+ this is just the manifest shape.
322
+
323
+ | Field | Type | Description |
324
+ |-------|------|-------------|
325
+ | `files.js.umd` | string | UMD bundle path (e.g. `dist/functions.umd.js`) — a self-contained bundle exporting each handler by name |
326
+ | `definitions[]` | object[] | One entry per function |
327
+ | `definitions[].name` | string | Function name; also the handler export name (override with `handler`) |
328
+ | `definitions[].trigger` | object | `{ type: "http"|"event"|"cron", … }` |
329
+ | `definitions[].visibility` | string | `admin` \| `public` (http only) — who may call it |
330
+ | `definitions[].authority` | string | `caller` \| `collection` — whose authority it runs as |
331
+ | `definitions[].capabilities` | string[] | Least-privilege grants (`sl:<res>:<read\|write>`, `network[:<host>]`, `secrets:<ref>`) |
332
+
315
333
  ---
316
334
 
317
335
  ## `app.admin.json`
@@ -16,6 +16,31 @@ This guide covers registration and how to wire it into your build.
16
16
 
17
17
  ---
18
18
 
19
+ ## Fast dev publish (recommended for dev)
20
+
21
+ For the **dev** channel there's a one-step path that does *both* halves — SmartLinks **hosts and
22
+ registers** — so you don't host bundles anywhere yourself (no Lovable, no CDN of your own) and
23
+ don't run a separate register step:
24
+
25
+ ```bash
26
+ # build your app first (produces dist/ with app.manifest.json), then:
27
+ smartlinks-publish # uploads dist/ → SmartLinks hosts it + registers the dev release
28
+ smartlinks-publish --watch # re-publish on every change (save → live)
29
+ ```
30
+
31
+ Env: `SMARTLINKS_APP_ID` (your platform id), `SMARTLINKS_DEPLOY_KEY` (dev key — dev-only, safe to
32
+ keep locally), optional `SMARTLINKS_API` / `--dir`. It uploads the built files to
33
+ `POST /api/v1/apps/{appId}/publish?channel=dev`; the server validates the manifest + functions,
34
+ writes the bundle to `smartlinks.app/apps/{appId}/dev/…` with **`no-store` caching** (every reload
35
+ is instantly fresh — no cache-busting, no invalidation), and registers the release. It works from
36
+ **anywhere** — local, Claude, CI, or a build step — because the builder builds and the platform
37
+ hosts; there's no dependency on any external host for dev.
38
+
39
+ Use this for dev. Use the **register-a-URL** flow below when *you* host the bundle (a beta/stable
40
+ CDN deploy, or an external host) and just want to register where it lives + its manifest.
41
+
42
+ ---
43
+
19
44
  ## Two "wheres": the API host vs the bundle CDN
20
45
 
21
46
  Deploying touches two different locations — keep them distinct:
@@ -63,6 +63,8 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
63
63
  | **Containers** | `docs/containers.md` | Building full-app embeddable containers (lazy-loaded) |
64
64
  | **Mobile Admin Container** | `docs/mobile-admin-container.md` | Building a separate Capacitor-aware mobile admin bundle for field operators |
65
65
  | **Executors** | `docs/executor.md` | Building executor bundles for SEO, LLM content, programmatic config |
66
+ | **Server Functions** | `docs/server-functions.md` | App-authored server-side functions `(ctx, event) ⇒ result`: security model, runtime surface, invoking |
67
+ | **Deploying & Registering** | `docs/deploying-apps.md` | Getting your app into the platform: fast dev publish, channels, deploy keys, registering releases |
66
68
  | **Deep Linking** | `docs/deep-link-discovery.md` | URL state management, navigable states, portal menus, AI nav |
67
69
  | **Portal Back Button** | `docs/portal-back-button.md` | Hierarchy-aware "up" navigation inside embedded apps |
68
70
  | **Portal Request Action** | `docs/portal-request-action.md` | Triggering portal built-in actions (__qrScanner, __share, __logout, etc.) from sub-apps |
@@ -63,6 +63,47 @@ in a bundle alongside your widgets/containers:
63
63
 
64
64
  Each definition maps to an exported handler of the same `name` (override with `handler`).
65
65
 
66
+ ## Building the functions bundle
67
+
68
+ The handlers ship as a **self-contained UMD** — one file, all dependencies compiled in, no
69
+ `require`/`import` of platform or Node modules (see [Runtime](#runtime--what-your-function-can-use)).
70
+ Your functions entry just **named-exports** each handler:
71
+
72
+ ```js
73
+ // src/functions/index.js — the functions entry
74
+ export async function submitCompetitionEntry(ctx, event) { /* … */ }
75
+ export async function onProofCreated(ctx, event) { /* … */ }
76
+ ```
77
+
78
+ Build it to `dist/functions.umd.js` with a Vite lib build — **UMD format, nothing externalised**
79
+ (so it's self-contained), and don't wipe the widgets you built into the same `dist/`:
80
+
81
+ ```ts
82
+ // vite.functions.config.ts
83
+ import { defineConfig } from 'vite'
84
+ export default defineConfig({
85
+ build: {
86
+ lib: { entry: 'src/functions/index.js', formats: ['umd'], name: 'functions',
87
+ fileName: () => 'functions.umd.js' },
88
+ outDir: 'dist',
89
+ emptyOutDir: false, // co-exist with the widgets/containers build
90
+ rollupOptions: { external: [] } // bundle everything — no Node/platform externals
91
+ },
92
+ })
93
+ ```
94
+
95
+ ```jsonc
96
+ // package.json — build widgets/containers as usual, then the functions bundle
97
+ "scripts": {
98
+ "build": "vite build && vite build --config vite.functions.config.ts"
99
+ }
100
+ ```
101
+
102
+ That emits `dist/functions.umd.js` (matching `functions.files.js.umd`), whose named exports the
103
+ platform pairs with your manifest `definitions`. Then **deploy** it — for dev,
104
+ `smartlinks-publish` uploads `dist/` and registers in one step (see
105
+ [Deploying & registering](deploying-apps.md)).
106
+
66
107
  ---
67
108
 
68
109
  ## The two security questions every function answers
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.0.3 | Generated: 2026-09-20T09:34:31.596Z
3
+ Version: 2.0.4 | Generated: 2026-09-20T10:21:15.062Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -312,6 +312,24 @@ See the **[Executor Model guide](executor.md)** for the full build setup, SEO co
312
312
  | `llmContent.function` | string | Name of the `getLLMContent` export |
313
313
  | `llmContent.timeout` | number | Timeout in ms (default 500) |
314
314
 
315
+ #### `functions`
316
+
317
+ Declares app-authored **server functions** — server-side handlers `(ctx, event) ⇒ result`
318
+ (http / event / cron), shipped in a self-contained UMD bundle. Omit if the app has no server
319
+ functions. The security model (`visibility`/`authority`/`capabilities`), the runtime surface, how
320
+ to build the bundle, and how to invoke are all in the **[Server Functions guide](server-functions.md)** —
321
+ this is just the manifest shape.
322
+
323
+ | Field | Type | Description |
324
+ |-------|------|-------------|
325
+ | `files.js.umd` | string | UMD bundle path (e.g. `dist/functions.umd.js`) — a self-contained bundle exporting each handler by name |
326
+ | `definitions[]` | object[] | One entry per function |
327
+ | `definitions[].name` | string | Function name; also the handler export name (override with `handler`) |
328
+ | `definitions[].trigger` | object | `{ type: "http"|"event"|"cron", … }` |
329
+ | `definitions[].visibility` | string | `admin` \| `public` (http only) — who may call it |
330
+ | `definitions[].authority` | string | `caller` \| `collection` — whose authority it runs as |
331
+ | `definitions[].capabilities` | string[] | Least-privilege grants (`sl:<res>:<read\|write>`, `network[:<host>]`, `secrets:<ref>`) |
332
+
315
333
  ---
316
334
 
317
335
  ## `app.admin.json`
@@ -16,6 +16,31 @@ This guide covers registration and how to wire it into your build.
16
16
 
17
17
  ---
18
18
 
19
+ ## Fast dev publish (recommended for dev)
20
+
21
+ For the **dev** channel there's a one-step path that does *both* halves — SmartLinks **hosts and
22
+ registers** — so you don't host bundles anywhere yourself (no Lovable, no CDN of your own) and
23
+ don't run a separate register step:
24
+
25
+ ```bash
26
+ # build your app first (produces dist/ with app.manifest.json), then:
27
+ smartlinks-publish # uploads dist/ → SmartLinks hosts it + registers the dev release
28
+ smartlinks-publish --watch # re-publish on every change (save → live)
29
+ ```
30
+
31
+ Env: `SMARTLINKS_APP_ID` (your platform id), `SMARTLINKS_DEPLOY_KEY` (dev key — dev-only, safe to
32
+ keep locally), optional `SMARTLINKS_API` / `--dir`. It uploads the built files to
33
+ `POST /api/v1/apps/{appId}/publish?channel=dev`; the server validates the manifest + functions,
34
+ writes the bundle to `smartlinks.app/apps/{appId}/dev/…` with **`no-store` caching** (every reload
35
+ is instantly fresh — no cache-busting, no invalidation), and registers the release. It works from
36
+ **anywhere** — local, Claude, CI, or a build step — because the builder builds and the platform
37
+ hosts; there's no dependency on any external host for dev.
38
+
39
+ Use this for dev. Use the **register-a-URL** flow below when *you* host the bundle (a beta/stable
40
+ CDN deploy, or an external host) and just want to register where it lives + its manifest.
41
+
42
+ ---
43
+
19
44
  ## Two "wheres": the API host vs the bundle CDN
20
45
 
21
46
  Deploying touches two different locations — keep them distinct:
package/docs/overview.md CHANGED
@@ -63,6 +63,8 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
63
63
  | **Containers** | `docs/containers.md` | Building full-app embeddable containers (lazy-loaded) |
64
64
  | **Mobile Admin Container** | `docs/mobile-admin-container.md` | Building a separate Capacitor-aware mobile admin bundle for field operators |
65
65
  | **Executors** | `docs/executor.md` | Building executor bundles for SEO, LLM content, programmatic config |
66
+ | **Server Functions** | `docs/server-functions.md` | App-authored server-side functions `(ctx, event) ⇒ result`: security model, runtime surface, invoking |
67
+ | **Deploying & Registering** | `docs/deploying-apps.md` | Getting your app into the platform: fast dev publish, channels, deploy keys, registering releases |
66
68
  | **Deep Linking** | `docs/deep-link-discovery.md` | URL state management, navigable states, portal menus, AI nav |
67
69
  | **Portal Back Button** | `docs/portal-back-button.md` | Hierarchy-aware "up" navigation inside embedded apps |
68
70
  | **Portal Request Action** | `docs/portal-request-action.md` | Triggering portal built-in actions (__qrScanner, __share, __logout, etc.) from sub-apps |
@@ -63,6 +63,47 @@ in a bundle alongside your widgets/containers:
63
63
 
64
64
  Each definition maps to an exported handler of the same `name` (override with `handler`).
65
65
 
66
+ ## Building the functions bundle
67
+
68
+ The handlers ship as a **self-contained UMD** — one file, all dependencies compiled in, no
69
+ `require`/`import` of platform or Node modules (see [Runtime](#runtime--what-your-function-can-use)).
70
+ Your functions entry just **named-exports** each handler:
71
+
72
+ ```js
73
+ // src/functions/index.js — the functions entry
74
+ export async function submitCompetitionEntry(ctx, event) { /* … */ }
75
+ export async function onProofCreated(ctx, event) { /* … */ }
76
+ ```
77
+
78
+ Build it to `dist/functions.umd.js` with a Vite lib build — **UMD format, nothing externalised**
79
+ (so it's self-contained), and don't wipe the widgets you built into the same `dist/`:
80
+
81
+ ```ts
82
+ // vite.functions.config.ts
83
+ import { defineConfig } from 'vite'
84
+ export default defineConfig({
85
+ build: {
86
+ lib: { entry: 'src/functions/index.js', formats: ['umd'], name: 'functions',
87
+ fileName: () => 'functions.umd.js' },
88
+ outDir: 'dist',
89
+ emptyOutDir: false, // co-exist with the widgets/containers build
90
+ rollupOptions: { external: [] } // bundle everything — no Node/platform externals
91
+ },
92
+ })
93
+ ```
94
+
95
+ ```jsonc
96
+ // package.json — build widgets/containers as usual, then the functions bundle
97
+ "scripts": {
98
+ "build": "vite build && vite build --config vite.functions.config.ts"
99
+ }
100
+ ```
101
+
102
+ That emits `dist/functions.umd.js` (matching `functions.files.js.umd`), whose named exports the
103
+ platform pairs with your manifest `definitions`. Then **deploy** it — for dev,
104
+ `smartlinks-publish` uploads `dist/` and registers in one step (see
105
+ [Deploying & registering](deploying-apps.md)).
106
+
66
107
  ---
67
108
 
68
109
  ## The two security questions every function answers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proveanything/smartlinks",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,12 +15,14 @@
15
15
  }
16
16
  },
17
17
  "bin": {
18
- "smartlinks-register-release": "scripts/register-release.mjs"
18
+ "smartlinks-register-release": "scripts/register-release.mjs",
19
+ "smartlinks-publish": "scripts/publish.mjs"
19
20
  },
20
21
  "files": [
21
22
  "dist/",
22
23
  "docs/",
23
24
  "scripts/register-release.mjs",
25
+ "scripts/publish.mjs",
24
26
  "openapi.yaml",
25
27
  "README.md"
26
28
  ],
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * publish — the FAST dev publish for a SmartLinks app. Builds nothing itself: it takes an already-
4
+ * built `dist/`, uploads it to the SmartLinks publish endpoint, which writes it straight to the CDN
5
+ * (dev = no-store, instantly fresh) and registers the release. One call = host + register, no Cloud
6
+ * Build, no Lovable. Works from anywhere: local, Claude, CI, or a build step.
7
+ *
8
+ * smartlinks-publish # build dist/ yourself first, then this
9
+ * smartlinks-publish --watch # re-publish on file change (save → live)
10
+ *
11
+ * Env:
12
+ * SMARTLINKS_APP_ID the platform app id (required)
13
+ * SMARTLINKS_DEPLOY_KEY channel-scoped deploy key (dev key is dev-only, safe to keep locally)
14
+ * SMARTLINKS_API API host (default https://smartlinks.app)
15
+ * SMARTLINKS_CHANNEL channel (default dev; only dev is supported by this fast path today)
16
+ * Flags: --dir <dist> --watch --channel <dev>
17
+ */
18
+
19
+ import { readFileSync, readdirSync, statSync, watch } from 'node:fs'
20
+ import { join, relative, sep } from 'node:path'
21
+
22
+ const args = process.argv.slice(2)
23
+ const flag = (name, def) => { const i = args.indexOf(name); return i >= 0 ? (args[i + 1] && !args[i + 1].startsWith('--') ? args[i + 1] : true) : def }
24
+
25
+ const API = process.env.SMARTLINKS_API || 'https://smartlinks.app'
26
+ const KEY = process.env.SMARTLINKS_DEPLOY_KEY
27
+ const APP_ID = process.env.SMARTLINKS_APP_ID
28
+ const CHANNEL = flag('--channel', process.env.SMARTLINKS_CHANNEL || 'dev')
29
+ const DIR = flag('--dir', 'dist')
30
+ const WATCH = args.includes('--watch')
31
+
32
+ // Mirror of the server's write allowlist — a bundle is static web assets only.
33
+ const ALLOWED = new Set(['.js', '.mjs', '.css', '.json', '.map', '.wasm', '.html', '.txt',
34
+ '.svg', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif', '.ico', '.woff', '.woff2', '.ttf', '.otf'])
35
+ const extOf = (p) => { const i = p.lastIndexOf('.'); return i < 0 ? '' : p.slice(i).toLowerCase() }
36
+
37
+ function fail(msg) { console.error(`❌ ${msg}`); process.exit(1) }
38
+ if (!APP_ID) fail('SMARTLINKS_APP_ID is required')
39
+ if (!KEY) fail('SMARTLINKS_DEPLOY_KEY is required')
40
+
41
+ function collectFiles(dir) {
42
+ const out = {}
43
+ const walk = (d) => {
44
+ for (const e of readdirSync(d, { withFileTypes: true })) {
45
+ const full = join(d, e.name)
46
+ if (e.isDirectory()) { walk(full); continue }
47
+ if (!ALLOWED.has(extOf(e.name))) continue
48
+ const rel = relative(dir, full).split(sep).join('/') // forward slashes for the bucket
49
+ out[rel] = readFileSync(full).toString('base64')
50
+ }
51
+ }
52
+ walk(dir)
53
+ return out
54
+ }
55
+
56
+ async function publishOnce() {
57
+ let stat
58
+ try { stat = statSync(DIR) } catch { fail(`build dir not found: ${DIR} (build first)`) }
59
+ if (!stat.isDirectory()) fail(`${DIR} is not a directory`)
60
+
61
+ const files = collectFiles(DIR)
62
+ if (!files['app.manifest.json']) fail(`${DIR}/app.manifest.json not found — is this a built app bundle?`)
63
+
64
+ const t0 = Date.now()
65
+ const res = await fetch(`${API}/api/v1/apps/${APP_ID}/publish?channel=${encodeURIComponent(CHANNEL)}`, {
66
+ method: 'POST',
67
+ headers: { 'content-type': 'application/json', 'x-smartlinks-deploy-key': KEY },
68
+ body: JSON.stringify({ files }),
69
+ }).catch((e) => fail(`request failed — ${e.message}`))
70
+
71
+ const b = await res.json().catch(() => ({}))
72
+ if (!res.ok || !b.ok) {
73
+ console.error(`❌ publish failed (${res.status}):`)
74
+ for (const e of b.errors || []) console.error(` • ${e.path || ''}: ${e.message}`)
75
+ if (WATCH) return false // keep watching; don't exit
76
+ process.exit(1)
77
+ }
78
+ for (const w of b.warnings || []) console.warn(` ⚠︎ ${w.path}: ${w.message}`)
79
+ console.log(`✅ ${APP_ID}@${b.version} → ${CHANNEL} (${Object.keys(files).length} files, ${Date.now() - t0}ms) — functions: ${(b.functions || []).join(', ') || 'none'}`)
80
+ console.log(` ${b.bundleBaseUrl}`)
81
+ return true
82
+ }
83
+
84
+ if (!WATCH) {
85
+ await publishOnce()
86
+ } else {
87
+ console.log(`👀 watching ${DIR} — publishing ${APP_ID} to ${CHANNEL} on change (Ctrl+C to stop)`)
88
+ await publishOnce()
89
+ let timer = null
90
+ let publishing = false
91
+ watch(DIR, { recursive: true }, () => {
92
+ clearTimeout(timer)
93
+ timer = setTimeout(async () => {
94
+ if (publishing) return
95
+ publishing = true
96
+ try { await publishOnce() } finally { publishing = false }
97
+ }, 300) // debounce a burst of file writes from one build
98
+ })
99
+ }