@cellapp/cli 0.1.0 → 0.2.1
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/LICENSE +1 -1
- package/NOTICE +1 -1
- package/README.md +25 -10
- package/dist/packages/cli/src/client.js +1 -1
- package/dist/packages/cli/src/data.js +42 -0
- package/dist/packages/cli/src/data.js.map +1 -0
- package/dist/packages/cli/src/deploy.js +28 -11
- package/dist/packages/cli/src/deploy.js.map +1 -1
- package/dist/packages/cli/src/main.js +23 -13
- package/dist/packages/cli/src/main.js.map +1 -1
- package/dist/packages/cli/src/origin.js +2 -1
- package/dist/packages/cli/src/origin.js.map +1 -1
- package/dist/packages/cli/src/update.js +57 -0
- package/dist/packages/cli/src/update.js.map +1 -0
- package/dist/packages/cli/src/version.js +2 -0
- package/dist/packages/cli/src/version.js.map +1 -0
- package/dist/packages/shared/src/data.js +103 -0
- package/dist/packages/shared/src/data.js.map +1 -0
- package/package.json +26 -7
- package/skills/cellapp-deploy/SKILL.md +35 -10
- package/skills/cellapp-deploy/examples/notes/README.md +21 -0
- package/skills/cellapp-deploy/examples/notes/app.ts +115 -0
- package/skills/cellapp-deploy/examples/notes/cellapp.data.json +22 -0
- package/skills/cellapp-deploy/examples/notes/index.html +40 -0
- package/skills/cellapp-deploy/examples/notes/notes.ts +37 -0
- package/skills/cellapp-deploy/examples/notes/package-lock.json +622 -0
- package/skills/cellapp-deploy/examples/notes/package.json +16 -0
- package/skills/cellapp-deploy/examples/notes/tsconfig.json +13 -0
package/package.json
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cellapp/cli",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Cellapp static application deployment CLI and Agent Skill",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"publishConfig": {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public",
|
|
9
|
+
"registry": "https://registry.npmjs.org/"
|
|
10
|
+
},
|
|
11
|
+
"workspaces": [
|
|
12
|
+
"packages/cli",
|
|
13
|
+
"packages/shared"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=22.12.0"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"cellapp": "dist/packages/cli/src/main.js",
|
|
20
|
+
"ohmyapp": "dist/packages/cli/src/main.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist/packages",
|
|
24
|
+
"skills/cellapp-deploy",
|
|
25
|
+
"!skills/cellapp-deploy/examples/**/node_modules",
|
|
26
|
+
"!skills/cellapp-deploy/examples/**/dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"NOTICE"
|
|
30
|
+
],
|
|
12
31
|
"scripts": {
|
|
13
32
|
"build": "tsc -p tsconfig.json",
|
|
14
33
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
@@ -1,22 +1,47 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cellapp-deploy
|
|
3
|
-
description:
|
|
3
|
+
description: Build and publish a static Cellapp frontend, manage its PostgreSQL tables and row policies, and connect supported supabase-js CRUD without an application backend.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Develop and deploy a Cellapp application
|
|
7
7
|
|
|
8
|
-
Use the installed `cellapp` CLI. Installing this Skill does not install the CLI; if it is unavailable,
|
|
8
|
+
Use the installed `cellapp` CLI. Installing this Skill does not install the CLI; if it is unavailable, direct the user to the public repository README. Use an origin supplied by the user, existing project configuration, or `CELLAPP_ORIGIN` when present; otherwise the CLI defaults to `https://console.cellapp.ai`. Preserve `ohmyapp.json` and `OHMYAPP_ORIGIN` migration behavior. Do not invent another origin or store a credential in the project.
|
|
9
9
|
|
|
10
|
-
Inspect
|
|
10
|
+
## Inspect
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Read `cellapp.json`, the existing build command, static output directory, and `cellapp.data.json` if present. Reuse the application association on updates. For plain HTML omit the build command. Ask only for an ambiguous output or build setting. Cellapp hosts static frontend code; do not deploy SSR, a custom server, private service key, direct PostgreSQL credential, or arbitrary SQL. Bundled frontend values are readable by admitted visitors.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
## Choose an application reference
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
The display name may repeat. Every owner has one public user slug (`namespace` in the API and CLI), and application slugs are unique inside it. On the first deployment a new owner may choose `--namespace <user-slug>`; each new app may choose `--slug <app-slug>`. Both use lowercase ASCII letters, digits and hyphens and start with a letter. The user slug needs at least three characters, reserved names such as `accounts`, `cloud`, `login` and `app` are unavailable, and `{app-slug}-{user-slug}` must fit one 63-character DNS label. Without choices, Cellapp assigns a privacy-preserving user slug and an application slug derived from the display name, adding a suffix on collision. Existing owners must reuse their established user slug for CLI creation. The console can update either slug after creation. The returned URL uses `{app-slug}-{user-slug}.<apps-domain>`; prior addresses remain assigned to the original app. The `@user-slug/app-slug` share link redirects to the current host. Never treat a slug or display name as an authorization identity; app access and tables remain bound to the immutable app ID.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
## Plan, apply, and verify tables
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
The versioned, non-secret [`cellapp.data.json`](examples/notes/cellapp.data.json) manifest defines tables, columns, primary keys, indexes and operation policies. Types: `text`, `uuid`, `bigint`, `boolean`, `timestamptz`, `jsonb`. Policy modes: `none` (default), `all-admitted-users`, `own-rows`. The `own-rows` mode needs a non-null text `ownerColumn`; reads and writes are checked against the signed application user ID. Every table needs a primary key. No SQL, function, trigger, database password or provider identity belongs in the manifest. Table names are scoped to the app: an app may define `users` without touching platform users or another app's `users`. This is ordinary app data, not the platform identity source. Keep names unique within one app; `rpc` is reserved for the unsupported PostgREST route, PostgreSQL system column names are unavailable, and generated index names must not collide with table names. Check these rules with `data plan` before applying.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
A new project needs its first static deployment to create the application association in `cellapp.json`. Build and deploy the frontend once, then use the linked app and configured origin to apply the data manifest. Do not report the table-dependent page as ready until verification succeeds:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
cellapp data status --project <project>
|
|
26
|
+
cellapp data plan --project <project>
|
|
27
|
+
cellapp data apply --project <project>
|
|
28
|
+
cellapp data verify --project <project>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The first manifest version is `1`; increase by exactly one. Inspect the plan and data version separately from the static release. Identical retries are safe; changed content at an applied version conflicts. For a table or column drop, set `allowDestructive: true` in the manifest and pass `--allow-destructive` to `data apply` after reviewing the plan and backup implications. After a timeout, run status and verify, then retry the same manifest if needed. Do not create a new version for a retry. Adding a non-null column to a populated table fails and rolls back; use a nullable column in this initial subset.
|
|
32
|
+
|
|
33
|
+
New tables and operations deny frontend access until granted. Apply with an owner credential. Verify an allowed operation and a denied cross-user or ungranted operation before handing out the app URL. Distinguish database-only tests from real browser and PostgREST acceptance. The application database is separate from control data and other apps; application code gets no database password.
|
|
34
|
+
|
|
35
|
+
## Connect the static frontend
|
|
36
|
+
|
|
37
|
+
Use `@supabase/supabase-js@2.117.3` for the declared table subset. Initialize `createClient(window.location.origin, 'cellapp-public', {auth: {autoRefreshToken: false, persistSession: false, detectSessionInUrl: false}})`. The publishable string is non-secret and grants nothing. Cellapp checks the logged-in user's host-only app session and admission on each request. Fetch `/_cellapp/me` with same-origin credentials for the stable app-scoped ID; never expose or derive a global Cellapp ID. See the [runnable notes demo](examples/notes/README.md), which includes its build, manifest, and browser CRUD page.
|
|
38
|
+
|
|
39
|
+
Use only `.from(table).select(columns)`, `.insert(values)`, `.update(values)`, `.delete()`, `.eq()`, `.order()`, `.range()` and `.select()` after a write for returned rows. Handle `{data,error}` and 401/403 states visibly. Supabase Auth, Realtime, Storage, RPC, Edge Functions, joins, arbitrary filters, direct database access, and custom application servers are outside this release. The gateway ignores client authorization/profile headers. A public app admits any signed-in Cellapp user, while tables still require policies. A restricted app requires owner approval or access-key redemption after login.
|
|
40
|
+
|
|
41
|
+
## Publish and report
|
|
42
|
+
|
|
43
|
+
Run `cellapp deploy --project <project> --output <output-directory> --build <existing-build-command>` (optionally adding `--namespace` and `--slug` on the first deployment), omitting `--build` for plain HTML. Add `--spa` only for client-side routes. The CLI performs device authorization when needed; provide the verification link and code, keep the command running, and never request account passwords. Report publication only after `status: published`. Report data version/verification and static release separately, then provide the app URL.
|
|
44
|
+
|
|
45
|
+
The first deployment returns an access key once. Give it only to the requesting owner; never put it in a URL, repository, issue or project state. Old keys can be redeemed only after platform login. `cellapp reset-key <app-id>` rotates future redemption; existing user grants remain active. Revoke an individual grant in the owner console. Use reset or delete only when requested. A lost initial key can be explicitly rotated.
|
|
46
|
+
|
|
47
|
+
On transient upload/network failure, retry the same deploy command once: pending project metadata reuses the operation. Stop after another failure and report the error. A release version conflict needs review. If creation reports `app_data_not_ready`, keep `cellapp.json`, inspect `cellapp data status --project <project>` and retry `cellapp data reconcile --project <project>` after the platform data service is healthy; then retry deploy. Do not switch slugs or delete the app to work around this error. For an app created by an older CLI without `cellapp.json`, run `cellapp link <app-id> --project <project> --output <output-directory>` using an ID verified in `cellapp apps`. The CLI keeps the initial access key only in its private credential directory until publication; never copy it into project files. For data errors use `data status` and `data verify`; do not claim a table-dependent frontend is ready while its data API is unready. Browser local-only state does not synchronize between devices; managed table data does.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Cellapp 笔记 Demo
|
|
2
|
+
|
|
3
|
+
这是一个可以直接发布的静态前端。Cellapp 管理登录身份、应用数据库和访问控制;浏览器用 `supabase-js` 读写 `notes` 表。每位已获准访问应用的用户只能看到自己的笔记。无需自建服务端、Supabase 项目或数据库密码。
|
|
4
|
+
|
|
5
|
+
## 运行
|
|
6
|
+
|
|
7
|
+
需要 Node.js 22.12+、已安装并登录的 `cellapp` CLI,以及一个可用的 Cellapp 环境。先设置 `CELLAPP_ORIGIN` 为控制台 HTTPS 地址。
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
cd <本示例目录>
|
|
11
|
+
npm ci
|
|
12
|
+
npm run build
|
|
13
|
+
cellapp deploy --project . --output dist --name "Notes demo" --slug notes-demo
|
|
14
|
+
cellapp data plan --project .
|
|
15
|
+
cellapp data apply --project .
|
|
16
|
+
cellapp data verify --project .
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
首次部署可加 `--namespace <你的空间名>` 自选命名空间;未指定时 Cellapp 生成与平台用户 ID 无关的空间名。示例中的 `--slug notes-demo` 在同一空间内必须唯一。应用显示名可以重名,控制台提供 `@空间名/notes-demo` 分享链接,原应用 ID 地址保持稳定。首次部署会创建应用及项目关联,然后 `data apply` 在独立应用数据库中建表并授予当前应用的表操作权限。首次发布返回的访问密钥只交给所有者,不要存入项目文件。打开部署返回的应用地址,完成 Cellapp 登录;受限应用还需授权访问。页面可新增、查看、编辑、删除笔记;“Run CRUD” 会依次执行创建、读取、更新和删除,并显示结果。第一次打开前应完成 `data verify`,确认 `ready: true`。
|
|
20
|
+
|
|
21
|
+
`cellapp.data.json` 声明表、主键、索引及 `own-rows` 策略。[`notes.ts`](notes.ts) 只用支持的 `supabase-js` 表级操作和 `/_cellapp/me` 应用内身份;`cellapp-public` 是非秘密标识,不授予任何数据权限。构建产物仅包含静态前端,不包含 CLI 凭证、访问密钥或数据库连接串。
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { addNote, currentAppUser, deleteNote, listNotes, updateNote } from './notes.js';
|
|
2
|
+
|
|
3
|
+
const form = document.querySelector<HTMLFormElement>('#create-form')!;
|
|
4
|
+
const input = document.querySelector<HTMLInputElement>('#note-body')!;
|
|
5
|
+
const notes = document.querySelector<HTMLUListElement>('#notes')!;
|
|
6
|
+
const identity = document.querySelector<HTMLElement>('#identity')!;
|
|
7
|
+
const status = document.querySelector<HTMLElement>('#status')!;
|
|
8
|
+
const check = document.querySelector<HTMLButtonElement>('#crud')!;
|
|
9
|
+
|
|
10
|
+
function report(message: string, error = false): void {
|
|
11
|
+
status.textContent = message;
|
|
12
|
+
status.classList.toggle('error', error);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function failure(error: unknown): void {
|
|
16
|
+
report(error instanceof Error ? error.message : String(error), true);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function refresh(): Promise<void> {
|
|
20
|
+
const rows = await listNotes();
|
|
21
|
+
notes.replaceChildren();
|
|
22
|
+
if (!rows?.length) {
|
|
23
|
+
const empty = document.createElement('li');
|
|
24
|
+
empty.textContent = '还没有笔记。';
|
|
25
|
+
notes.append(empty);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
for (const row of rows) {
|
|
29
|
+
const item = document.createElement('li');
|
|
30
|
+
const body = document.createElement('span');
|
|
31
|
+
body.textContent = row.body;
|
|
32
|
+
const actions = document.createElement('div');
|
|
33
|
+
const edit = document.createElement('button');
|
|
34
|
+
edit.type = 'button';
|
|
35
|
+
edit.className = 'secondary';
|
|
36
|
+
edit.textContent = '编辑';
|
|
37
|
+
edit.setAttribute('aria-label', `编辑笔记 ${row.body}`);
|
|
38
|
+
edit.addEventListener('click', async () => {
|
|
39
|
+
const next = prompt('修改笔记', row.body);
|
|
40
|
+
if (next === null || !next.trim()) return;
|
|
41
|
+
edit.disabled = true;
|
|
42
|
+
try {
|
|
43
|
+
await updateNote(row.id, next.trim());
|
|
44
|
+
await refresh();
|
|
45
|
+
report('笔记已更新');
|
|
46
|
+
} catch (error) { failure(error); }
|
|
47
|
+
finally { edit.disabled = false; }
|
|
48
|
+
});
|
|
49
|
+
const remove = document.createElement('button');
|
|
50
|
+
remove.type = 'button';
|
|
51
|
+
remove.className = 'secondary';
|
|
52
|
+
remove.textContent = '删除';
|
|
53
|
+
remove.setAttribute('aria-label', `删除笔记 ${row.body}`);
|
|
54
|
+
remove.addEventListener('click', async () => {
|
|
55
|
+
remove.disabled = true;
|
|
56
|
+
try {
|
|
57
|
+
await deleteNote(row.id);
|
|
58
|
+
await refresh();
|
|
59
|
+
report('笔记已删除');
|
|
60
|
+
} catch (error) { failure(error); }
|
|
61
|
+
finally { remove.disabled = false; }
|
|
62
|
+
});
|
|
63
|
+
actions.append(edit, remove);
|
|
64
|
+
item.append(body, actions);
|
|
65
|
+
notes.append(item);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
form.addEventListener('submit', async event => {
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
const body = input.value.trim();
|
|
72
|
+
if (!body) return;
|
|
73
|
+
const save = form.querySelector<HTMLButtonElement>('button')!;
|
|
74
|
+
save.disabled = true;
|
|
75
|
+
try {
|
|
76
|
+
await addNote(body);
|
|
77
|
+
input.value = '';
|
|
78
|
+
await refresh();
|
|
79
|
+
report('笔记已保存');
|
|
80
|
+
} catch (error) { failure(error); }
|
|
81
|
+
finally { save.disabled = false; }
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
check.addEventListener('click', async () => {
|
|
85
|
+
check.disabled = true;
|
|
86
|
+
const marker = `CRUD ${crypto.randomUUID()}`;
|
|
87
|
+
try {
|
|
88
|
+
const inserted = await addNote(marker);
|
|
89
|
+
if (inserted?.length !== 1) throw new Error('创建后未返回笔记');
|
|
90
|
+
const row = inserted[0]!;
|
|
91
|
+
const selected = await listNotes();
|
|
92
|
+
if (!selected?.some(note => note.id === row.id && note.body === marker)) throw new Error('未读到刚创建的笔记');
|
|
93
|
+
const changed = await updateNote(row.id, marker + ' updated');
|
|
94
|
+
if (changed?.[0]?.body !== marker + ' updated') throw new Error('更新后的内容不一致');
|
|
95
|
+
await deleteNote(row.id);
|
|
96
|
+
const remaining = await listNotes();
|
|
97
|
+
if (remaining?.some(note => note.id === row.id)) throw new Error('删除后仍能读到笔记');
|
|
98
|
+
await refresh();
|
|
99
|
+
report('CRUD passed');
|
|
100
|
+
} catch (error) { failure(error); }
|
|
101
|
+
finally { check.disabled = false; }
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
void (async () => {
|
|
105
|
+
try {
|
|
106
|
+
const user = await currentAppUser();
|
|
107
|
+
document.body.dataset.user = user;
|
|
108
|
+
identity.textContent = `当前应用用户:${user.slice(0, 8)}…`;
|
|
109
|
+
await refresh();
|
|
110
|
+
report('应用数据库已连接');
|
|
111
|
+
} catch (error) {
|
|
112
|
+
identity.textContent = '无法连接应用数据库';
|
|
113
|
+
failure(error);
|
|
114
|
+
}
|
|
115
|
+
})();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"tables": [
|
|
4
|
+
{
|
|
5
|
+
"name": "notes",
|
|
6
|
+
"columns": [
|
|
7
|
+
{"name": "id", "type": "uuid"},
|
|
8
|
+
{"name": "owner_id", "type": "text"},
|
|
9
|
+
{"name": "body", "type": "text"}
|
|
10
|
+
],
|
|
11
|
+
"primaryKey": ["id"],
|
|
12
|
+
"indexes": [{"columns": ["owner_id"]}],
|
|
13
|
+
"policies": {
|
|
14
|
+
"select": "own-rows",
|
|
15
|
+
"insert": "own-rows",
|
|
16
|
+
"update": "own-rows",
|
|
17
|
+
"delete": "own-rows",
|
|
18
|
+
"ownerColumn": "owner_id"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Cellapp 笔记 Demo</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { font-family: system-ui, sans-serif; color: #1c2b25; background: #f4f7f3; }
|
|
9
|
+
body { max-width: 720px; margin: 40px auto; padding: 0 20px; }
|
|
10
|
+
main { background: white; padding: 28px; border: 1px solid #d9e5dc; border-radius: 16px; box-shadow: 0 8px 32px #1536230d; }
|
|
11
|
+
h1 { margin: 0 0 8px; }
|
|
12
|
+
p { line-height: 1.5; }
|
|
13
|
+
form { display: flex; gap: 8px; margin: 20px 0; }
|
|
14
|
+
input { flex: 1; min-width: 0; padding: 10px; border: 1px solid #adbfaf; border-radius: 8px; font: inherit; }
|
|
15
|
+
button { padding: 9px 14px; border: 0; border-radius: 8px; color: white; background: #21684a; font: inherit; cursor: pointer; }
|
|
16
|
+
button:disabled { opacity: .55; cursor: wait; }
|
|
17
|
+
button.secondary { color: #21684a; background: #e5f0e8; }
|
|
18
|
+
li { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 0; border-top: 1px solid #e7eee7; }
|
|
19
|
+
li span { overflow-wrap: anywhere; }
|
|
20
|
+
li div { display: flex; gap: 8px; flex: none; }
|
|
21
|
+
#status { min-height: 1.5em; color: #345446; }
|
|
22
|
+
#status.error { color: #a22828; }
|
|
23
|
+
</style>
|
|
24
|
+
</head>
|
|
25
|
+
<body>
|
|
26
|
+
<main>
|
|
27
|
+
<h1>Notes app</h1>
|
|
28
|
+
<p>使用 Cellapp 登录身份保存笔记。每位用户只能读写自己的记录。</p>
|
|
29
|
+
<p id="identity">正在连接应用数据库…</p>
|
|
30
|
+
<form id="create-form">
|
|
31
|
+
<input id="note-body" aria-label="笔记内容" maxlength="2000" required placeholder="写一条笔记">
|
|
32
|
+
<button type="submit">保存笔记</button>
|
|
33
|
+
</form>
|
|
34
|
+
<p id="status" role="status" aria-live="polite"></p>
|
|
35
|
+
<ul id="notes"></ul>
|
|
36
|
+
<button id="crud" class="secondary" type="button">Run CRUD</button>
|
|
37
|
+
</main>
|
|
38
|
+
<script type="module" src="/app.js"></script>
|
|
39
|
+
</body>
|
|
40
|
+
</html>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createClient } from '@supabase/supabase-js';
|
|
2
|
+
|
|
3
|
+
const client = createClient(window.location.origin, 'cellapp-public', {
|
|
4
|
+
auth: { autoRefreshToken: false, persistSession: false, detectSessionInUrl: false },
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
export async function currentAppUser(): Promise<string> {
|
|
8
|
+
const response = await fetch('/_cellapp/me', { credentials: 'same-origin' });
|
|
9
|
+
if (!response.ok) throw new Error(response.status === 401 ? 'Sign in to Cellapp' : 'Application access required');
|
|
10
|
+
const value: unknown = await response.json();
|
|
11
|
+
if (!value || typeof value !== 'object' || !('id' in value) || typeof value.id !== 'string') throw new Error('Invalid application identity');
|
|
12
|
+
return value.id;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function addNote(body: string) {
|
|
16
|
+
const ownerId = await currentAppUser();
|
|
17
|
+
const result = await client.from('notes').insert({ id: crypto.randomUUID(), owner_id: ownerId, body }).select('id,body');
|
|
18
|
+
if (result.error) throw result.error;
|
|
19
|
+
return result.data;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function listNotes() {
|
|
23
|
+
const result = await client.from('notes').select('id,body').order('id').range(0, 49);
|
|
24
|
+
if (result.error) throw result.error;
|
|
25
|
+
return result.data;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function updateNote(id: string, body: string) {
|
|
29
|
+
const result = await client.from('notes').update({ body }).eq('id', id).select('id,body');
|
|
30
|
+
if (result.error) throw result.error;
|
|
31
|
+
return result.data;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function deleteNote(id: string) {
|
|
35
|
+
const result = await client.from('notes').delete().eq('id', id);
|
|
36
|
+
if (result.error) throw result.error;
|
|
37
|
+
}
|