@mortar-ai/skill 0.3.0 → 0.6.0
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/README.md +33 -31
- package/SKILL.md +106 -250
- package/bin/install.js +8 -3
- package/index.js +2 -0
- package/manifest.json +4 -4
- package/package.json +5 -5
- package/references/analytics.md +29 -0
- package/references/architecture.md +99 -0
- package/references/auth.md +33 -0
- package/references/automation.md +23 -0
- package/references/communications.md +16 -0
- package/references/control-plane.md +25 -0
- package/references/database.md +101 -0
- package/references/functions.md +43 -0
- package/references/realtime.md +33 -0
- package/references/schema-migrations.md +98 -0
- package/references/secrets.md +17 -0
- package/references/storage.md +53 -0
- package/references/usage-billing.md +27 -0
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# @mortar-ai/skill
|
|
2
2
|
|
|
3
3
|
[Agent Skills](https://agentskills.io/) package for building on **Mortar** (the
|
|
4
|
-
BaaS that AI bundles talk to). Install the canonical `SKILL.md`
|
|
5
|
-
agent's skill directory to teach it the SDK
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
BaaS that AI bundles talk to). Install the canonical `SKILL.md` and its
|
|
5
|
+
`references/` into a compatible agent's skill directory to teach it the SDK
|
|
6
|
+
core invariants plus on-demand capability references. The split keeps Fabric's
|
|
7
|
+
loaded prompt small without hiding the storage, realtime, functions, auth, or
|
|
8
|
+
database guidance when a task needs it.
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
@@ -17,8 +17,9 @@ mortar-skill-install
|
|
|
17
17
|
|
|
18
18
|
Then restart Claude Code (or `/skill reload`).
|
|
19
19
|
|
|
20
|
-
The same versioned `SKILL.md` is
|
|
21
|
-
binds the npm version and Harness preinstall to
|
|
20
|
+
The same versioned `SKILL.md` and `references/` package is built into Fabric
|
|
21
|
+
Harness. `manifest.json` binds the npm version and Harness preinstall to a
|
|
22
|
+
deterministic package SHA-256 digest; the Fabric
|
|
22
23
|
Server rejects a Market install when the running Harness reports different
|
|
23
24
|
bytes. npm is an external distribution channel and is not required by Fabric at
|
|
24
25
|
runtime.
|
|
@@ -32,28 +33,26 @@ experience.
|
|
|
32
33
|
|
|
33
34
|
The skill teaches Claude:
|
|
34
35
|
|
|
35
|
-
- **
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- **
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
- **
|
|
45
|
-
|
|
46
|
-
- **
|
|
47
|
-
|
|
48
|
-
- **
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
- **Anti-patterns** — assuming Fabric runs on Mortar; using `namespace`
|
|
56
|
-
instead of `tenant_id`; hand-rolling RLS the SDK already enforces
|
|
36
|
+
- **Core contract** — Fabric-owned client/config files, schema-first workflow,
|
|
37
|
+
one migration operation per call, supported migration kinds, access-policy
|
|
38
|
+
invariants, version boundaries, and completion checks.
|
|
39
|
+
- **Database reference** — CRUD, filters, atomic row mutations, trusted
|
|
40
|
+
cross-table transactions, and ACID boundaries.
|
|
41
|
+
- **Auth reference** — email/password, phone OTP, sessions, password reset, and
|
|
42
|
+
public-access policy choices.
|
|
43
|
+
- **Storage reference** — upload/download, signed URLs, immutable physical
|
|
44
|
+
versions, metadata pointers, CORS, and row/object consistency boundaries.
|
|
45
|
+
- **Realtime reference** — Postgres changes, broadcast, presence, unsubscribe,
|
|
46
|
+
and teardown.
|
|
47
|
+
- **Functions + automation** — invoke/deploy boundaries, cron, queues, and
|
|
48
|
+
transactional enqueue.
|
|
49
|
+
- **Analytics + operations** — capture/flush, usage, managed logs, primitive
|
|
50
|
+
billing attribution, and object-egress evidence.
|
|
51
|
+
- **Trusted services** — communications, encrypted secrets, accounts/projects,
|
|
52
|
+
keys/PATs, custom domains, top-up, and capacity.
|
|
53
|
+
- **Architecture reference** — all 9 infrastructure primitives, the open
|
|
54
|
+
capability registry, application modules, usage accounting, and
|
|
55
|
+
credential/trust surfaces.
|
|
57
56
|
|
|
58
57
|
## Custom install location
|
|
59
58
|
|
|
@@ -70,8 +69,9 @@ Idempotent — overwrites in place.
|
|
|
70
69
|
|
|
71
70
|
## Maintainer release
|
|
72
71
|
|
|
73
|
-
`SKILL.md`
|
|
74
|
-
and
|
|
72
|
+
`SKILL.md` and `references/` are the hand-edited instruction sources. Keep the
|
|
73
|
+
core short and move capability detail into a routed reference. Regenerate the
|
|
74
|
+
manifest and Harness copy before testing or publishing:
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
77
|
npm run sync
|
|
@@ -79,6 +79,8 @@ npm run check
|
|
|
79
79
|
npm pack --dry-run
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
+
The repository-level synchronizer is shared with Keel and fails on missing
|
|
83
|
+
configured reference directories, resource drift, or unexpected Harness files.
|
|
82
84
|
Never edit `fabric/harness/internal/skills/preinstalled/mortar/` directly.
|
|
83
85
|
|
|
84
86
|
## License
|
package/SKILL.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mortar
|
|
3
|
-
description: Build or change Fabric applications that use Mortar
|
|
3
|
+
description: Build or change Fabric applications that use Mortar capabilities such as database, auth, storage, realtime, functions, analytics, automation, or messaging. Use for Mortar schema and provisioning, SDK integration, access policy, transactions, or backend consistency; not for Fabric's own control plane or a different backend.
|
|
4
4
|
metadata:
|
|
5
5
|
activation: auto
|
|
6
6
|
priority: high
|
|
7
7
|
triggers:
|
|
8
|
-
- keywords: [database, 数据库, backend, 后端, auth, authentication, 登录, 认证, persistence, 持久化, transaction, 事务, ACID, storage, upload, 文件, realtime, 实时, collaboration, compute, cloud, 云端]
|
|
8
|
+
- keywords: [database, 数据库, backend, 后端, auth, authentication, 登录, 认证, persistence, 持久化, transaction, 事务, ACID, storage, upload, 文件, realtime, 实时, collaboration, function, compute, analytics, cron, queue, comms, secrets, billing, cloud, 云端]
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# Mortar for Fabric applications
|
|
@@ -52,257 +52,113 @@ below; if platform provisioning fails, report that platform failure.
|
|
|
52
52
|
|
|
53
53
|
1. Call `mortar_schema` before designing or changing backend data. It returns
|
|
54
54
|
the current live schema, including console changes.
|
|
55
|
-
2.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
`
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
`
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
.
|
|
104
|
-
|
|
105
|
-
.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
55
|
+
2. Author database schema as exact Mortar logical SQL and submit `{ name, sql }`
|
|
56
|
+
to `mortar_migrate`. Fabric asks Mortar to parse a language-neutral typed
|
|
57
|
+
plan, applies the workspace review policy, writes those exact bytes under
|
|
58
|
+
`mortar/migrations/` with source-revision CAS, then Mortar re-verifies the
|
|
59
|
+
checksum and expected schema revision and commits the whole plan atomically.
|
|
60
|
+
3. Use `mortar_resource` for non-database desired state. Function source and its
|
|
61
|
+
manifest live under `mortar/functions/<name>/`; bucket policy lives under
|
|
62
|
+
`mortar/storage/buckets/`; cron schedules live under `mortar/cron/`. Fabric
|
|
63
|
+
writes the approved artifact before reconciling the live resource.
|
|
64
|
+
4. These source directories are platform-protected because direct edits would
|
|
65
|
+
bypass plan/apply. Submit the exact source through the matching tool; do not
|
|
66
|
+
edit or create `mortar.config.json`. Do not edit generated
|
|
67
|
+
`integrations/mortar/types.ts`; Fabric regenerates it from the post-apply live
|
|
68
|
+
schema. The same schema can generate TypeScript, Swift, Kotlin, and Dart app
|
|
69
|
+
data types; those outputs are derived and never drive migration execution.
|
|
70
|
+
5. Install or update `@mortar-ai/client` with npm when needed and let npm own the
|
|
71
|
+
lockfile. Atomic batches, server-side filtered mutations, and
|
|
72
|
+
`DatabaseTransactions` require version 0.8.0 or newer. Do not rewrite
|
|
73
|
+
unrelated dependencies.
|
|
74
|
+
6. Before a multi-step mutation, choose the real consistency boundary. A
|
|
75
|
+
one-table batch or filtered mutation may use the public singleton;
|
|
76
|
+
cross-table row + queue transactions require trusted server code; object
|
|
77
|
+
storage and external services never join a PostgreSQL transaction.
|
|
78
|
+
7. Type-check and build. Handle every `{ data, error }` SDK result in UI code.
|
|
79
|
+
|
|
80
|
+
Supported database DDL is `CREATE TABLE`, `DROP TABLE`, `ALTER TABLE … ADD/DROP
|
|
81
|
+
COLUMN`, `ALTER TABLE … SET ACCESS`, and `CREATE INDEX`. Put
|
|
82
|
+
`-- mortar:access public_insert` (or another access mode) immediately before a
|
|
83
|
+
`CREATE TABLE` when the default `public_read` is wrong. Resource kinds are
|
|
84
|
+
`set_bucket_access`, `deploy_function`, `delete_function`, `upsert_cron`, and
|
|
85
|
+
`delete_cron`; they belong to `mortar_resource`, never SQL. Destructive and
|
|
86
|
+
backend-changing operations respect the workspace approval gate. Arbitrary
|
|
87
|
+
PostgreSQL, data backfills, PL/pgSQL, transactions, and triggers are not inputs.
|
|
88
|
+
|
|
89
|
+
## Access and safety invariants
|
|
90
|
+
|
|
91
|
+
- Change an existing table's access with `set_table_access`; never drop and
|
|
92
|
+
recreate a table merely to change permissions.
|
|
93
|
+
- For a public form, registration, or evidence upload reviewed by an operator,
|
|
94
|
+
use table/bucket access `public_insert`. It permits new submissions while
|
|
95
|
+
denying anonymous list, read, update, delete, and overwrite.
|
|
96
|
+
- Never use `public` merely to make anonymous submission work: it exposes all
|
|
97
|
+
rows or files for public read and mutation.
|
|
98
|
+
- Generate an unguessable logical object key for each public upload.
|
|
99
|
+
- Client-side filters are UX, not authorization. Choose a server-enforced access
|
|
100
|
+
policy for private or owner-scoped rows.
|
|
101
|
+
- Never put `app` or `admin` keys, `DatabaseTransactions`, operator clients, or
|
|
102
|
+
other privileged capabilities in Web, Desktop renderer, Keel/React Native, or
|
|
103
|
+
WeChat bundles.
|
|
104
|
+
- Never call a database + storage/functions/external workflow "atomic". Only
|
|
105
|
+
work committed inside one tenant-scoped PostgreSQL transaction is ACID.
|
|
106
|
+
|
|
107
|
+
## On-demand references
|
|
108
|
+
|
|
109
|
+
The following detailed references are bundled beside this core Skill. Read only
|
|
110
|
+
the documents needed for the current task; do not load all of them by default.
|
|
111
|
+
In Fabric they are materialized under `/tmp/skills/mortar/references/`; other
|
|
112
|
+
Agent Skills hosts resolve the same paths relative to this `SKILL.md`.
|
|
113
|
+
|
|
114
|
+
- Database CRUD, atomic row mutations, trusted cross-table transactions, and
|
|
115
|
+
exact ACID boundaries: `references/database.md`
|
|
116
|
+
- Versioned SQL syntax, checksums/revisions, resource artifact layout, type
|
|
117
|
+
generation, legacy migrations, and the complete capability-to-source matrix:
|
|
118
|
+
`references/schema-migrations.md`
|
|
119
|
+
- App-user signup/signin, OTP, session lifecycle, and private-user modeling:
|
|
120
|
+
`references/auth.md`
|
|
121
|
+
- Upload/download/list/signed URLs, immutable object versions, CORS, and
|
|
122
|
+
database-to-object consistency: `references/storage.md`
|
|
123
|
+
- Postgres changes, broadcast, presence, reconnect behavior, and cleanup:
|
|
124
|
+
`references/realtime.md`
|
|
125
|
+
- Function deployment/invocation, logs, public-key boundaries, and error
|
|
126
|
+
handling: `references/functions.md`
|
|
127
|
+
- The 9 infrastructure primitives, open capability registry, application
|
|
128
|
+
modules, usage accounting, and trust surfaces: `references/architecture.md`
|
|
129
|
+
- Product analytics, error capture, delivery guarantees, and operator summary:
|
|
130
|
+
`references/analytics.md`
|
|
131
|
+
- Cron schedules, queues, workers, retries, and transactional enqueue:
|
|
132
|
+
`references/automation.md`
|
|
133
|
+
- Transactional email/SMS and provider/idempotency boundaries:
|
|
134
|
+
`references/communications.md`
|
|
135
|
+
- Encrypted project secrets and function environment injection:
|
|
136
|
+
`references/secrets.md`
|
|
137
|
+
- Project usage, managed logs, billing attribution, and egress semantics:
|
|
138
|
+
`references/usage-billing.md`
|
|
139
|
+
- Accounts, projects, API keys, PATs, custom domains, top-up, and capacity:
|
|
140
|
+
`references/control-plane.md`
|
|
141
|
+
|
|
142
|
+
When using Fabric `bash`, read the exact materialized path shown by `load_skill`,
|
|
143
|
+
for example:
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
sed -n '1,240p' /tmp/skills/mortar/references/storage.md
|
|
115
147
|
```
|
|
116
148
|
|
|
117
|
-
|
|
118
|
-
`neq`, ranges, `like/ilike`, `in`, `is`, `order`, `limit`, `single`, and
|
|
119
|
-
`maybeSingle`.
|
|
120
|
-
|
|
121
|
-
### Atomic row mutations
|
|
122
|
-
|
|
123
|
-
Pass an array when every insert or upsert must succeed or fail together. Mortar
|
|
124
|
-
sends at most 200 rows in one request and executes the batch in one
|
|
125
|
-
tenant-scoped PostgreSQL transaction. For upsert, a row with an `id` replaces
|
|
126
|
-
that row's data; a row without an `id` inserts. Do not use a loop or
|
|
127
|
-
`Promise.all` when partial completion would break an invariant.
|
|
128
|
-
|
|
129
|
-
```ts
|
|
130
|
-
const { data: createdTodos, error: batchError } = await mortar
|
|
131
|
-
.from('todos')
|
|
132
|
-
.insert([
|
|
133
|
-
{ title: 'Prepare', done: false, user_id: userId },
|
|
134
|
-
{ title: 'Ship', done: false, user_id: userId },
|
|
135
|
-
])
|
|
136
|
-
.select();
|
|
137
|
-
if (batchError) throw new Error(batchError.message);
|
|
138
|
-
|
|
139
|
-
const { data: changedTodos, error: updateError } = await mortar
|
|
140
|
-
.from('todos')
|
|
141
|
-
.update({ done: true })
|
|
142
|
-
.eq('user_id', userId)
|
|
143
|
-
.eq('archived', false)
|
|
144
|
-
.select();
|
|
145
|
-
if (updateError) throw new Error(updateError.message);
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
Filtered `update` and `delete` select and mutate all matching rows inside one
|
|
149
|
-
server-side transaction. Do not pre-select IDs and then update/delete them one
|
|
150
|
-
at a time: that creates a race and loses the all-or-nothing guarantee. Chain
|
|
151
|
-
`.select()` only when the app needs the affected rows returned.
|
|
152
|
-
|
|
153
|
-
### Cross-table transactions are server-only
|
|
154
|
-
|
|
155
|
-
`DatabaseTransactions` can atomically combine up to 200 cross-table row
|
|
156
|
-
`insert` / by-ID `update` / by-ID `delete` operations and PostgreSQL-backed
|
|
157
|
-
queue `enqueue` operations. It is intentionally not attached to the injected
|
|
158
|
-
`mortar` singleton and requires an `app` or `admin` key. Its `execute` method
|
|
159
|
-
throws on failure, so use `try`/`catch`, not a `{ data, error }` envelope.
|
|
160
|
-
|
|
161
|
-
```ts
|
|
162
|
-
import { DatabaseTransactions } from '@mortar-ai/client';
|
|
163
|
-
|
|
164
|
-
const transactions = new DatabaseTransactions(serverOnlyMortarOptions);
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
const results = await transactions.execute([
|
|
168
|
-
{ op: 'insert', table: 'orders', data: { customer_id: customerId, total } },
|
|
169
|
-
{ op: 'enqueue', type: 'send_receipt', payload: { customer_id: customerId } },
|
|
170
|
-
]);
|
|
171
|
-
console.log(results);
|
|
172
|
-
} catch (error) {
|
|
173
|
-
// Nothing committed. Report or retry the whole idempotent operation.
|
|
174
|
-
throw error;
|
|
175
|
-
}
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
Never instantiate `DatabaseTransactions` in Web, Desktop renderer,
|
|
179
|
-
Keel/React Native, or WeChat client code. Never add an app/admin key to a
|
|
180
|
-
client-side environment variable or source file. Use this API only in an
|
|
181
|
-
already-approved trusted server environment that holds the key. If a Fabric
|
|
182
|
-
project has no such path, report cross-table ACID as unsupported instead of
|
|
183
|
-
simulating it with several public-client calls. A transactional queue enqueue
|
|
184
|
-
also requires Mortar's PostgreSQL queue backend; otherwise the whole request
|
|
185
|
-
fails without committing row changes.
|
|
186
|
-
|
|
187
|
-
### Exact ACID boundary
|
|
188
|
-
|
|
189
|
-
Mortar provides true ACID only for work executed in the same tenant-scoped
|
|
190
|
-
PostgreSQL transaction: atomic row batches, filtered row mutations, and the
|
|
191
|
-
trusted cross-table row + PostgreSQL queue API above. Object storage,
|
|
192
|
-
compute/functions, communications providers, analytics delivery, and other
|
|
193
|
-
external systems do not participate in that transaction. Never describe a
|
|
194
|
-
workflow spanning them as one ACID transaction or claim two-phase commit.
|
|
195
|
-
|
|
196
|
-
## App-user authentication
|
|
197
|
-
|
|
198
|
-
Auth sessions are persisted and attached to later database, storage, realtime,
|
|
199
|
-
and compute calls by the SDK.
|
|
200
|
-
|
|
201
|
-
```ts
|
|
202
|
-
const { data, error } = await mortar.auth.signUp({ email, password });
|
|
203
|
-
const signedIn = await mortar.auth.signInWithPassword({ email, password });
|
|
204
|
-
|
|
205
|
-
const sent = await mortar.auth.signInWithOtp({ email }); // or { phone }
|
|
206
|
-
const verified = await mortar.auth.verifyOtp({ email, token });
|
|
207
|
-
|
|
208
|
-
const { data: sessionData } = await mortar.auth.getSession();
|
|
209
|
-
const { data: userData, error: userError } = await mortar.auth.getUser();
|
|
210
|
-
await mortar.auth.signOut();
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
Use `onAuthStateChange` when UI must react to session changes, and unsubscribe
|
|
214
|
-
the returned subscription during cleanup.
|
|
215
|
-
|
|
216
|
-
Password reset starts with `mortar.auth.resetPasswordForEmail(email)`; confirm via
|
|
217
|
-
`passwordResetConfirm(email, token, newPassword)`. Never store or pass session
|
|
218
|
-
tokens manually unless the user explicitly needs a server integration.
|
|
219
|
-
|
|
220
|
-
For private user rows, include the authenticated user's ID in the schema/data
|
|
221
|
-
model and choose the correct table access policy. Client-side filters improve
|
|
222
|
-
UX but do not replace server-side access rules.
|
|
223
|
-
|
|
224
|
-
## File storage
|
|
225
|
-
|
|
226
|
-
```ts
|
|
227
|
-
const bucket = mortar.storage.from('avatars');
|
|
228
|
-
|
|
229
|
-
const { data: uploaded, error: uploadError } = await bucket.upload(path, file, {
|
|
230
|
-
contentType: file.type,
|
|
231
|
-
upsert: true,
|
|
232
|
-
});
|
|
233
|
-
const { data: blob, error: downloadError } = await bucket.download(path);
|
|
234
|
-
const { data: files, error: listError } = await bucket.list('users/');
|
|
235
|
-
const { data: signed, error: signError } = await bucket.createSignedUrl(path, 3600);
|
|
236
|
-
const { error: removeError } = await bucket.remove([path]);
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
Applications address files by their stable logical `path`. Mortar stores new
|
|
240
|
-
uploads under immutable internal physical versions, then atomically switches
|
|
241
|
-
its PostgreSQL metadata pointer and durably schedules orphan/replaced versions
|
|
242
|
-
for retryable cleanup. Never construct, persist, or expose Mortar's internal
|
|
243
|
-
physical object key. Existing logical paths remain compatible, and application
|
|
244
|
-
projects do not run or author the platform storage migration.
|
|
245
|
-
|
|
246
|
-
This protocol keeps Mortar's own file metadata from pointing at a partial
|
|
247
|
-
upload, but it does not make a separate application row and an object upload
|
|
248
|
-
one transaction. When a business row must reference a file, generate an
|
|
249
|
-
unguessable logical key. For a simple workflow, upload first and write the
|
|
250
|
-
business row only after upload succeeds. For recoverable workflows, create a
|
|
251
|
-
row with a stable idempotency key and `pending` state, upload, then mark it
|
|
252
|
-
`ready`; reconcile stale rows to `failed` or retry them. Do not issue a database
|
|
253
|
-
write and `bucket.upload()` concurrently and call the result atomic.
|
|
254
|
-
|
|
255
|
-
Downloads mint a short-lived URL and fetch the object directly from the storage
|
|
256
|
-
backend. Reuse `signed.signedUrl` until it nears expiry when rendering media.
|
|
257
|
-
Prefer the signed URL directly in `<img src>`, `<video src>`, or `<audio src>`;
|
|
258
|
-
this avoids an unnecessary blob copy and does not require CORS merely to render
|
|
259
|
-
the media. Signed-URL minting is excluded from the project's API-concurrency
|
|
260
|
-
budget, but clients should still lazy-load visible media and cache/reuse each
|
|
261
|
-
URL instead of signing again on every render. Signing authorizes a possible
|
|
262
|
-
download; it does not prove bytes were served and must never be described or
|
|
263
|
-
modeled as an object-egress charge. Only provider-observed access-log or billing
|
|
264
|
-
reconciliation may record actual object egress. The optional third
|
|
265
|
-
`createSignedUrl` argument (`{ signal, timeoutMs }`) bounds only the signing
|
|
266
|
-
request when the UI needs a deadline.
|
|
267
|
-
|
|
268
|
-
`bucket.download(path)` uses JavaScript `fetch()` against the object store and
|
|
269
|
-
therefore needs a matching bucket CORS rule in browsers. Appunvs-managed Mortar
|
|
270
|
-
storage provides that contract; self-hosted object storage must allow the app
|
|
271
|
-
origin for credential-free `GET` / `HEAD`. Never attach Mortar Authorization or
|
|
272
|
-
browser cookies to a signed object URL.
|
|
273
|
-
|
|
274
|
-
## Realtime, broadcast, and presence
|
|
275
|
-
|
|
276
|
-
```ts
|
|
277
|
-
const room = mortar
|
|
278
|
-
.channel('todos-room', { presence: { key: userId } })
|
|
279
|
-
.on(
|
|
280
|
-
'postgres_changes',
|
|
281
|
-
{ event: '*', schema: 'public', table: 'todos' },
|
|
282
|
-
(payload) => refreshFromChange(payload),
|
|
283
|
-
)
|
|
284
|
-
.on('broadcast', { event: 'cursor' }, ({ payload }) => drawCursor(payload))
|
|
285
|
-
.on('presence', { event: 'sync' }, () => renderRoster(room.presenceState()))
|
|
286
|
-
.subscribe((status) => {
|
|
287
|
-
if (status === 'SUBSCRIBED') void room.track({ online: true });
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
await room.send({ type: 'broadcast', event: 'cursor', payload: { x, y } });
|
|
291
|
-
// On component/page cleanup:
|
|
292
|
-
mortar.removeChannel(room);
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
Clean up channels with their page/component. Persist through `from(table)`;
|
|
296
|
-
realtime only notifies peers.
|
|
297
|
-
|
|
298
|
-
## Compute and analytics
|
|
149
|
+
## Completion checklist
|
|
299
150
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
151
|
+
- Live schema was read before schema design.
|
|
152
|
+
- Every database schema change used reviewed exact SQL through `mortar_migrate`;
|
|
153
|
+
every function/bucket/cron change used `mortar_resource`.
|
|
154
|
+
- Application code imports only the platform-owned singleton.
|
|
155
|
+
- No secret/admin client or hand-written fallback configuration entered the
|
|
156
|
+
client bundle.
|
|
157
|
+
- Every SDK error path is handled.
|
|
158
|
+
- The relevant on-demand reference—not every reference—was consulted.
|
|
159
|
+
- Type-check and the surface's real build pass.
|
|
304
160
|
|
|
305
161
|
## Boundary for non-Fabric development
|
|
306
162
|
|
|
307
|
-
For Mortar itself or a standalone non-Fabric integration, inspect the current
|
|
308
|
-
README. Never copy its standalone `createClient` setup into a Fabric project.
|
|
163
|
+
For Mortar itself or a standalone non-Fabric integration, inspect the current
|
|
164
|
+
SDK README. Never copy its standalone `createClient` setup into a Fabric project.
|
package/bin/install.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* mortar-skill-install — copies the canonical SKILL.md
|
|
4
|
-
* Code skills directory.
|
|
3
|
+
* mortar-skill-install — copies the canonical SKILL.md and its on-demand
|
|
4
|
+
* references into the user's Claude Code skills directory.
|
|
5
5
|
*
|
|
6
6
|
* Default destination: `~/.claude/skills/mortar/SKILL.md`. Override
|
|
7
7
|
* via `--dest=<path>` for non-default Claude installs.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
'use strict';
|
|
12
12
|
|
|
13
|
-
const { copyFileSync, existsSync, mkdirSync } = require('node:fs');
|
|
13
|
+
const { copyFileSync, cpSync, existsSync, mkdirSync, rmSync } = require('node:fs');
|
|
14
14
|
const { homedir } = require('node:os');
|
|
15
15
|
const { dirname, resolve } = require('node:path');
|
|
16
16
|
|
|
@@ -27,6 +27,7 @@ function parseFlags(argv) {
|
|
|
27
27
|
function main() {
|
|
28
28
|
const flags = parseFlags(process.argv);
|
|
29
29
|
const src = resolve(__dirname, '..', 'SKILL.md');
|
|
30
|
+
const referencesSrc = resolve(__dirname, '..', 'references');
|
|
30
31
|
if (!existsSync(src)) {
|
|
31
32
|
console.error(`mortar-skill: source missing at ${src}`);
|
|
32
33
|
process.exit(1);
|
|
@@ -37,7 +38,11 @@ function main() {
|
|
|
37
38
|
|
|
38
39
|
mkdirSync(dirname(dest), { recursive: true });
|
|
39
40
|
copyFileSync(src, dest);
|
|
41
|
+
const referencesDest = resolve(dirname(dest), 'references');
|
|
42
|
+
rmSync(referencesDest, { recursive: true, force: true });
|
|
43
|
+
if (existsSync(referencesSrc)) cpSync(referencesSrc, referencesDest, { recursive: true });
|
|
40
44
|
console.log(`✓ Installed Mortar skill → ${dest}`);
|
|
45
|
+
console.log(`✓ Installed on-demand references → ${referencesDest}`);
|
|
41
46
|
console.log('');
|
|
42
47
|
console.log('Restart Claude Code (or run `/skill reload`) to activate.');
|
|
43
48
|
}
|
package/index.js
CHANGED
|
@@ -10,6 +10,7 @@ const { readFileSync } = require('node:fs');
|
|
|
10
10
|
const { resolve } = require('node:path');
|
|
11
11
|
|
|
12
12
|
const SKILL_PATH = resolve(__dirname, 'SKILL.md');
|
|
13
|
+
const REFERENCES_PATH = resolve(__dirname, 'references');
|
|
13
14
|
const MANIFEST_PATH = resolve(__dirname, 'manifest.json');
|
|
14
15
|
|
|
15
16
|
module.exports = {
|
|
@@ -25,5 +26,6 @@ module.exports = {
|
|
|
25
26
|
return JSON.parse(readFileSync(MANIFEST_PATH, 'utf8'));
|
|
26
27
|
},
|
|
27
28
|
SKILL_PATH,
|
|
29
|
+
REFERENCES_PATH,
|
|
28
30
|
MANIFEST_PATH,
|
|
29
31
|
};
|
package/manifest.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"id": "mortar",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"content_sha256": "sha256:
|
|
4
|
+
"version": "0.6.0",
|
|
5
|
+
"content_sha256": "sha256:f3cf01640a48dca83ccfe49a744c33355a5009567a1706d5c59528d6ddd3ee92",
|
|
6
6
|
"source_repo": "liamxujia/appunvs",
|
|
7
|
-
"source_path": "mortar/skill
|
|
7
|
+
"source_path": "mortar/skill",
|
|
8
8
|
"distributions": [
|
|
9
9
|
"harness_preinstalled",
|
|
10
10
|
"npm"
|
|
11
11
|
],
|
|
12
12
|
"npm": {
|
|
13
13
|
"package": "@mortar-ai/skill",
|
|
14
|
-
"version": "0.
|
|
14
|
+
"version": "0.6.0"
|
|
15
15
|
}
|
|
16
16
|
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mortar-ai/skill",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Agent skill package for building Fabric applications on Mortar (BaaS),
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Agent skill package for building Fabric applications on Mortar (BaaS), with compact core guidance and on-demand references.",
|
|
5
5
|
"main": "index.js",
|
|
6
|
-
"files": ["SKILL.md", "manifest.json", "index.js", "README.md"],
|
|
6
|
+
"files": ["SKILL.md", "references", "manifest.json", "index.js", "README.md"],
|
|
7
7
|
"bin": {
|
|
8
8
|
"mortar-skill-install": "bin/install.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"sync": "node
|
|
12
|
-
"check": "node scripts/
|
|
11
|
+
"sync": "node ../../shared/skill-distribution/sync.js --config distribution.json",
|
|
12
|
+
"check": "node scripts/check-coverage.js && node ../../shared/skill-distribution/sync.js --config distribution.json --check"
|
|
13
13
|
},
|
|
14
14
|
"publishConfig": { "access": "public" },
|
|
15
15
|
"license": "Apache-2.0"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Analytics and error-capture reference
|
|
2
|
+
|
|
3
|
+
Read this reference for product events, identity traits, error capture, flush
|
|
4
|
+
behavior, or operator summaries. Analytics composes the `warehouse`, `network`,
|
|
5
|
+
and operational logging paths; it is a capability, not a new cost primitive.
|
|
6
|
+
|
|
7
|
+
The public client batches events and exposes synchronous capture calls plus an
|
|
8
|
+
async flush:
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
mortar.analytics.track('pageview', { path: location.pathname });
|
|
12
|
+
mortar.analytics.identify(user.id, { plan: 'pro' });
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
await checkout();
|
|
16
|
+
} catch (error) {
|
|
17
|
+
mortar.analytics.captureError(error, { where: 'checkout' });
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
await mortar.analytics.flush();
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Do not treat capture or flush as a durable business transaction and do not block
|
|
25
|
+
primary UX on analytics delivery. Flush at an explicit lifecycle boundary when
|
|
26
|
+
best-effort delivery should be attempted before navigation or shutdown. The
|
|
27
|
+
summary endpoint is a trusted project/operator read, not part of Fabric's public
|
|
28
|
+
bundle singleton. A deployment without a configured warehouse returns an
|
|
29
|
+
availability error; do not silently invent local analytics persistence.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Architecture and capability taxonomy reference
|
|
2
|
+
|
|
3
|
+
Read this reference when deciding where a Mortar API belongs, which credential
|
|
4
|
+
may call it, which code implements it, which infrastructure it consumes, or
|
|
5
|
+
how its observed usage is priced.
|
|
6
|
+
|
|
7
|
+
Mortar keeps five concerns separate. Do not collapse them into one:
|
|
8
|
+
|
|
9
|
+
1. A **capability** is an open-ended user or operator outcome exposed by API,
|
|
10
|
+
SDK, CLI, MCP, or control-plane tooling. There is deliberately no fixed
|
|
11
|
+
capability count.
|
|
12
|
+
2. A **trust surface** says which credential and runtime may call a capability.
|
|
13
|
+
3. An **application module** implements and composes use cases.
|
|
14
|
+
4. A **primitive** is a provider-neutral resource, replacement, operational,
|
|
15
|
+
and cost-attribution boundary.
|
|
16
|
+
5. **Usage accounting** observes selected operations, applies meters, quotas,
|
|
17
|
+
and pricing policy, and records the customer ledger.
|
|
18
|
+
|
|
19
|
+
An `internal/application/<name>` Go package is an application composition
|
|
20
|
+
module, not a closed product taxonomy. A capability may span multiple
|
|
21
|
+
application, primitive, account, or API packages, and one application module
|
|
22
|
+
may support several capabilities.
|
|
23
|
+
|
|
24
|
+
The current application modules are `auth`, `cron`, `data`, `files`,
|
|
25
|
+
`migration`, `queue`, `realtime`, and `secret`. Functions, comms, analytics,
|
|
26
|
+
accounts, and custom domains remain capabilities even when their implementation
|
|
27
|
+
spans primitive, API, account, or other packages rather than a same-named
|
|
28
|
+
application directory. `internal/usage` separately owns metering, quota,
|
|
29
|
+
pricing, and the credit ledger. `internal/subscription` separately owns account
|
|
30
|
+
subscription periods, fulfillment, settlement, and compute-grant allocation;
|
|
31
|
+
usage accounting consumes those grants without owning the subscription
|
|
32
|
+
lifecycle.
|
|
33
|
+
|
|
34
|
+
## The 9 primitives
|
|
35
|
+
|
|
36
|
+
| Primitive | Role |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `compute` | Baseline tenant share of pool compute |
|
|
39
|
+
| `database` | PostgreSQL baseline and durable relational state |
|
|
40
|
+
| `cache` | Redis/in-process cache, rate limits, pub/sub, and presence |
|
|
41
|
+
| `storage` | Object bytes and GB-month storage |
|
|
42
|
+
| `network` | Provider-observed or response-writer outbound bytes |
|
|
43
|
+
| `function` | User function execution through a configured runner |
|
|
44
|
+
| `comms` | Transactional email and SMS delivery |
|
|
45
|
+
| `warehouse` | High-cardinality analytics events and summaries |
|
|
46
|
+
| `log` | Managed provider-log ingestion, indexing, and retention |
|
|
47
|
+
|
|
48
|
+
These names are stable because they align resource ownership, cost attribution,
|
|
49
|
+
and swappable cloud backends. They do not predict the public SDK or customer
|
|
50
|
+
invoice shape. For example, `network` is a side effect rather than
|
|
51
|
+
`mortar.network`, while auth composes database, cache, comms, and network
|
|
52
|
+
without becoming another primitive.
|
|
53
|
+
|
|
54
|
+
Primitive use is not equivalent to a customer charge. The same `warehouse`
|
|
55
|
+
primitive backs both uncharged summary reads and metered event ingestion.
|
|
56
|
+
Provider cost, observed usage, included allowance, and customer price are four
|
|
57
|
+
different facts; `internal/usage` connects them without making primitive
|
|
58
|
+
interfaces depend on plans or billing.
|
|
59
|
+
|
|
60
|
+
## Current capability registry
|
|
61
|
+
|
|
62
|
+
| Capability family | Public app bundle | Trusted project/operator | Account control plane |
|
|
63
|
+
|---|---|---|---|
|
|
64
|
+
| Auth | Signup/signin, OTP, session, reset | User administration | Account signup/signin and notification email |
|
|
65
|
+
| Data | Row CRUD under access policy | Schema/index/column changes and cross-table transactions | Project ownership and lifecycle |
|
|
66
|
+
| Files | Upload/list/delete/sign under bucket policy | Bucket policy and cleanup operations | Project lifecycle cleanup |
|
|
67
|
+
| Realtime | Row changes, broadcast, presence | Backend/channel configuration | — |
|
|
68
|
+
| Functions | Invoke deployed functions | List/deploy/delete/read logs | Read project function logs |
|
|
69
|
+
| Analytics | Capture events and errors | Read project summaries | — |
|
|
70
|
+
| Automation | — | Cron CRUD and queue enqueue/list/get | Read project cron schedules |
|
|
71
|
+
| Communications | — | Send configured email/SMS | Operational account email |
|
|
72
|
+
| Secrets | — | Encrypted project secret CRUD and function injection | — |
|
|
73
|
+
| Usage and logs | — | Project usage ledger and audit logs | Read project usage |
|
|
74
|
+
| Identity and access | — | Project API-key administration | Projects, keys, PATs, ownership |
|
|
75
|
+
| Domains and commerce | — | — | Domain claim/verification, plans, top-up, auto-top-up, capacity/enterprise intent |
|
|
76
|
+
|
|
77
|
+
The registry grows when a real API or workflow ships. Cron, queue, and custom
|
|
78
|
+
domains are implemented capabilities, not a future three-item extension list.
|
|
79
|
+
Their availability can still depend on configuration: cron may be disabled,
|
|
80
|
+
queue may use PostgreSQL or be off, analytics needs a warehouse, functions need
|
|
81
|
+
a runner, comms needs a provider, and domain TLS routing is completed by the
|
|
82
|
+
deployment edge after Mortar verifies ownership.
|
|
83
|
+
|
|
84
|
+
## Trust surfaces
|
|
85
|
+
|
|
86
|
+
- **Public app bundle**: a PUBLIC bundle key plus optional app-user session.
|
|
87
|
+
Fabric's injected `MortarClient` intentionally contains only auth, row data,
|
|
88
|
+
storage, realtime, function invocation, and analytics capture.
|
|
89
|
+
- **Trusted project/operator**: app/admin project keys in server, CI, CLI, MCP,
|
|
90
|
+
or platform provisioning. Never ship these credentials or clients to a Web,
|
|
91
|
+
Desktop renderer, Keel/React Native, or WeChat bundle.
|
|
92
|
+
- **Account control plane**: account JWT or `mtr_pat_*` PAT against the apex
|
|
93
|
+
`_accounts` routes. These credentials manage projects and ownership; they are
|
|
94
|
+
not interchangeable with project or app-user credentials.
|
|
95
|
+
|
|
96
|
+
When documenting or implementing a capability, state its trust surface, its
|
|
97
|
+
application implementation, the primitives it consumes, and any metering and
|
|
98
|
+
pricing policy. No one field answers authorization, implementation, operations,
|
|
99
|
+
and billing at the same time.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# App-user authentication reference
|
|
2
|
+
|
|
3
|
+
Read this reference for signup/signin, OTP, password reset, session state, or
|
|
4
|
+
private-user data modeling.
|
|
5
|
+
|
|
6
|
+
Auth sessions are persisted and attached to later database, storage, realtime,
|
|
7
|
+
and compute calls by the platform-owned singleton.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
const { data, error } = await mortar.auth.signUp({ email, password });
|
|
11
|
+
const signedIn = await mortar.auth.signInWithPassword({ email, password });
|
|
12
|
+
|
|
13
|
+
const sent = await mortar.auth.signInWithOtp({ email }); // or { phone }
|
|
14
|
+
const verified = await mortar.auth.verifyOtp({ email, token });
|
|
15
|
+
|
|
16
|
+
const { data: sessionData } = await mortar.auth.getSession();
|
|
17
|
+
const { data: userData, error: userError } = await mortar.auth.getUser();
|
|
18
|
+
await mortar.auth.signOut();
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Check each returned `error`; do not infer authentication from the presence of UI
|
|
22
|
+
state. Use `onAuthStateChange` when the UI must react to session changes and
|
|
23
|
+
unsubscribe during component/page cleanup.
|
|
24
|
+
|
|
25
|
+
Password reset starts with `mortar.auth.resetPasswordForEmail(email)` and is
|
|
26
|
+
confirmed with `passwordResetConfirm(email, token, newPassword)`. Never store or
|
|
27
|
+
pass session tokens manually unless the user explicitly needs a trusted server
|
|
28
|
+
integration.
|
|
29
|
+
|
|
30
|
+
For private user rows, include the authenticated user ID in the schema/data
|
|
31
|
+
model and select a server-enforced private or owner-scoped table access policy.
|
|
32
|
+
Client-side filters improve UX but do not replace authorization. Anonymous
|
|
33
|
+
intake uses `public_insert`, not broad `public` access.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Cron and queue automation reference
|
|
2
|
+
|
|
3
|
+
Read this reference for scheduled functions, background jobs, retries, workers,
|
|
4
|
+
or atomic row-plus-job workflows. Cron and queue are shipped capabilities whose
|
|
5
|
+
runtime availability depends on configuration; they are not “planned extension
|
|
6
|
+
slots.”
|
|
7
|
+
|
|
8
|
+
Fabric provisions schedules only through one `mortar_resource` operation:
|
|
9
|
+
`upsert_cron` creates or updates a schedule for an already deployed function,
|
|
10
|
+
and `delete_cron` removes it. Direct cron list/create/patch/delete endpoints are
|
|
11
|
+
trusted operator surfaces. Cron is normally enabled and can be disabled by the
|
|
12
|
+
Mortar deployment.
|
|
13
|
+
|
|
14
|
+
Direct queue enqueue/list/get is also trusted project/operator functionality;
|
|
15
|
+
it is intentionally absent from the public app-bundle `MortarClient`. Queue may
|
|
16
|
+
use the PostgreSQL backend or be disabled. Workers must make handlers
|
|
17
|
+
idempotent, bound retries, and distinguish a claim from successful completion.
|
|
18
|
+
|
|
19
|
+
When a database change and queue enqueue must commit together, use the trusted
|
|
20
|
+
`DatabaseTransactions` API described in `database.md`. That guarantee applies
|
|
21
|
+
only to the PostgreSQL queue backend and the row operations inside the same
|
|
22
|
+
tenant transaction. Function execution, communications, object storage, and
|
|
23
|
+
external providers remain outside that transaction.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Transactional communications reference
|
|
2
|
+
|
|
3
|
+
Read this reference for email, SMS, OTP delivery, or provider behavior. Mortar's
|
|
4
|
+
`comms` primitive records email and SMS as separate ledger resources because
|
|
5
|
+
their provider prices differ.
|
|
6
|
+
|
|
7
|
+
Direct `/comms/send` is a trusted project/operator capability. It is not exposed
|
|
8
|
+
on Fabric's public app-bundle singleton; never place an app/admin key or provider
|
|
9
|
+
credential in client code to reach it. App-user auth may internally compose the
|
|
10
|
+
same primitive for phone OTP, email verification, and password reset.
|
|
11
|
+
|
|
12
|
+
Hosted Mortar can use configured Aliyun SMS and Aliyun DirectMail adapters.
|
|
13
|
+
Provider availability is deployment-specific, and an unconfigured channel must
|
|
14
|
+
fail visibly. Treat delivery as an external side effect: use an idempotency key
|
|
15
|
+
or persisted outbox state where duplicate messages matter, and never claim that
|
|
16
|
+
a database transaction atomically includes provider delivery.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Account and project control-plane reference
|
|
2
|
+
|
|
3
|
+
Read this reference for Mortar accounts, projects, API keys, PATs, custom
|
|
4
|
+
domains, plans, top-up, auto-top-up, capacity, or enterprise placement.
|
|
5
|
+
|
|
6
|
+
The control plane uses the apex host and an account JWT or `mtr_pat_*` personal
|
|
7
|
+
access token. It manages account identity, project lifecycle, project API keys,
|
|
8
|
+
PATs, project usage, domain claims, plan/top-up settings, and enterprise intent.
|
|
9
|
+
These credentials are not app-user sessions and cannot be substituted for a
|
|
10
|
+
project key on tenant data-plane routes.
|
|
11
|
+
|
|
12
|
+
Project API keys have their own scopes. Public bundle keys are expected in app
|
|
13
|
+
code; app/admin keys belong only in trusted server, CI, CLI, MCP, or platform
|
|
14
|
+
provisioning. Revoke and rotate keys through the control plane rather than
|
|
15
|
+
editing generated client configuration.
|
|
16
|
+
|
|
17
|
+
Custom-domain creation records a claim and verification proves DNS ownership.
|
|
18
|
+
Mortar does not by itself finish edge routing or ACME issuance: the deployment
|
|
19
|
+
edge/operator consumes verified state and configures TLS. Do not claim a domain
|
|
20
|
+
is live merely because its TXT verification succeeded.
|
|
21
|
+
|
|
22
|
+
Top-up and capacity changes are commercial/control-plane operations, not schema
|
|
23
|
+
migrations. They may involve provider webhooks and asynchronous placement;
|
|
24
|
+
verify the resulting project state rather than assuming request acceptance is
|
|
25
|
+
final settlement or capacity activation.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Database and transaction reference
|
|
2
|
+
|
|
3
|
+
Read this reference for row CRUD, batch mutations, cross-table transactions, or
|
|
4
|
+
questions about Mortar's exact ACID boundary.
|
|
5
|
+
|
|
6
|
+
## Database SDK
|
|
7
|
+
|
|
8
|
+
The query surface is Supabase-shaped. Async row methods normally return
|
|
9
|
+
`{ data, error }`; check `error` rather than assuming success.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
const { data: todos, error } = await mortar
|
|
13
|
+
.from('todos')
|
|
14
|
+
.select('*')
|
|
15
|
+
.eq('done', false)
|
|
16
|
+
.order('created_at', { ascending: false })
|
|
17
|
+
.limit(20);
|
|
18
|
+
if (error) throw new Error(error.message);
|
|
19
|
+
|
|
20
|
+
const { data: created, error: insertError } = await mortar
|
|
21
|
+
.from('todos')
|
|
22
|
+
.insert({ title: 'Ship', done: false, user_id: userId })
|
|
23
|
+
.select()
|
|
24
|
+
.single();
|
|
25
|
+
if (insertError) throw new Error(insertError.message);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Updates and deletes use the same filter chain. Useful operators include `eq`,
|
|
29
|
+
`neq`, ranges, `like/ilike`, `in`, `is`, `order`, `limit`, `single`, and
|
|
30
|
+
`maybeSingle`.
|
|
31
|
+
|
|
32
|
+
## Atomic row mutations
|
|
33
|
+
|
|
34
|
+
Pass an array when every insert or upsert must succeed or fail together. Mortar
|
|
35
|
+
sends at most 200 rows in one request and executes the batch in one
|
|
36
|
+
tenant-scoped PostgreSQL transaction. For upsert, a row with an `id` replaces
|
|
37
|
+
that row's data; a row without an `id` inserts. Do not use a loop or
|
|
38
|
+
`Promise.all` when partial completion would break an invariant.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const { data: createdTodos, error: batchError } = await mortar
|
|
42
|
+
.from('todos')
|
|
43
|
+
.insert([
|
|
44
|
+
{ title: 'Prepare', done: false, user_id: userId },
|
|
45
|
+
{ title: 'Ship', done: false, user_id: userId },
|
|
46
|
+
])
|
|
47
|
+
.select();
|
|
48
|
+
if (batchError) throw new Error(batchError.message);
|
|
49
|
+
|
|
50
|
+
const { data: changedTodos, error: updateError } = await mortar
|
|
51
|
+
.from('todos')
|
|
52
|
+
.update({ done: true })
|
|
53
|
+
.eq('user_id', userId)
|
|
54
|
+
.eq('archived', false)
|
|
55
|
+
.select();
|
|
56
|
+
if (updateError) throw new Error(updateError.message);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Filtered `update` and `delete` select and mutate all matching rows inside one
|
|
60
|
+
server-side transaction. Do not pre-select IDs and update/delete them one at a
|
|
61
|
+
time; that introduces a race and loses the all-or-nothing guarantee. Chain
|
|
62
|
+
`.select()` only when the app needs the affected rows returned.
|
|
63
|
+
|
|
64
|
+
## Cross-table transactions are server-only
|
|
65
|
+
|
|
66
|
+
`DatabaseTransactions` can atomically combine up to 200 cross-table row
|
|
67
|
+
`insert`, by-ID `update`, by-ID `delete`, and PostgreSQL-backed queue `enqueue`
|
|
68
|
+
operations. It is intentionally absent from the injected `mortar` singleton and
|
|
69
|
+
requires an `app` or `admin` key. `execute` throws on failure, so use
|
|
70
|
+
`try`/`catch`, not a `{ data, error }` envelope.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { DatabaseTransactions } from '@mortar-ai/client';
|
|
74
|
+
|
|
75
|
+
const transactions = new DatabaseTransactions(serverOnlyMortarOptions);
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const results = await transactions.execute([
|
|
79
|
+
{ op: 'insert', table: 'orders', data: { customer_id: customerId, total } },
|
|
80
|
+
{ op: 'enqueue', type: 'send_receipt', payload: { customer_id: customerId } },
|
|
81
|
+
]);
|
|
82
|
+
console.log(results);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// Nothing committed. Report or retry the whole idempotent operation.
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Never instantiate `DatabaseTransactions` in Web, Desktop renderer,
|
|
90
|
+
Keel/React Native, or WeChat code. If a Fabric project has no approved trusted
|
|
91
|
+
server path, report cross-table ACID as unsupported instead of simulating it
|
|
92
|
+
with public-client calls. Transactional queue enqueue also requires Mortar's
|
|
93
|
+
PostgreSQL queue backend; otherwise no row operation commits.
|
|
94
|
+
|
|
95
|
+
## Exact ACID boundary
|
|
96
|
+
|
|
97
|
+
Mortar provides ACID only for work executed in the same tenant-scoped
|
|
98
|
+
PostgreSQL transaction: atomic row batches, filtered row mutations, and the
|
|
99
|
+
trusted cross-table row + PostgreSQL queue API above. Object storage,
|
|
100
|
+
compute/functions, communications providers, analytics delivery, and other
|
|
101
|
+
external systems do not participate. Never claim two-phase commit across them.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Functions reference
|
|
2
|
+
|
|
3
|
+
Read this reference for deploying or invoking compute functions and reading
|
|
4
|
+
function logs. Cron is in `automation.md`; analytics is in `analytics.md`.
|
|
5
|
+
|
|
6
|
+
## Deploy and invoke functions
|
|
7
|
+
|
|
8
|
+
Deploy or delete functions only through one `mortar_resource` operation with
|
|
9
|
+
kind `deploy_function` or `delete_function`. Do not edit `mortar.config.json`,
|
|
10
|
+
directly write the protected function directory, or call operator/admin SDK methods from application
|
|
11
|
+
code. Function list/deploy/delete/logs are trusted project or account/operator
|
|
12
|
+
surfaces; the app-bundle client exposes invocation only.
|
|
13
|
+
|
|
14
|
+
The desired-state directory contains `function.json` plus the runtime entrypoint:
|
|
15
|
+
`index.js` for Node-compatible runtimes and `index.py` for Python-compatible
|
|
16
|
+
runtimes. Switching runtime removes the stale entrypoint; deleting a function
|
|
17
|
+
removes the manifest, both entrypoint forms, and the legacy flat `.js` artifact.
|
|
18
|
+
|
|
19
|
+
Client bundles may invoke a function that the platform already deployed:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
const response = await mortar.compute.invoke({
|
|
23
|
+
name: 'create-checkout',
|
|
24
|
+
method: 'POST',
|
|
25
|
+
body: JSON.stringify({ orderId }),
|
|
26
|
+
contentType: 'application/json',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
throw new Error(`Function failed: ${response.status} ${await response.text()}`);
|
|
31
|
+
}
|
|
32
|
+
const result = await response.json();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`compute.invoke` is the only public-key compute operation. Mortar infrastructure
|
|
36
|
+
errors such as unavailable compute or timeout throw; user-function status codes
|
|
37
|
+
are returned as the raw `Response`, so check `response.ok` and consume the body.
|
|
38
|
+
Function deployment/logs remain operator-side capabilities and must never be
|
|
39
|
+
recreated with an app/admin key in a browser or mobile bundle.
|
|
40
|
+
|
|
41
|
+
Functions and external providers do not join the caller's PostgreSQL
|
|
42
|
+
transaction. Make externally visible operations idempotent and persist explicit
|
|
43
|
+
pending/succeeded/failed state when a workflow must recover.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Realtime, broadcast, and presence reference
|
|
2
|
+
|
|
3
|
+
Read this reference for row-change notifications, collaboration events, online
|
|
4
|
+
presence, reconnect behavior, or channel cleanup.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
const room = mortar
|
|
8
|
+
.channel('todos-room', { presence: { key: userId } })
|
|
9
|
+
.on(
|
|
10
|
+
'postgres_changes',
|
|
11
|
+
{ event: '*', schema: 'public', table: 'todos' },
|
|
12
|
+
(payload) => refreshFromChange(payload),
|
|
13
|
+
)
|
|
14
|
+
.on('broadcast', { event: 'cursor' }, ({ payload }) => drawCursor(payload))
|
|
15
|
+
.on('presence', { event: 'sync' }, () => renderRoster(room.presenceState()))
|
|
16
|
+
.subscribe((status) => {
|
|
17
|
+
if (status === 'SUBSCRIBED') void room.track({ online: true });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
await room.send({ type: 'broadcast', event: 'cursor', payload: { x, y } });
|
|
21
|
+
|
|
22
|
+
// On component/page cleanup:
|
|
23
|
+
mortar.removeChannel(room);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Persist durable state through `mortar.from(table)`. Realtime only notifies
|
|
27
|
+
peers; broadcast and presence are not a database and must not be the sole copy
|
|
28
|
+
of business data.
|
|
29
|
+
|
|
30
|
+
Use a stable presence key for the signed-in user, keep cursor/ephemeral payloads
|
|
31
|
+
small, and remove channels when their component/page unmounts. The SDK retains
|
|
32
|
+
the last tracked presence state and re-tracks it after reconnect, but the UI
|
|
33
|
+
must still tolerate disconnect/reconnect and duplicate change notifications.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Schema migrations and resource artifacts
|
|
2
|
+
|
|
3
|
+
Read this reference when authoring backend source, reviewing a typed migration
|
|
4
|
+
plan, resolving revision/checksum conflicts, generating app-data types, or
|
|
5
|
+
deciding whether a Mortar capability belongs in source at all.
|
|
6
|
+
|
|
7
|
+
## One canonical plan, four derived type targets
|
|
8
|
+
|
|
9
|
+
Mortar parses SQL into `mortar.migration-plan.v1`, a language-neutral JSON plan.
|
|
10
|
+
That plan—not TypeScript, Swift, Kotlin, or Dart code—is the execution contract.
|
|
11
|
+
After a successful apply, the canonical live schema may generate app-data models
|
|
12
|
+
for all four SDK languages. Generated models are downstream output; never edit
|
|
13
|
+
them to request a schema change.
|
|
14
|
+
|
|
15
|
+
## Versioned database SQL
|
|
16
|
+
|
|
17
|
+
Submit exact SQL through `mortar_migrate`:
|
|
18
|
+
|
|
19
|
+
```sql
|
|
20
|
+
-- mortar:access public_insert
|
|
21
|
+
CREATE TABLE submissions (
|
|
22
|
+
email text NOT NULL,
|
|
23
|
+
score integer,
|
|
24
|
+
metadata jsonb
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
ALTER TABLE submissions ADD COLUMN reviewed boolean NOT NULL;
|
|
28
|
+
CREATE INDEX submissions_reviewed ON submissions (reviewed, created_at);
|
|
29
|
+
ALTER TABLE submissions SET ACCESS app_only;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The parser accepts common PostgreSQL type spellings and canonicalizes them to
|
|
33
|
+
Mortar's logical types: `text`, `number`, `bool`, `json`, `timestamp`, and
|
|
34
|
+
`uuid`. It rejects row data, backfills, triggers, stored procedures, transaction
|
|
35
|
+
control, and calls that pretend functions/storage/cron are SQL.
|
|
36
|
+
|
|
37
|
+
Plan/apply guarantees:
|
|
38
|
+
|
|
39
|
+
- SHA-256 covers the exact SQL bytes written to source.
|
|
40
|
+
- `migration_id + checksum` is idempotent; reusing an id with different bytes is
|
|
41
|
+
a conflict.
|
|
42
|
+
- Every tenant has a monotonic schema revision. Apply fails if the reviewed
|
|
43
|
+
`expected_revision` is stale.
|
|
44
|
+
- All logical schema operations and the revision ledger row commit in one
|
|
45
|
+
tenant-scoped PostgreSQL transaction.
|
|
46
|
+
- Source is written with Fabric's source-revision CAS before apply. If apply
|
|
47
|
+
fails, the file remains an honest pending migration that can be fixed or
|
|
48
|
+
retried; no live mutation is wrapped afterwards as fake SQL.
|
|
49
|
+
|
|
50
|
+
`DROP TABLE` removes its rows and `DROP COLUMN` removes the schema declaration;
|
|
51
|
+
both are destructive plan diagnostics. Changing access must use `ALTER TABLE …
|
|
52
|
+
SET ACCESS`, never drop/recreate.
|
|
53
|
+
|
|
54
|
+
## Non-database desired state
|
|
55
|
+
|
|
56
|
+
Use `mortar_resource` so Fabric writes the exact artifact before reconciling the
|
|
57
|
+
live resource:
|
|
58
|
+
|
|
59
|
+
| Source path | Managed desired state |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `mortar/functions/<name>/index.js` or `index.py` | Runtime-specific function source |
|
|
62
|
+
| `mortar/functions/<name>/function.json` | Runtime and authoritative entrypoint |
|
|
63
|
+
| `mortar/storage/buckets/<name>.json` | Bucket access policy |
|
|
64
|
+
| `mortar/cron/<name>.json` | Schedule, target function, enabled state |
|
|
65
|
+
|
|
66
|
+
Deleting a function or cron removes its current-state artifact. These resources
|
|
67
|
+
may call external systems and do not join a database migration transaction.
|
|
68
|
+
|
|
69
|
+
## Complete capability-to-source matrix
|
|
70
|
+
|
|
71
|
+
“Mortar supports it” does not imply it should be a source artifact:
|
|
72
|
+
|
|
73
|
+
| Capability | Source authority | Why |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| Database tables/columns/access/index intent | Versioned SQL migration | Durable ordered schema with atomic revision ledger |
|
|
76
|
+
| Functions | Function directory | Executable source plus runtime metadata, not SQL |
|
|
77
|
+
| Bucket policy | Bucket JSON | Durable access policy; object bytes are runtime data |
|
|
78
|
+
| Cron schedules | Cron JSON | Durable desired schedule; executions are runtime state |
|
|
79
|
+
| Auth users/sessions/OTP/reset | None | Identity and credentials are runtime/security state |
|
|
80
|
+
| Database rows and row transactions | None | Application data, not schema |
|
|
81
|
+
| Stored files and signed URLs | None | Runtime objects and ephemeral authorization |
|
|
82
|
+
| Realtime row changes/broadcast/presence | None | Events and presence are runtime/ephemeral state |
|
|
83
|
+
| Queue jobs/claims/retries | None | Runtime work state; enqueue may join a row transaction |
|
|
84
|
+
| Analytics events/summaries | None | Runtime observations and derived reporting |
|
|
85
|
+
| Email/SMS sends | None | External side effects; make business workflows idempotent |
|
|
86
|
+
| Secret values | Encrypted Mortar secret store only | Values must never enter project source; source may document names in application-owned docs |
|
|
87
|
+
| Usage, logs, billing ledger | None | Platform observations and accounting authority |
|
|
88
|
+
| Projects, keys, PATs, domains, plans, capacity | None | Account/control-plane state |
|
|
89
|
+
|
|
90
|
+
## Legacy projects
|
|
91
|
+
|
|
92
|
+
Do not automatically replay or rewrite an existing migration history merely
|
|
93
|
+
because this workflow shipped. Old files can describe already-live schema, and
|
|
94
|
+
some projects may keep explicit operator SQL under `mortar/operations/` for
|
|
95
|
+
capabilities outside Mortar logical DDL. Treat those files as legacy history;
|
|
96
|
+
new supported schema changes start at the next migration id. Arbitrary operator
|
|
97
|
+
SQL remains a separately reviewed operational path and is not silently claimed
|
|
98
|
+
as replayable Mortar migration source.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Project secrets reference
|
|
2
|
+
|
|
3
|
+
Read this reference for function credentials, API tokens, encryption, or secret
|
|
4
|
+
rotation. Secret list/put/delete is a trusted project/operator capability and is
|
|
5
|
+
intentionally absent from the public app-bundle client.
|
|
6
|
+
|
|
7
|
+
Store provider credentials through Mortar's secret provisioning surface. Mortar
|
|
8
|
+
encrypts project secret values at rest and injects the resolved values into the
|
|
9
|
+
configured function environment. Do not copy a secret into Fabric source,
|
|
10
|
+
generated integration files, public environment variables, logs, analytics, or
|
|
11
|
+
function request payloads merely to make it reachable.
|
|
12
|
+
|
|
13
|
+
Application bundles should call a deployed function that uses the secret on the
|
|
14
|
+
server side. Rotation is a separate operation from function invocation; design
|
|
15
|
+
long-running workflows to tolerate a credential changing between attempts. If
|
|
16
|
+
the deployment has no encryption key or secret service configured, report the
|
|
17
|
+
platform failure instead of falling back to plaintext storage.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# File storage and consistency reference
|
|
2
|
+
|
|
3
|
+
Read this reference for buckets, uploads/downloads, signed URLs, media rendering,
|
|
4
|
+
CORS, or workflows that relate a database row to an object.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
const bucket = mortar.storage.from('avatars');
|
|
8
|
+
|
|
9
|
+
const { data: uploaded, error: uploadError } = await bucket.upload(path, file, {
|
|
10
|
+
contentType: file.type,
|
|
11
|
+
upsert: true,
|
|
12
|
+
});
|
|
13
|
+
const { data: blob, error: downloadError } = await bucket.download(path);
|
|
14
|
+
const { data: files, error: listError } = await bucket.list('users/');
|
|
15
|
+
const { data: signed, error: signError } = await bucket.createSignedUrl(path, 3600);
|
|
16
|
+
const { error: removeError } = await bucket.remove([path]);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Applications address files by their stable logical `path`. Mortar stores new
|
|
20
|
+
uploads under immutable internal physical versions, atomically switches its
|
|
21
|
+
PostgreSQL metadata pointer, and schedules orphan/replaced versions for durable
|
|
22
|
+
retryable cleanup. Never construct, persist, or expose an internal physical
|
|
23
|
+
object key. Application projects do not run or author the platform storage
|
|
24
|
+
migration.
|
|
25
|
+
|
|
26
|
+
Set durable bucket access through `mortar_resource`; Fabric writes
|
|
27
|
+
`mortar/storage/buckets/<name>.json` before reconciling the live policy. Object
|
|
28
|
+
bytes, logical object keys, signed URLs, and cleanup jobs remain runtime state
|
|
29
|
+
and never enter this source artifact.
|
|
30
|
+
|
|
31
|
+
This protocol prevents Mortar metadata from pointing at a partial upload, but a
|
|
32
|
+
separate application row and an object upload are not one transaction. Generate
|
|
33
|
+
an unguessable logical key. For a simple workflow, upload first and write the
|
|
34
|
+
business row after success. For recoverable workflows, create a row with a
|
|
35
|
+
stable idempotency key and `pending` state, upload, then mark it `ready`; reconcile
|
|
36
|
+
stale rows to `failed` or retry them. Do not run a database write and
|
|
37
|
+
`bucket.upload()` concurrently and call the result atomic.
|
|
38
|
+
|
|
39
|
+
Downloads mint a short-lived URL and fetch the object directly from the storage
|
|
40
|
+
backend. Reuse `signed.signedUrl` until near expiry and prefer it directly in
|
|
41
|
+
`<img>`, `<video>`, or `<audio>` instead of making an unnecessary blob copy.
|
|
42
|
+
Signed-URL minting is excluded from the project's API-concurrency budget, but
|
|
43
|
+
clients should lazy-load media and cache/reuse each URL. Signing authorizes a
|
|
44
|
+
possible download; it does not prove bytes were served and is not an
|
|
45
|
+
object-egress charge. Only provider access logs or billing reconciliation may
|
|
46
|
+
record actual object egress.
|
|
47
|
+
|
|
48
|
+
The optional third `createSignedUrl` argument (`{ signal, timeoutMs }`) bounds
|
|
49
|
+
only the signing request. `bucket.download(path)` uses JavaScript `fetch()` and
|
|
50
|
+
therefore needs a matching bucket CORS rule in browsers. Appunvs-managed Mortar
|
|
51
|
+
provides that contract; self-hosted storage must allow credential-free `GET` and
|
|
52
|
+
`HEAD` from the app origin. Never attach Mortar Authorization or cookies to a
|
|
53
|
+
signed object URL.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Usage, managed logs, and billing reference
|
|
2
|
+
|
|
3
|
+
Read this reference for quota, balance, overage, managed logs, or network/object
|
|
4
|
+
egress attribution. The 9 primitives are infrastructure and cost-attribution
|
|
5
|
+
boundaries; user-facing usage and billing are capabilities built over selected
|
|
6
|
+
observations, meters, allowances, pricing policy, and the credit ledger.
|
|
7
|
+
|
|
8
|
+
A primitive is never inherently free or billable. For example, warehouse event
|
|
9
|
+
ingestion is metered while a warehouse summary read is not. Keep four facts
|
|
10
|
+
separate: provider cost, observed units, included allowance, and customer
|
|
11
|
+
charge. Primitive interfaces remain unaware of those policies.
|
|
12
|
+
|
|
13
|
+
Project usage and audit-log reads are trusted project/operator surfaces, with an
|
|
14
|
+
account-owned project usage view on the account control plane. They are not part
|
|
15
|
+
of Fabric's public bundle singleton. A balance or quota read does not itself
|
|
16
|
+
create a metered observation or customer charge.
|
|
17
|
+
|
|
18
|
+
Do not estimate object egress from signed-URL minting or object size. Signing is
|
|
19
|
+
authorization and the URL may never be used or may be reused many times. Actual
|
|
20
|
+
direct OSS/COS/OBS bytes require provider access logs or billing reconciliation.
|
|
21
|
+
Mortar-served responses can be counted at the response writer, but direct object
|
|
22
|
+
downloads bypass it.
|
|
23
|
+
|
|
24
|
+
Managed-log billing uses provider-canonical ingestion records with durable IDs
|
|
25
|
+
for deduplication; query response bytes are not ingestion. Function logs retain
|
|
26
|
+
their function/source context. Never turn a read query or repeated reconciliation
|
|
27
|
+
scan into duplicate usage rows.
|