@mortar-ai/skill 0.1.0 → 0.3.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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # @mortar-ai/skill
2
2
 
3
- [Claude Code](https://claude.ai/code) skill package for building on
4
- **Mortar** (the BaaS that AI bundles talk to). Drop it into your skills
5
- directory + Claude becomes instantly Mortar-fluent: knows the SDK
3
+ [Agent Skills](https://agentskills.io/) package for building on **Mortar** (the
4
+ BaaS that AI bundles talk to). Install the canonical `SKILL.md` into a compatible
5
+ agent's skill directory to teach it the SDK
6
6
  accessors, CLI commands, MCP tools, and — most importantly — the
7
7
  tenant-isolation + `auth.tenant_id()` RLS rules that are the easiest
8
8
  thing to get wrong.
@@ -17,6 +17,12 @@ mortar-skill-install
17
17
 
18
18
  Then restart Claude Code (or `/skill reload`).
19
19
 
20
+ The same versioned `SKILL.md` is packaged into Fabric Harness. `manifest.json`
21
+ binds the npm version and Harness preinstall to its SHA-256 digest; the Fabric
22
+ Server rejects a Market install when the running Harness reports different
23
+ bytes. npm is an external distribution channel and is not required by Fabric at
24
+ runtime.
25
+
20
26
  Pairs naturally with the [Mortar MCP server](../mcp) — the skill teaches Claude
21
27
  *how* to think about Mortar; the MCP gives it live tools to *act* on a
22
28
  project. Install both for the full Supabase-plugin-equivalent
@@ -29,6 +35,12 @@ The skill teaches Claude:
29
35
  - **SDK surface** — the `mortar.from()` / `auth` / `storage` /
30
36
  `realtime` / `compute` / `usage` / `cron` / `queue` / `comms`
31
37
  accessors + the account-scope `AccountClient`; which one to reach for
38
+ - **Transaction boundaries** — atomic row batches and filtered mutations,
39
+ trusted cross-table row + PostgreSQL queue transactions, and why privileged
40
+ transaction clients must never enter browser/mobile bundles
41
+ - **Storage consistency** — immutable physical object versions, atomic metadata
42
+ pointer switches, durable cleanup, and why object storage is not part of a
43
+ PostgreSQL ACID transaction
32
44
  - **CLI commands** — `mortar login / use / db / storage / compute /
33
45
  cron / queue / domains / comms / keys / tokens / topup / usage`
34
46
  - **MCP tools** — the 40 `mortar_*` tools and when an agent should use
@@ -56,6 +68,19 @@ Useful for team-shared skill repositories or in-repo skill packs.
56
68
  Re-run `mortar-skill-install` after `npm update @mortar-ai/skill`.
57
69
  Idempotent — overwrites in place.
58
70
 
71
+ ## Maintainer release
72
+
73
+ `SKILL.md` is the only hand-edited instruction source. Regenerate the manifest
74
+ and Harness copy before testing or publishing:
75
+
76
+ ```bash
77
+ npm run sync
78
+ npm run check
79
+ npm pack --dry-run
80
+ ```
81
+
82
+ Never edit `fabric/harness/internal/skills/preinstalled/mortar/` directly.
83
+
59
84
  ## License
60
85
 
61
86
  Apache-2.0 (same as the rest of Mortar).
@@ -1,10 +1,11 @@
1
1
  ---
2
- name: Mortar
3
- description: Required guidance for Fabric apps that need a database, backend, authentication, persistence, file storage, realtime collaboration, cloud functions, or product analytics.
4
- activation: auto
5
- priority: high
6
- triggers:
7
- - keywords: [database, 数据库, backend, 后端, auth, authentication, 登录, 认证, persistence, 持久化, storage, upload, 文件, realtime, 实时, collaboration, compute, cloud, 云端]
2
+ name: mortar
3
+ description: Build or change Fabric applications that use Mortar for database, auth, storage, realtime, compute, or analytics. Use for Mortar schema and provisioning, SDK integration, RLS, transactions, or storage consistency; not for Fabric's own control plane or a different backend.
4
+ metadata:
5
+ activation: auto
6
+ priority: high
7
+ triggers:
8
+ - keywords: [database, 数据库, backend, 后端, auth, authentication, 登录, 认证, persistence, 持久化, transaction, 事务, ACID, storage, upload, 文件, realtime, 实时, collaboration, compute, cloud, 云端]
8
9
  ---
9
10
 
10
11
  # Mortar for Fabric applications
@@ -59,14 +60,16 @@ below; if platform provisioning fails, report that platform failure.
59
60
  tenant. Do not edit or create `mortar.config.json`. Do not edit generated
60
61
  `integrations/mortar/types.ts`; Fabric regenerates it from the live schema.
61
62
  4. Run `npm install @mortar-ai/client` if the dependency is absent, then write app
62
- code using the injected singleton.
63
- 5. Type-check and build. Handle every `{ data, error }` result in UI code.
64
-
65
- Example schema operations (the tool schema is authoritative for all fields):
66
-
67
- ```json
68
- {"kind":"create_table","table":"todos","access":"private","columns":[{"name":"title","type":"text","required":true},{"name":"done","type":"bool"},{"name":"user_id","type":"uuid","required":true}]}
69
- ```
63
+ code using the injected singleton. Atomic batches, server-side filtered
64
+ mutations, and `DatabaseTransactions` require `@mortar-ai/client` 0.8.0 or
65
+ newer. If an existing project needs one of those capabilities and pins an
66
+ older version, update that dependency with npm and let npm update its lockfile;
67
+ do not rewrite unrelated dependencies.
68
+ 5. Before writing a multi-step mutation, choose the supported consistency
69
+ boundary: one-table batches and filtered mutations can use the bundle-safe
70
+ singleton; cross-table row + queue transactions require trusted server code;
71
+ object storage and external services never join a PostgreSQL transaction.
72
+ 6. Type-check and build. Handle every `{ data, error }` result in UI code.
70
73
 
71
74
  Supported migration kinds are `create_table`, `drop_table`, `set_table_access`,
72
75
  `add_column`, `drop_column`, `create_index`, `set_bucket_access`, `deploy_function`, `delete_function`,
@@ -109,21 +112,86 @@ const { data: created, error: insertError } = await mortar
109
112
  .insert({ title: 'Ship', done: false, user_id: userId })
110
113
  .select()
111
114
  .single();
115
+ ```
116
+
117
+ Updates and deletes use the same filter chain. Useful operators include `eq`,
118
+ `neq`, ranges, `like/ilike`, `in`, `is`, `order`, `limit`, `single`, and
119
+ `maybeSingle`.
112
120
 
113
- const { data: updated, error: updateError } = await mortar
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
114
131
  .from('todos')
115
- .update({ done: true })
116
- .eq('id', todoId)
132
+ .insert([
133
+ { title: 'Prepare', done: false, user_id: userId },
134
+ { title: 'Ship', done: false, user_id: userId },
135
+ ])
117
136
  .select();
137
+ if (batchError) throw new Error(batchError.message);
118
138
 
119
- const { error: deleteError } = await mortar
139
+ const { data: changedTodos, error: updateError } = await mortar
120
140
  .from('todos')
121
- .delete()
122
- .eq('id', todoId);
141
+ .update({ done: true })
142
+ .eq('user_id', userId)
143
+ .eq('archived', false)
144
+ .select();
145
+ if (updateError) throw new Error(updateError.message);
123
146
  ```
124
147
 
125
- Useful chaining includes `eq`, `neq`, `gt/gte`, `lt/lte`, `like/ilike`, `in`,
126
- `is`, `order`, `range`, `limit`, `single`, and `maybeSingle`.
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.
127
195
 
128
196
  ## App-user authentication
129
197
 
@@ -139,15 +207,12 @@ const verified = await mortar.auth.verifyOtp({ email, token });
139
207
 
140
208
  const { data: sessionData } = await mortar.auth.getSession();
141
209
  const { data: userData, error: userError } = await mortar.auth.getUser();
142
-
143
- const { data: listener } = mortar.auth.onAuthStateChange((event, session) => {
144
- // INITIAL_SESSION | SIGNED_IN | SIGNED_OUT | TOKEN_REFRESHED
145
- });
146
- listener.subscription.unsubscribe();
147
-
148
210
  await mortar.auth.signOut();
149
211
  ```
150
212
 
213
+ Use `onAuthStateChange` when UI must react to session changes, and unsubscribe
214
+ the returned subscription during cleanup.
215
+
151
216
  Password reset starts with `mortar.auth.resetPasswordForEmail(email)`; confirm via
152
217
  `passwordResetConfirm(email, token, newPassword)`. Never store or pass session
153
218
  tokens manually unless the user explicitly needs a server integration.
@@ -171,6 +236,22 @@ const { data: signed, error: signError } = await bucket.createSignedUrl(path, 36
171
236
  const { error: removeError } = await bucket.remove([path]);
172
237
  ```
173
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
+
174
255
  Downloads mint a short-lived URL and fetch the object directly from the storage
175
256
  backend. Reuse `signed.signedUrl` until it nears expiry when rendering media.
176
257
  Prefer the signed URL directly in `<img src>`, `<video src>`, or `<audio src>`;
@@ -180,16 +261,9 @@ budget, but clients should still lazy-load visible media and cache/reuse each
180
261
  URL instead of signing again on every render. Signing authorizes a possible
181
262
  download; it does not prove bytes were served and must never be described or
182
263
  modeled as an object-egress charge. Only provider-observed access-log or billing
183
- reconciliation may record actual object egress. Use the third
184
- `createSignedUrl` argument to bound only the signing request when the UI needs
185
- a deadline:
186
-
187
- ```ts
188
- const { data: signed, error } = await bucket.createSignedUrl(path, 900, {
189
- signal: controller.signal,
190
- timeoutMs: 10_000,
191
- });
192
- ```
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.
193
267
 
194
268
  `bucket.download(path)` uses JavaScript `fetch()` against the object store and
195
269
  therefore needs a matching bucket CORS rule in browsers. Appunvs-managed Mortar
@@ -223,26 +297,10 @@ realtime only notifies peers.
223
297
 
224
298
  ## Compute and analytics
225
299
 
226
- Invoke only functions already deployed through `mortar_migrate`:
227
-
228
- ```ts
229
- const response = await mortar.compute.invoke({
230
- name: 'summarize',
231
- method: 'POST',
232
- body: JSON.stringify({ text }),
233
- contentType: 'application/json',
234
- });
235
- if (!response.ok) throw new Error(await response.text());
236
- ```
237
-
238
- Analytics calls are fire-and-forget except `flush`:
239
-
240
- ```ts
241
- mortar.analytics.track('pageview', { path: location.pathname });
242
- mortar.analytics.identify(user.id, { plan: 'free' });
243
- mortar.analytics.captureError(error, { screen: 'checkout' });
244
- await mortar.analytics.flush();
245
- ```
300
+ Invoke only functions already deployed through `mortar_migrate`; check
301
+ `response.ok` and read the response body. Analytics provides `track`,
302
+ `identify`, `captureError`, and `flush`; events are fire-and-forget except
303
+ `flush`.
246
304
 
247
305
  ## Boundary for non-Fabric development
248
306
 
package/bin/install.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * mortar-skill-install — copies mortar-skill.md into the user's Claude
3
+ * mortar-skill-install — copies the canonical SKILL.md into the user's Claude
4
4
  * Code skills directory.
5
5
  *
6
6
  * Default destination: `~/.claude/skills/mortar/SKILL.md`. Override
@@ -26,7 +26,7 @@ function parseFlags(argv) {
26
26
 
27
27
  function main() {
28
28
  const flags = parseFlags(process.argv);
29
- const src = resolve(__dirname, '..', 'mortar-skill.md');
29
+ const src = resolve(__dirname, '..', 'SKILL.md');
30
30
  if (!existsSync(src)) {
31
31
  console.error(`mortar-skill: source missing at ${src}`);
32
32
  process.exit(1);
package/index.js CHANGED
@@ -9,7 +9,8 @@
9
9
  const { readFileSync } = require('node:fs');
10
10
  const { resolve } = require('node:path');
11
11
 
12
- const SKILL_PATH = resolve(__dirname, 'mortar-skill.md');
12
+ const SKILL_PATH = resolve(__dirname, 'SKILL.md');
13
+ const MANIFEST_PATH = resolve(__dirname, 'manifest.json');
13
14
 
14
15
  module.exports = {
15
16
  /**
@@ -20,5 +21,9 @@ module.exports = {
20
21
  skillContents() {
21
22
  return readFileSync(SKILL_PATH, 'utf8');
22
23
  },
24
+ manifest() {
25
+ return JSON.parse(readFileSync(MANIFEST_PATH, 'utf8'));
26
+ },
23
27
  SKILL_PATH,
28
+ MANIFEST_PATH,
24
29
  };
package/manifest.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "schema_version": 1,
3
+ "id": "mortar",
4
+ "version": "0.3.0",
5
+ "content_sha256": "sha256:c69dab74ad4a377086c0c883c4d400770600738de2741778fad21ec27e925f27",
6
+ "source_repo": "liamxujia/appunvs",
7
+ "source_path": "mortar/skill/SKILL.md",
8
+ "distributions": [
9
+ "harness_preinstalled",
10
+ "npm"
11
+ ],
12
+ "npm": {
13
+ "package": "@mortar-ai/skill",
14
+ "version": "0.3.0"
15
+ }
16
+ }
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@mortar-ai/skill",
3
- "version": "0.1.0",
4
- "description": "Claude Code skill package for building on Mortar (BaaS). Drop `mortar-skill.md` into ~/.claude/skills/mortar/ to give Claude expert-level guidance about the Mortar SDK / CLI / MCP, tenant isolation + auth.tenant_id() RLS, and the 9 primitive × feature model.",
3
+ "version": "0.3.0",
4
+ "description": "Agent skill package for building Fabric applications on Mortar (BaaS), distributed from one versioned SKILL.md with a content-addressed manifest.",
5
5
  "main": "index.js",
6
- "files": ["mortar-skill.md", "index.js", "README.md"],
6
+ "files": ["SKILL.md", "manifest.json", "index.js", "README.md"],
7
7
  "bin": {
8
8
  "mortar-skill-install": "bin/install.js"
9
9
  },
10
+ "scripts": {
11
+ "sync": "node scripts/sync-distributions.js",
12
+ "check": "node scripts/sync-distributions.js --check"
13
+ },
10
14
  "publishConfig": { "access": "public" },
11
15
  "license": "Apache-2.0"
12
16
  }