@spooky-sync/cli 0.0.0-canary.1 → 0.0.1-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +155 -0
- package/README.md +9 -9
- package/devtools-mcp/index.js +24030 -0
- package/dist/cli.cjs +1 -1
- package/dist/cli.js +5 -40
- package/dist/index.d.ts.map +1 -1
- package/dist/resolve-binary-J8VAJRMF.cjs +8 -0
- package/dist/resolve-binary-kH1Jwk0z.js +60 -0
- package/dist/resolve-binary.d.ts +2 -0
- package/dist/resolve-binary.d.ts.map +1 -0
- package/dist/syncgen.cjs +1 -4
- package/dist/syncgen.js +11 -33
- package/package.json +18 -8
- package/templates/cookbook/INDEX.md +31 -0
- package/templates/cookbook/crdt-text-field.tsx +36 -0
- package/templates/cookbook/live-list.tsx +22 -0
- package/templates/cookbook/optimistic-mutation.tsx +33 -0
- package/target/release/spooky +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";const
|
|
2
|
+
"use strict";const r=require("child_process"),s=require("./resolve-binary-J8VAJRMF.cjs"),i=s.findBinary(),e=r.spawnSync(i,process.argv.slice(2),{stdio:"inherit"});e.error&&(console.error(`Failed to execute spky: ${e.error.message}`),process.exit(1));process.exit(e.status??1);
|
package/dist/cli.js
CHANGED
|
@@ -1,41 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
const o =
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"-o, --output <path>",
|
|
8
|
-
"Path to the output file (extension determines format: .json, .ts, .dart)"
|
|
9
|
-
).option("-f, --format <format>", "Output format (json, typescript, dart, surql)").option("-p, --pretty", "Pretty print the JSON output (only for JSON format)", !0).option(
|
|
10
|
-
"-a, --all",
|
|
11
|
-
"Generate all formats (TypeScript and Dart) in addition to JSON Schema",
|
|
12
|
-
!1
|
|
13
|
-
).option("--no-header", "Disable the generated file comment header (enabled by default)", !1).option("--append <path>", "Path to another .surql file to append to the input").option("--modules-dir <path>", "Directory containing Surrealism modules to compile and bundle").option(
|
|
14
|
-
"--mode <mode>",
|
|
15
|
-
'Generation mode: "singlenode" (HTTP to single SSP), "cluster" (HTTP to scheduler), or "surrealism" (embedded WASM)',
|
|
16
|
-
"singlenode"
|
|
17
|
-
).option("--endpoint <url>", "SSP/Scheduler Endpoint URL").option("--secret <secret>", "SSP/Scheduler Auth Secret").option("--config <path>", "Path to spooky.yml configuration file").parse(process.argv);
|
|
18
|
-
const e = o.opts();
|
|
19
|
-
async function i() {
|
|
20
|
-
(!e.input || !e.output) && (console.error("Error: --input and --output are required"), o.help(), process.exit(1));
|
|
21
|
-
try {
|
|
22
|
-
const t = await r({
|
|
23
|
-
input: e.input,
|
|
24
|
-
output: e.output,
|
|
25
|
-
format: e.format,
|
|
26
|
-
pretty: e.pretty,
|
|
27
|
-
all: e.all,
|
|
28
|
-
noHeader: e.noHeader,
|
|
29
|
-
append: e.append,
|
|
30
|
-
modulesDir: e.modulesDir,
|
|
31
|
-
mode: e.mode,
|
|
32
|
-
endpoint: e.endpoint,
|
|
33
|
-
secret: e.secret,
|
|
34
|
-
config: e.config
|
|
35
|
-
});
|
|
36
|
-
console.log(t);
|
|
37
|
-
} catch (t) {
|
|
38
|
-
console.error("Error:", t instanceof Error ? t.message : String(t)), process.exit(1);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
i();
|
|
2
|
+
import { spawnSync as e } from "child_process";
|
|
3
|
+
import { f as s } from "./resolve-binary-kH1Jwk0z.js";
|
|
4
|
+
const o = s(), r = e(o, process.argv.slice(2), { stdio: "inherit" });
|
|
5
|
+
r.error && (console.error(`Failed to execute spky: ${r.error.message}`), process.exit(1));
|
|
6
|
+
process.exit(r.status ?? 1);
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,MAAM,GAAG,OAAO,CAAC;IAClD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA0DzE"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";const o=require("os"),e=require("path"),t=require("fs"),g=require("url"),k=require("module");var s=typeof document<"u"?document.currentScript:null;const x=g.fileURLToPath(typeof document>"u"?require("url").pathToFileURL(__filename).href:s&&s.tagName.toUpperCase()==="SCRIPT"&&s.src||new URL("resolve-binary-J8VAJRMF.cjs",document.baseURI).href),c=e.dirname(x),d={"darwin-arm64":"@spooky-sync/cli-darwin-arm64","darwin-x64":"@spooky-sync/cli-darwin-x64","linux-arm64":"@spooky-sync/cli-linux-arm64","linux-x64":"@spooky-sync/cli-linux-x64","win32-x64":"@spooky-sync/cli-win32-x64"};function $(){const r=`${o.platform()}-${o.arch()}`,n=d[r];if(!n)return;const l=o.platform()==="win32"?"spky.exe":"spky";try{const i=k.createRequire(typeof document>"u"?require("url").pathToFileURL(__filename).href:s&&s.tagName.toUpperCase()==="SCRIPT"&&s.src||new URL("resolve-binary-J8VAJRMF.cjs",document.baseURI).href).resolve(`${n}/package.json`);return e.resolve(e.dirname(i),l)}catch{return}}function w(){const r=o.platform()==="win32"?"spky.exe":"spky",n=$();if(n&&t.existsSync(n))return n;const l=[e.resolve(c,"../../../target/release",r),e.resolve(c,"../target/release",r),e.resolve(c,"../../../target/debug",r),e.resolve(c,"../target/debug",r)],u=l.filter(a=>t.existsSync(a));if(u.length>0)return u.sort((a,h)=>t.statSync(h).mtimeMs-t.statSync(a).mtimeMs),u[0];const i=e.resolve(c,"..",r);if(t.existsSync(i))return i;const p=e.resolve(process.cwd(),r);if(t.existsSync(p))return p;const m=`${o.platform()}-${o.arch()}`,y=d[m],f=y?`
|
|
2
|
+
Try installing the platform package: npm install ${y}`:`
|
|
3
|
+
Your platform (${m}) is not supported.`;throw new Error(`Could not find spky binary. Checked paths:
|
|
4
|
+
- Platform package (${y??"none"})
|
|
5
|
+
`+l.map(a=>` - ${a}
|
|
6
|
+
`).join("")+` - ${i}
|
|
7
|
+
- ${p}
|
|
8
|
+
`+f)}exports.findBinary=w;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { platform as m, arch as u } from "os";
|
|
2
|
+
import { dirname as d, resolve as t } from "path";
|
|
3
|
+
import { existsSync as c, statSync as f } from "fs";
|
|
4
|
+
import { fileURLToPath as h } from "url";
|
|
5
|
+
import { createRequire as w } from "module";
|
|
6
|
+
const $ = h(import.meta.url), i = d($), k = {
|
|
7
|
+
"darwin-arm64": "@spooky-sync/cli-darwin-arm64",
|
|
8
|
+
"darwin-x64": "@spooky-sync/cli-darwin-x64",
|
|
9
|
+
"linux-arm64": "@spooky-sync/cli-linux-arm64",
|
|
10
|
+
"linux-x64": "@spooky-sync/cli-linux-x64",
|
|
11
|
+
"win32-x64": "@spooky-sync/cli-win32-x64"
|
|
12
|
+
};
|
|
13
|
+
function P() {
|
|
14
|
+
const r = `${m()}-${u()}`, n = k[r];
|
|
15
|
+
if (!n) return;
|
|
16
|
+
const a = m() === "win32" ? "spky.exe" : "spky";
|
|
17
|
+
try {
|
|
18
|
+
const e = w(import.meta.url).resolve(`${n}/package.json`);
|
|
19
|
+
return t(d(e), a);
|
|
20
|
+
} catch {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function B() {
|
|
25
|
+
const r = m() === "win32" ? "spky.exe" : "spky", n = P();
|
|
26
|
+
if (n && c(n))
|
|
27
|
+
return n;
|
|
28
|
+
const a = [
|
|
29
|
+
t(i, "../../../target/release", r),
|
|
30
|
+
// workspace release
|
|
31
|
+
t(i, "../target/release", r),
|
|
32
|
+
// per-package release
|
|
33
|
+
t(i, "../../../target/debug", r),
|
|
34
|
+
// workspace debug
|
|
35
|
+
t(i, "../target/debug", r)
|
|
36
|
+
// per-package debug
|
|
37
|
+
], s = a.filter((o) => c(o));
|
|
38
|
+
if (s.length > 0)
|
|
39
|
+
return s.sort((o, x) => f(x).mtimeMs - f(o).mtimeMs), s[0];
|
|
40
|
+
const e = t(i, "..", r);
|
|
41
|
+
if (c(e))
|
|
42
|
+
return e;
|
|
43
|
+
const p = t(process.cwd(), r);
|
|
44
|
+
if (c(p))
|
|
45
|
+
return p;
|
|
46
|
+
const y = `${m()}-${u()}`, l = k[y], g = l ? `
|
|
47
|
+
Try installing the platform package: npm install ${l}` : `
|
|
48
|
+
Your platform (${y}) is not supported.`;
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Could not find spky binary. Checked paths:
|
|
51
|
+
- Platform package (${l ?? "none"})
|
|
52
|
+
` + a.map((o) => ` - ${o}
|
|
53
|
+
`).join("") + ` - ${e}
|
|
54
|
+
- ${p}
|
|
55
|
+
` + g
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
export {
|
|
59
|
+
B as f
|
|
60
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-binary.d.ts","sourceRoot":"","sources":["../src/resolve-binary.ts"],"names":[],"mappings":"AAiCA,wBAAgB,UAAU,IAAI,MAAM,CAuDnC"}
|
package/dist/syncgen.cjs
CHANGED
|
@@ -1,4 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
2
|
-
${n.map(r=>` - ${r}`).join(`
|
|
3
|
-
`)}
|
|
4
|
-
Please ensure it is built or installed.`)}async function y(e){const n=m(),r=[];r.push("--input",e.input),r.push("--output",e.output),e.format&&r.push("--format",e.format),e.pretty&&r.push("--pretty"),e.all&&r.push("--all"),e.noHeader&&r.push("--no-header"),e.append&&r.push("--append",e.append),e.modulesDir&&r.push("--modules-dir",e.modulesDir),e.mode&&r.push("--mode",e.mode),e.endpoint&&r.push("--endpoint",e.endpoint),e.secret&&r.push("--secret",e.secret),e.config&&r.push("--config",e.config);try{const{stdout:s,stderr:u}=await p(n,r);return u&&console.error(u),s}catch(s){throw new Error(`Syncgen failed: ${s.message}`)}}exports.runSyncgen=y;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("child_process"),a=require("util"),i=require("./resolve-binary-J8VAJRMF.cjs"),d=a.promisify(t.execFile);async function f(e){const n=i.findBinary(),r=[];r.push("--input",e.input),r.push("--output",e.output),e.format&&r.push("--format",e.format),e.pretty&&r.push("--pretty"),e.all&&r.push("--all"),e.noHeader&&r.push("--no-header"),e.append&&r.push("--append",e.append),e.modulesDir&&r.push("--modules-dir",e.modulesDir),e.mode&&r.push("--mode",e.mode),e.endpoint&&r.push("--endpoint",e.endpoint),e.secret&&r.push("--secret",e.secret),e.config&&r.push("--config",e.config);try{const{stdout:u,stderr:c}=await d(n,r);return c&&console.error(c),u}catch(u){throw new Error(`Syncgen failed: ${u.message}`,{cause:u})}}exports.runSyncgen=f;
|
package/dist/syncgen.js
CHANGED
|
@@ -1,39 +1,17 @@
|
|
|
1
|
-
import { execFile as
|
|
2
|
-
import { promisify as
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const p = o(s), l = d(import.meta.url), f = c(l);
|
|
8
|
-
function h() {
|
|
9
|
-
const e = u() === "win32" ? "spooky.exe" : "spooky", t = [
|
|
10
|
-
// Development/Source build location (from dist/index.js -> ../target/release)
|
|
11
|
-
i(f, "../target/release", e),
|
|
12
|
-
// Distributed package location (relative to dist/index.js -> ../bin)
|
|
13
|
-
i(f, "..", e),
|
|
14
|
-
// Installation root
|
|
15
|
-
i(process.cwd(), e)
|
|
16
|
-
];
|
|
17
|
-
for (const r of t)
|
|
18
|
-
if (m(r))
|
|
19
|
-
return r;
|
|
20
|
-
throw new Error(
|
|
21
|
-
`Could not find syncgen binary. Checked paths:
|
|
22
|
-
${t.map((r) => ` - ${r}`).join(`
|
|
23
|
-
`)}
|
|
24
|
-
Please ensure it is built or installed.`
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
async function _(e) {
|
|
28
|
-
const t = h(), r = [];
|
|
1
|
+
import { execFile as c } from "child_process";
|
|
2
|
+
import { promisify as d } from "util";
|
|
3
|
+
import { f as t } from "./resolve-binary-kH1Jwk0z.js";
|
|
4
|
+
const i = d(c);
|
|
5
|
+
async function s(e) {
|
|
6
|
+
const a = t(), r = [];
|
|
29
7
|
r.push("--input", e.input), r.push("--output", e.output), e.format && r.push("--format", e.format), e.pretty && r.push("--pretty"), e.all && r.push("--all"), e.noHeader && r.push("--no-header"), e.append && r.push("--append", e.append), e.modulesDir && r.push("--modules-dir", e.modulesDir), e.mode && r.push("--mode", e.mode), e.endpoint && r.push("--endpoint", e.endpoint), e.secret && r.push("--secret", e.secret), e.config && r.push("--config", e.config);
|
|
30
8
|
try {
|
|
31
|
-
const { stdout:
|
|
32
|
-
return
|
|
33
|
-
} catch (
|
|
34
|
-
throw new Error(`Syncgen failed: ${
|
|
9
|
+
const { stdout: u, stderr: f } = await i(a, r);
|
|
10
|
+
return f && console.error(f), u;
|
|
11
|
+
} catch (u) {
|
|
12
|
+
throw new Error(`Syncgen failed: ${u.message}`, { cause: u });
|
|
35
13
|
}
|
|
36
14
|
}
|
|
37
15
|
export {
|
|
38
|
-
|
|
16
|
+
s as runSyncgen
|
|
39
17
|
};
|
package/package.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spooky-sync/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.1-alpha.2",
|
|
4
4
|
"description": "Generate TypeScript/Dart types from SurrealDB schema files",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/syncgen.cjs",
|
|
7
7
|
"module": "./dist/syncgen.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"bin": {
|
|
10
|
-
"
|
|
10
|
+
"spky": "./dist/cli.js"
|
|
11
11
|
},
|
|
12
12
|
"exports": {
|
|
13
|
+
"./devtools-mcp": "./devtools-mcp/index.js",
|
|
13
14
|
".": {
|
|
14
15
|
"import": "./dist/syncgen.js",
|
|
15
16
|
"require": "./dist/syncgen.cjs",
|
|
@@ -18,14 +19,19 @@
|
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
20
21
|
"dist",
|
|
21
|
-
"
|
|
22
|
+
"devtools-mcp",
|
|
23
|
+
"templates/cookbook",
|
|
24
|
+
"AGENTS.md"
|
|
22
25
|
],
|
|
23
26
|
"scripts": {
|
|
24
27
|
"dev": "vite",
|
|
28
|
+
"dev:build": "cargo build -p sp00ky-cli",
|
|
25
29
|
"build:rust": "cargo build --release",
|
|
26
30
|
"build:vite": "vite build",
|
|
27
31
|
"build:types": "tsc --emitDeclarationOnly --declaration --declarationDir dist",
|
|
28
|
-
"build": "
|
|
32
|
+
"build:devtools-mcp": "vite build --config vite.config.devtools-mcp.ts",
|
|
33
|
+
"build:js": "npm run build:vite && npm run build:types",
|
|
34
|
+
"build": "npm run build:rust && npm run build:devtools-mcp && npm run build:js",
|
|
29
35
|
"preview": "vite preview",
|
|
30
36
|
"test": "vitest",
|
|
31
37
|
"lint": "eslint src",
|
|
@@ -42,20 +48,24 @@
|
|
|
42
48
|
"license": "MIT",
|
|
43
49
|
"repository": {
|
|
44
50
|
"type": "git",
|
|
45
|
-
"url": "https://github.com/mono424/
|
|
51
|
+
"url": "https://github.com/mono424/sp00ky.git",
|
|
46
52
|
"directory": "apps/cli"
|
|
47
53
|
},
|
|
48
54
|
"publishConfig": {
|
|
49
55
|
"access": "public"
|
|
50
56
|
},
|
|
51
|
-
"dependencies": {
|
|
52
|
-
"commander": "^12.0.0"
|
|
53
|
-
},
|
|
54
57
|
"devDependencies": {
|
|
55
58
|
"@types/node": "^20.0.0",
|
|
56
59
|
"typescript": "^5.3.0",
|
|
57
60
|
"vite": "^5.0.0",
|
|
58
61
|
"vite-plugin-dts": "^3.7.0",
|
|
59
62
|
"vitest": "^1.0.0"
|
|
63
|
+
},
|
|
64
|
+
"optionalDependencies": {
|
|
65
|
+
"@spooky-sync/cli-darwin-arm64": "0.0.1-alpha.2",
|
|
66
|
+
"@spooky-sync/cli-darwin-x64": "0.0.1-alpha.2",
|
|
67
|
+
"@spooky-sync/cli-linux-arm64": "0.0.1-alpha.2",
|
|
68
|
+
"@spooky-sync/cli-linux-x64": "0.0.1-alpha.2",
|
|
69
|
+
"@spooky-sync/cli-win32-x64": "0.0.1-alpha.2"
|
|
60
70
|
}
|
|
61
71
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# sp00ky cookbook
|
|
2
|
+
|
|
3
|
+
A short, scannable list of patterns an AI agent (or human) reaches for when writing code against a sp00ky-backed app. Each entry has a one-sentence "when to use," the canonical snippet, and one gotcha.
|
|
4
|
+
|
|
5
|
+
Render any recipe with:
|
|
6
|
+
```
|
|
7
|
+
spky scaffold <recipe> --table <your-table>
|
|
8
|
+
```
|
|
9
|
+
or pass `--out path/to/file.tsx` to write the snippet directly.
|
|
10
|
+
|
|
11
|
+
## Recipes
|
|
12
|
+
|
|
13
|
+
### `live-list`
|
|
14
|
+
**When to use:** you want a reactive, sorted list of rows from a table that updates as records change.
|
|
15
|
+
**Render:** `spky scaffold live-list --table thread`
|
|
16
|
+
**Gotcha:** end the query with `.build()` — `useQuery` will hang on a bare builder.
|
|
17
|
+
|
|
18
|
+
### `optimistic-mutation`
|
|
19
|
+
**When to use:** you're inserting a new record from a UI action (form submit, button click) and want the local cache to update immediately while the mutation drains to the remote.
|
|
20
|
+
**Render:** `spky scaffold optimistic-mutation --table thread`
|
|
21
|
+
**Gotcha:** `db.create` takes a *full* record-ID string (`'thread:abc'`), not `(table, payload)`. Use `Uuid` to mint IDs.
|
|
22
|
+
|
|
23
|
+
### `crdt-text-field`
|
|
24
|
+
**When to use:** a text column is annotated `-- @crdt text` in your schema and you need a collaborative editor wired to it.
|
|
25
|
+
**Render:** `spky scaffold crdt-text-field --table thread --field content`
|
|
26
|
+
**Gotcha:** never read the field via `useQuery`; always `useCrdtField`. Writes must pass `{ debounced: true }` to `db.update` so rapid keystrokes coalesce.
|
|
27
|
+
|
|
28
|
+
## Related
|
|
29
|
+
|
|
30
|
+
- See `AGENTS.md` (in your project root or `node_modules/@spooky-sync/*/AGENTS.md`) for the broader mental model and gotchas.
|
|
31
|
+
- After editing the schema: `spky generate` then `spky doctor` to confirm everything is in sync.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Recipe: crdt-text-field
|
|
2
|
+
// Wire a CRDT text column (`{{table}}.{{field}}` annotated `-- @crdt text` in the schema)
|
|
3
|
+
// to a textarea. Concurrent edits from multiple clients merge via Loro.
|
|
4
|
+
|
|
5
|
+
import { useDb, useQuery, useCrdtField } from '@spooky-sync/client-solid';
|
|
6
|
+
import { schema } from '../schema.gen';
|
|
7
|
+
|
|
8
|
+
export function {{TablePascal}}{{FieldPascal}}Editor(props: { id: string }) {
|
|
9
|
+
const db = useDb<typeof schema>();
|
|
10
|
+
|
|
11
|
+
// Pull the surrounding row so we know the field is loaded.
|
|
12
|
+
const rowResult = useQuery(() =>
|
|
13
|
+
db.query('{{table}}').where({ id: props.id } as any).build()
|
|
14
|
+
);
|
|
15
|
+
const row = () => rowResult.data()?.[0];
|
|
16
|
+
|
|
17
|
+
// Bind the CRDT field. All four args take accessor functions for SolidJS tracking.
|
|
18
|
+
const field = useCrdtField(
|
|
19
|
+
'{{table}}',
|
|
20
|
+
() => row()?.id,
|
|
21
|
+
'{{field}}',
|
|
22
|
+
() => row()?.{{field}}
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const handleInput = async (e: InputEvent) => {
|
|
26
|
+
const next = (e.currentTarget as HTMLTextAreaElement).value;
|
|
27
|
+
const r = row();
|
|
28
|
+
if (!r?.id) return;
|
|
29
|
+
// `debounced` coalesces rapid keystrokes into a single mutation.
|
|
30
|
+
await db.update('{{table}}', r.id, { {{field}}: next }, { debounced: true });
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<textarea value={field.value() ?? ''} onInput={handleInput} />
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Recipe: live-list
|
|
2
|
+
// Reactive, sorted list of `{{table}}` rows. Re-runs automatically as records change.
|
|
3
|
+
|
|
4
|
+
import { For } from 'solid-js';
|
|
5
|
+
import { useQuery, useDb } from '@spooky-sync/client-solid';
|
|
6
|
+
import { schema } from '../schema.gen';
|
|
7
|
+
|
|
8
|
+
export function {{TablePascal}}List() {
|
|
9
|
+
const db = useDb<typeof schema>();
|
|
10
|
+
|
|
11
|
+
const result = useQuery(() =>
|
|
12
|
+
db.query('{{table}}').orderBy('id', 'asc').limit(50).build()
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<ul>
|
|
17
|
+
<For each={result.data() ?? []}>
|
|
18
|
+
{(row) => <li>{row.id}</li>}
|
|
19
|
+
</For>
|
|
20
|
+
</ul>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Recipe: optimistic-mutation
|
|
2
|
+
// Insert a new `{{table}}` row from a UI action. Local cache updates immediately;
|
|
3
|
+
// the mutation drains to the remote in the background.
|
|
4
|
+
|
|
5
|
+
import { createSignal } from 'solid-js';
|
|
6
|
+
import { Uuid, useDb } from '@spooky-sync/client-solid';
|
|
7
|
+
import { schema } from '../schema.gen';
|
|
8
|
+
|
|
9
|
+
export function Create{{TablePascal}}Form() {
|
|
10
|
+
const db = useDb<typeof schema>();
|
|
11
|
+
const [submitting, setSubmitting] = createSignal(false);
|
|
12
|
+
|
|
13
|
+
const handleSubmit = async (e: SubmitEvent) => {
|
|
14
|
+
e.preventDefault();
|
|
15
|
+
setSubmitting(true);
|
|
16
|
+
try {
|
|
17
|
+
const id = new Uuid().toString();
|
|
18
|
+
await db.create(`{{table}}:${id}`, {
|
|
19
|
+
// TODO: fill in the fields your `{{table}}` schema requires.
|
|
20
|
+
});
|
|
21
|
+
} finally {
|
|
22
|
+
setSubmitting(false);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<form onSubmit={handleSubmit}>
|
|
28
|
+
<button type="submit" disabled={submitting()}>
|
|
29
|
+
{submitting() ? 'Creating…' : 'Create {{table}}'}
|
|
30
|
+
</button>
|
|
31
|
+
</form>
|
|
32
|
+
);
|
|
33
|
+
}
|
package/target/release/spooky
DELETED
|
Binary file
|