@lunora/cli 0.0.0 → 1.0.0-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/LICENSE.md +105 -0
- package/README.md +109 -9
- package/__assets__/package-og.svg +14 -0
- package/dist/bin.mjs +11 -0
- package/dist/index.d.mts +852 -0
- package/dist/index.d.ts +852 -0
- package/dist/index.mjs +19 -0
- package/dist/packem_chunks/handler.mjs +76 -0
- package/dist/packem_chunks/handler10.mjs +22 -0
- package/dist/packem_chunks/handler11.mjs +192 -0
- package/dist/packem_chunks/handler12.mjs +131 -0
- package/dist/packem_chunks/handler13.mjs +65 -0
- package/dist/packem_chunks/handler14.mjs +58 -0
- package/dist/packem_chunks/handler15.mjs +79 -0
- package/dist/packem_chunks/handler16.mjs +41 -0
- package/dist/packem_chunks/handler17.mjs +105 -0
- package/dist/packem_chunks/handler18.mjs +172 -0
- package/dist/packem_chunks/handler19.mjs +89 -0
- package/dist/packem_chunks/handler2.mjs +114 -0
- package/dist/packem_chunks/handler20.mjs +94 -0
- package/dist/packem_chunks/handler21.mjs +311 -0
- package/dist/packem_chunks/handler3.mjs +204 -0
- package/dist/packem_chunks/handler4.mjs +33 -0
- package/dist/packem_chunks/handler5.mjs +49 -0
- package/dist/packem_chunks/handler6.mjs +91 -0
- package/dist/packem_chunks/handler7.mjs +42 -0
- package/dist/packem_chunks/handler8.mjs +174 -0
- package/dist/packem_chunks/handler9.mjs +16 -0
- package/dist/packem_chunks/planDevCommand.mjs +543 -0
- package/dist/packem_chunks/runCodegenCommand.mjs +52 -0
- package/dist/packem_chunks/runDeployCommand.mjs +504 -0
- package/dist/packem_chunks/runInitCommand.mjs +652 -0
- package/dist/packem_chunks/runMigrateGenerateCommand.mjs +397 -0
- package/dist/packem_chunks/runResetCommand.mjs +41 -0
- package/dist/packem_chunks/runRpcCommand.mjs +68 -0
- package/dist/packem_shared/COMMANDS-1V_KEx35.mjs +905 -0
- package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs +244 -0
- package/dist/packem_shared/admin-url-4UzT-CI4.mjs +19 -0
- package/dist/packem_shared/api-spec-CtA6ilu4.mjs +13 -0
- package/dist/packem_shared/buildRegistryIndex-BcYe607_.mjs +38 -0
- package/dist/packem_shared/command-BDXcJCCJ.mjs +14 -0
- package/dist/packem_shared/createLogger-CHPNjFw2.mjs +73 -0
- package/dist/packem_shared/defaultSpawner-DxI3mebw.mjs +43 -0
- package/dist/packem_shared/diffSnapshots-RR2ZE8Ya.mjs +161 -0
- package/dist/packem_shared/docker-hMQ97KSQ.mjs +21 -0
- package/dist/packem_shared/features-ocSSpZtS.mjs +24 -0
- package/dist/packem_shared/insertSchemaExtension-BuzF6-t2.mjs +59 -0
- package/dist/packem_shared/open-url-Dfq6fAyT.mjs +41 -0
- package/dist/packem_shared/output-format-7gyGR3h8.mjs +17 -0
- package/dist/packem_shared/parseArgs-YXFuKdEk.mjs +56 -0
- package/dist/packem_shared/parseManifest--vZf2FY1.mjs +94 -0
- package/dist/packem_shared/resolve-target-qbsJ_5sF.mjs +16 -0
- package/dist/packem_shared/runAddCommand-BZGkRnBs.mjs +693 -0
- package/dist/packem_shared/schema-drift-gate-BtBt0as0.mjs +79 -0
- package/dist/packem_shared/schemaIrToSnapshot-aBTo7TM5.mjs +43 -0
- package/dist/packem_shared/wrangler-name-cy4yhm9j.mjs +12 -0
- package/package.json +61 -18
- package/skills/README.md +29 -0
- package/skills/lunora/SKILL.md +83 -0
- package/skills/lunora-create-package/SKILL.md +129 -0
- package/skills/lunora-deploy/SKILL.md +150 -0
- package/skills/lunora-functions/SKILL.md +182 -0
- package/skills/lunora-migration-helper/SKILL.md +194 -0
- package/skills/lunora-performance-audit/SKILL.md +143 -0
- package/skills/lunora-quickstart/SKILL.md +240 -0
- package/skills/lunora-realtime/SKILL.md +177 -0
- package/skills/lunora-setup-auth/SKILL.md +170 -0
- package/skills/lunora-setup-hyperdrive/SKILL.md +154 -0
- package/skills/lunora-setup-hyperdrive-global/SKILL.md +171 -0
- package/skills/lunora-setup-mail/SKILL.md +151 -0
- package/skills/lunora-setup-scheduler/SKILL.md +157 -0
- package/skills/lunora-setup-storage/SKILL.md +154 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { evaluateSchemaDrift, parseSchemaSnapshot, SchemaSnapshotParseError, serializeSchemaSnapshot } from '@lunora/codegen';
|
|
3
|
+
|
|
4
|
+
const readBaseline = (snapshotPath) => {
|
|
5
|
+
if (!existsSync(snapshotPath)) {
|
|
6
|
+
return { status: "absent" };
|
|
7
|
+
}
|
|
8
|
+
let content;
|
|
9
|
+
try {
|
|
10
|
+
content = readFileSync(snapshotPath, "utf8");
|
|
11
|
+
} catch {
|
|
12
|
+
return { status: "corrupt" };
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const snapshot = parseSchemaSnapshot(content);
|
|
16
|
+
return snapshot === void 0 ? { status: "corrupt" } : { snapshot, status: "ok" };
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error instanceof SchemaSnapshotParseError) {
|
|
19
|
+
return { status: "corrupt" };
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const writeBaseline = (snapshotPath, snapshot) => {
|
|
25
|
+
writeFileSync(snapshotPath, serializeSchemaSnapshot(snapshot), "utf8");
|
|
26
|
+
};
|
|
27
|
+
const corruptBaselineResult = (context) => {
|
|
28
|
+
const reason = `schema-drift gate: the committed baseline ${context.snapshotPath} is unreadable or malformed, so schema drift cannot be checked. Fix it (e.g. resolve a merge conflict in lunora/.lunora-schema.json), or pass --update-schema-baseline to regenerate it from the current schema.`;
|
|
29
|
+
if (context.updateBaseline && !context.readOnly) {
|
|
30
|
+
context.logger.warn(
|
|
31
|
+
`schema baseline was unreadable; regenerating from the current schema on success (--update-schema-baseline): ${context.snapshotPath}`
|
|
32
|
+
);
|
|
33
|
+
return { blocked: false, changes: [], reason, rebless: context.rebless };
|
|
34
|
+
}
|
|
35
|
+
if (!context.readOnly) {
|
|
36
|
+
context.logger.error(reason);
|
|
37
|
+
}
|
|
38
|
+
return { blocked: true, changes: [], reason };
|
|
39
|
+
};
|
|
40
|
+
const blockedDecisionResult = (decision, context) => {
|
|
41
|
+
if (!context.readOnly) {
|
|
42
|
+
context.logger.error(decision.reason);
|
|
43
|
+
}
|
|
44
|
+
if (context.updateBaseline && !context.readOnly) {
|
|
45
|
+
context.logger.warn(`schema baseline will be re-blessed despite breaking drift on success (--update-schema-baseline): ${context.snapshotPath}`);
|
|
46
|
+
return { blocked: false, changes: decision.changes, reason: decision.reason, rebless: context.rebless };
|
|
47
|
+
}
|
|
48
|
+
return { blocked: true, changes: decision.changes, reason: decision.reason };
|
|
49
|
+
};
|
|
50
|
+
const runSchemaDriftGate = (options) => {
|
|
51
|
+
const { allowDrift, codegen, logger, readOnly = false, updateBaseline = false } = options;
|
|
52
|
+
const snapshotPath = codegen.schemaSnapshotPath;
|
|
53
|
+
const baseline = readBaseline(snapshotPath);
|
|
54
|
+
const context = {
|
|
55
|
+
logger,
|
|
56
|
+
readOnly,
|
|
57
|
+
rebless: () => {
|
|
58
|
+
writeBaseline(snapshotPath, codegen.schemaSnapshot);
|
|
59
|
+
},
|
|
60
|
+
snapshotPath,
|
|
61
|
+
updateBaseline
|
|
62
|
+
};
|
|
63
|
+
if (baseline.status === "corrupt") {
|
|
64
|
+
return corruptBaselineResult(context);
|
|
65
|
+
}
|
|
66
|
+
const decision = evaluateSchemaDrift({ allowDrift, baseline: baseline.status === "ok" ? baseline.snapshot : void 0, current: codegen.schemaSnapshot });
|
|
67
|
+
if (decision.blocked) {
|
|
68
|
+
return blockedDecisionResult(decision, context);
|
|
69
|
+
}
|
|
70
|
+
if (decision.changes.length > 0) {
|
|
71
|
+
if (!readOnly) {
|
|
72
|
+
logger.info(decision.reason);
|
|
73
|
+
}
|
|
74
|
+
return { blocked: false, changes: decision.changes, reason: decision.reason, rebless: readOnly ? void 0 : context.rebless };
|
|
75
|
+
}
|
|
76
|
+
return { blocked: false, changes: decision.changes, reason: decision.reason };
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export { runSchemaDriftGate as r };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { validatorKindToSqlType } from './diffSnapshots-RR2ZE8Ya.mjs';
|
|
2
|
+
|
|
3
|
+
const validatorToColumn = (validator) => {
|
|
4
|
+
if (validator.kind === "optional" && validator.inner) {
|
|
5
|
+
return {
|
|
6
|
+
nullable: true,
|
|
7
|
+
sqlType: validatorKindToSqlType(validator.inner.kind)
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
nullable: false,
|
|
12
|
+
sqlType: validatorKindToSqlType(validator.kind)
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
const isGlobal = (mode) => mode === "global";
|
|
16
|
+
const schemaIrToSnapshot = (ir) => {
|
|
17
|
+
const tables = {};
|
|
18
|
+
for (const table of ir.tables) {
|
|
19
|
+
if (!isGlobal(table.shardMode)) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const columns = {};
|
|
23
|
+
for (const [columnName, validator] of Object.entries(table.shape)) {
|
|
24
|
+
columns[columnName] = validatorToColumn(validator);
|
|
25
|
+
}
|
|
26
|
+
const indexes = {};
|
|
27
|
+
for (const index of table.indexes) {
|
|
28
|
+
indexes[index.name] = {
|
|
29
|
+
fields: [...index.fields],
|
|
30
|
+
name: index.name,
|
|
31
|
+
unique: index.unique ?? false
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
tables[table.name] = {
|
|
35
|
+
columns,
|
|
36
|
+
indexes,
|
|
37
|
+
name: table.name
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return { tables, version: 1 };
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export { schemaIrToSnapshot as default };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { findWranglerFile, readWranglerJsonc } from '@lunora/config';
|
|
2
|
+
|
|
3
|
+
const readWranglerName = (cwd) => {
|
|
4
|
+
const wranglerPath = findWranglerFile(cwd);
|
|
5
|
+
if (!wranglerPath) {
|
|
6
|
+
return void 0;
|
|
7
|
+
}
|
|
8
|
+
const { parsed } = readWranglerJsonc(wranglerPath);
|
|
9
|
+
return typeof parsed?.name === "string" && parsed.name.length > 0 ? parsed.name : void 0;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { readWranglerName as r };
|
package/package.json
CHANGED
|
@@ -1,33 +1,76 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "1.0.0-alpha.2",
|
|
4
4
|
"description": "The Lunora CLI: init, dev, deploy, codegen, run, reset, and migrate commands",
|
|
5
|
-
"
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent-skills",
|
|
7
|
+
"cli",
|
|
8
|
+
"cloudflare",
|
|
9
|
+
"codegen",
|
|
10
|
+
"deploy",
|
|
11
|
+
"durable-objects",
|
|
12
|
+
"lunora",
|
|
13
|
+
"tanstack-intent",
|
|
14
|
+
"vite",
|
|
15
|
+
"workers"
|
|
16
|
+
],
|
|
6
17
|
"homepage": "https://lunora.sh",
|
|
18
|
+
"bugs": "https://github.com/anolilab/lunora/issues",
|
|
19
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
20
|
+
"author": {
|
|
21
|
+
"name": "Daniel Bannert",
|
|
22
|
+
"email": "d.bannert@anolilab.de"
|
|
23
|
+
},
|
|
7
24
|
"repository": {
|
|
8
25
|
"type": "git",
|
|
9
26
|
"url": "git+https://github.com/anolilab/lunora.git",
|
|
10
27
|
"directory": "packages/cli"
|
|
11
28
|
},
|
|
12
|
-
"
|
|
13
|
-
"
|
|
29
|
+
"bin": {
|
|
30
|
+
"lunora": "./dist/bin.mjs"
|
|
14
31
|
},
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"codegen",
|
|
22
|
-
"deploy",
|
|
23
|
-
"vite",
|
|
24
|
-
"tanstack-intent",
|
|
25
|
-
"agent-skills"
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"__assets__",
|
|
35
|
+
"skills",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE.md"
|
|
26
38
|
],
|
|
39
|
+
"type": "module",
|
|
40
|
+
"sideEffects": false,
|
|
41
|
+
"module": "./dist/index.mjs",
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
43
|
+
"exports": {
|
|
44
|
+
".": {
|
|
45
|
+
"types": "./dist/index.d.ts",
|
|
46
|
+
"import": "./dist/index.mjs"
|
|
47
|
+
},
|
|
48
|
+
"./package.json": "./package.json"
|
|
49
|
+
},
|
|
27
50
|
"publishConfig": {
|
|
28
51
|
"access": "public"
|
|
29
52
|
},
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@bomb.sh/tab": "0.0.17",
|
|
55
|
+
"@lunora/codegen": "1.0.0-alpha.2",
|
|
56
|
+
"@lunora/config": "1.0.0-alpha.2",
|
|
57
|
+
"@lunora/container": "1.0.0-alpha.1",
|
|
58
|
+
"@lunora/d1": "1.0.0-alpha.2",
|
|
59
|
+
"@lunora/seed": "1.0.0-alpha.1",
|
|
60
|
+
"@lunora/server": "1.0.0-alpha.1",
|
|
61
|
+
"@lunora/studio": "1.0.0-alpha.1",
|
|
62
|
+
"@lunora/values": "1.0.0-alpha.1",
|
|
63
|
+
"@visulima/cerebro": "3.0.0-alpha.32",
|
|
64
|
+
"@visulima/fs": "5.0.0-alpha.32",
|
|
65
|
+
"@visulima/pail": "4.0.0-alpha.22",
|
|
66
|
+
"@visulima/path": "3.0.0-alpha.13",
|
|
67
|
+
"@visulima/spinner": "1.0.0-alpha.4",
|
|
68
|
+
"giget": "3.3.0",
|
|
69
|
+
"jsonc-parser": "^3.3.1",
|
|
70
|
+
"magic-string": "^0.30.21",
|
|
71
|
+
"ts-morph": "^28.0.0"
|
|
72
|
+
},
|
|
73
|
+
"engines": {
|
|
74
|
+
"node": "^22.15.0 || >=24.11.0"
|
|
75
|
+
}
|
|
33
76
|
}
|
package/skills/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Lunora Agent Skills
|
|
2
|
+
|
|
3
|
+
First-party [Agent Skills](https://tanstack.com/intent/latest/docs/registry) for
|
|
4
|
+
Lunora — portable instructions that teach AI coding agents how to use the
|
|
5
|
+
framework correctly. They ship inside `@lunora/cli` and are discovered by the
|
|
6
|
+
[TanStack Intent registry](https://tanstack.com/intent/registry) via the
|
|
7
|
+
`tanstack-intent` package keyword.
|
|
8
|
+
|
|
9
|
+
Each skill is a `SKILL.md` with YAML frontmatter (`name`, `description`) in its
|
|
10
|
+
own directory:
|
|
11
|
+
|
|
12
|
+
| Skill | Use for |
|
|
13
|
+
| -------------------------- | --------------------------------------------------------------------------- |
|
|
14
|
+
| `lunora` | Router — start here, then switch to the matching skill below. |
|
|
15
|
+
| `lunora-quickstart` | `lunora init` / adding Lunora to an app + first round-trip. |
|
|
16
|
+
| `lunora-functions` | Core authoring rules — schema, validators, query/mutation/action, `ctx.db`. |
|
|
17
|
+
| `lunora-realtime` | Client reactivity — live hooks, optimistic updates, `@lunora/db`. |
|
|
18
|
+
| `lunora-setup-auth` | Authentication via `lunora registry add auth` (+ providers). |
|
|
19
|
+
| `lunora-setup-mail` | Transactional email via `lunora registry add mail` — `sendEmail` actions. |
|
|
20
|
+
| `lunora-setup-storage` | R2 file storage via `lunora registry add storage` — signed upload/download. |
|
|
21
|
+
| `lunora-setup-scheduler` | Deferred (`ctx.scheduler`) + cron jobs (`lunora registry add crons`). |
|
|
22
|
+
| `lunora-create-package` | Building a reusable registry item or `@lunora/*` package. |
|
|
23
|
+
| `lunora-migration-helper` | Schema/data migrations, `.global()` D1 flow, the drift gate. |
|
|
24
|
+
| `lunora-deploy` | Deploying to Cloudflare — wrangler bindings, secrets, the gate. |
|
|
25
|
+
| `lunora-performance-audit` | Scans, indexes, OCC write conflicts, sharding/`.global()`. |
|
|
26
|
+
|
|
27
|
+
These are mirrored into `.agents/skills/` and `.claude/skills/` (via symlinks)
|
|
28
|
+
so agents working inside this repo pick them up directly. The source of truth
|
|
29
|
+
lives here so the published `@lunora/cli` tarball carries them.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lunora
|
|
3
|
+
description: Routes general Lunora requests to the right project skill. Use when the user
|
|
4
|
+
asks which Lunora skill to use, or gives an underspecified task for a Lunora
|
|
5
|
+
app (a type-safe, real-time backend on Cloudflare Workers + Durable Objects
|
|
6
|
+
with a Vite-first DX).
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Lunora
|
|
10
|
+
|
|
11
|
+
Use this as the routing skill for Lunora work in this repo.
|
|
12
|
+
|
|
13
|
+
Lunora exposes a Convex-style functional API (`defineSchema`, `query`,
|
|
14
|
+
`mutation`, `action`) on top of Cloudflare Workers and Durable Objects. State
|
|
15
|
+
lives in a per-app `ShardDO` (SQLite, OCC, hibernated WebSocket subscriptions)
|
|
16
|
+
by default; `.shardBy(key)` partitions it across many DOs and `.global()`
|
|
17
|
+
replicates a table to D1 for low-latency cross-region reads. A Vite plugin
|
|
18
|
+
drives codegen and end-to-end type sync.
|
|
19
|
+
|
|
20
|
+
If a more specific Lunora skill clearly matches the request, use that instead.
|
|
21
|
+
|
|
22
|
+
## Start Here
|
|
23
|
+
|
|
24
|
+
Before writing or changing any `lunora/` code, make sure the generated types are
|
|
25
|
+
current — they are the contract the client and server share.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
lunora codegen
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This regenerates `lunora/_generated/` (`api.ts`, `server.ts`, `dataModel.ts`,
|
|
32
|
+
`shard.ts`, `openapi.ts`, …) from `lunora/schema.ts` and your function files. The
|
|
33
|
+
output typechecks your schema and functions, so it doubles as the agent's main
|
|
34
|
+
feedback loop after each edit. Commit `lunora/_generated/` — it is part of the
|
|
35
|
+
source tree, not a build artifact to gitignore.
|
|
36
|
+
|
|
37
|
+
If a project-level `AGENTS.md` / `CLAUDE.md` exists, read it first — it overrides
|
|
38
|
+
these defaults.
|
|
39
|
+
|
|
40
|
+
## Route to the Right Skill
|
|
41
|
+
|
|
42
|
+
After codegen is green, use the most specific Lunora skill for the task:
|
|
43
|
+
|
|
44
|
+
- New project, or adding Lunora to an existing app: `lunora-quickstart`
|
|
45
|
+
- Writing or reviewing schema + functions (the core authoring rules):
|
|
46
|
+
`lunora-functions`
|
|
47
|
+
- Wiring live data into a client (hooks, optimistic updates): `lunora-realtime`
|
|
48
|
+
- Authentication setup (email/password, OAuth, magic link, OTP):
|
|
49
|
+
`lunora-setup-auth`
|
|
50
|
+
- Wiring a prebuilt capability (mail, file storage, scheduled jobs, rate
|
|
51
|
+
limiting, vectors, AI, containers, payments, MCP): install it with
|
|
52
|
+
`lunora registry add <item>` (see `lunora registry list`). Capabilities with a
|
|
53
|
+
dedicated skill: `lunora-setup-mail` (mail), `lunora-setup-storage` (R2 file
|
|
54
|
+
storage), `lunora-setup-scheduler` (deferred `ctx.scheduler` + cron jobs). For
|
|
55
|
+
the rest, read the item's README after installing.
|
|
56
|
+
- Building a reusable capability — a registry item or an `@lunora/*` package:
|
|
57
|
+
`lunora-create-package`
|
|
58
|
+
- Planning or running a schema/data migration: `lunora-migration-helper`
|
|
59
|
+
- Deploying to Cloudflare (wrangler, bindings, secrets, the drift gate):
|
|
60
|
+
`lunora-deploy`
|
|
61
|
+
- Investigating performance, scan, or write-conflict issues:
|
|
62
|
+
`lunora-performance-audit`
|
|
63
|
+
|
|
64
|
+
If one of those clearly matches the user's goal, switch to it instead of staying
|
|
65
|
+
in this skill.
|
|
66
|
+
|
|
67
|
+
## Core Mental Model
|
|
68
|
+
|
|
69
|
+
- **Functions** live in `lunora/*.ts` and are one of `query` (reactive read),
|
|
70
|
+
`mutation` (transactional write), or `action` (side effects / `fetch` / no
|
|
71
|
+
direct db). `internalQuery` / `internalMutation` / `internalAction` are the
|
|
72
|
+
non-public variants.
|
|
73
|
+
- **Schema** lives in `lunora/schema.ts` via `defineSchema` + `defineTable`,
|
|
74
|
+
with validators from `v.*` (re-exported by `@lunora/server`).
|
|
75
|
+
- **Reads go through indexes.** Prefer `ctx.db.query("t").withIndex(...)` over
|
|
76
|
+
`.filter(...)`; declare the index with `.index("by_x", ["x"])`.
|
|
77
|
+
- **Clients** subscribe over WebSocket. `useQuery`/`useMutation` (React, Vue,
|
|
78
|
+
Solid, Svelte) re-render the moment a mutation changes the queried rows.
|
|
79
|
+
|
|
80
|
+
## When Not to Use
|
|
81
|
+
|
|
82
|
+
- The user has already named a more specific Lunora workflow.
|
|
83
|
+
- Another Lunora skill obviously fits the request better.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lunora-create-package
|
|
3
|
+
description: Builds a reusable Lunora capability — either a registry item installed with
|
|
4
|
+
`lunora registry add`, or a publishable `@lunora/*` workspace package. Use for
|
|
5
|
+
packaging schema + functions + bindings others can drop into their app.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Lunora Create Package
|
|
9
|
+
|
|
10
|
+
Package a reusable Lunora capability. There are two distribution shapes; pick
|
|
11
|
+
based on whether the capability is **copied into the user's `lunora/`** or
|
|
12
|
+
**imported as a dependency**.
|
|
13
|
+
|
|
14
|
+
| Shape | Distribution | Use for |
|
|
15
|
+
| ----------------- | -------------------------------- | -------------------------------------------------------- |
|
|
16
|
+
| **Registry item** | `lunora registry add <name>` | App-owned code (schema/functions) the user edits + wires |
|
|
17
|
+
| **Workspace pkg** | `import … from "@lunora/<name>"` | Reusable library code imported as a dependency |
|
|
18
|
+
|
|
19
|
+
Many capabilities use **both**: a thin `@lunora/<name>` package holding the
|
|
20
|
+
reusable runtime, plus a registry item that scaffolds the glue (`lunora/<name>/`
|
|
21
|
+
files, bindings, env vars) into the user's project. `auth`, `mail`, `ratelimit`,
|
|
22
|
+
and `storage` all follow this pattern.
|
|
23
|
+
|
|
24
|
+
## When to Use
|
|
25
|
+
|
|
26
|
+
- Extracting schema + functions you have written into something reusable.
|
|
27
|
+
- Authoring a new capability (presence, search, payments, …) for other apps.
|
|
28
|
+
- Adding a new item to this repo's `registry/`.
|
|
29
|
+
|
|
30
|
+
## When Not to Use
|
|
31
|
+
|
|
32
|
+
- A one-off feature for a single app — just write it in `lunora/`.
|
|
33
|
+
- The capability already exists as a registry item or `@lunora/*` package — use
|
|
34
|
+
it (`lunora registry list` to browse).
|
|
35
|
+
|
|
36
|
+
## Path A: Registry Item
|
|
37
|
+
|
|
38
|
+
A registry item is a directory under `registry/<name>/` with three files:
|
|
39
|
+
|
|
40
|
+
- `registry.json` — the manifest (deps, bindings, env vars, files, requires).
|
|
41
|
+
- `index.ts` (and any siblings) — the code copied into the user's project.
|
|
42
|
+
- `README.md` — install + configuration docs.
|
|
43
|
+
|
|
44
|
+
### Manifest shape
|
|
45
|
+
|
|
46
|
+
```jsonc
|
|
47
|
+
// registry/<name>/registry.json
|
|
48
|
+
{
|
|
49
|
+
"$schema": "../schema/registry-item.schema.json",
|
|
50
|
+
"name": "<name>",
|
|
51
|
+
"title": "Human Title",
|
|
52
|
+
"description": "One-paragraph summary shown in `lunora registry list`.",
|
|
53
|
+
"docs": "Post-install steps surfaced to the user after `lunora registry add`.",
|
|
54
|
+
"requires": [], // other item names this one depends on
|
|
55
|
+
"deps": { "@lunora/server": "workspace:*" },
|
|
56
|
+
"bindings": [
|
|
57
|
+
// reconciled into wrangler.jsonc
|
|
58
|
+
{ "path": ["d1_databases"], "value": [{ "binding": "DB", "database_name": "REPLACE_ME-db", "database_id": "<replace-with-d1-create-id>" }] },
|
|
59
|
+
],
|
|
60
|
+
"envVars": [{ "name": "MY_SECRET", "description": "What it is and how to generate it.", "secret": true }],
|
|
61
|
+
"files": [{ "from": "index.ts", "to": "lunora/<name>/index.ts", "merge": "create-or-skip" }],
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- `files[].merge` is typically `create-or-skip` (never clobber edited user code);
|
|
66
|
+
`bindings` are reconciled into `wrangler.jsonc`; `envVars` are scaffolded into
|
|
67
|
+
`.dev.vars` (secret-looking ones get generated values).
|
|
68
|
+
- `requires` lets a provider item (e.g. `auth-clerk`) build on a base item
|
|
69
|
+
(`auth`). The resolver installs the chain.
|
|
70
|
+
|
|
71
|
+
### Register and validate
|
|
72
|
+
|
|
73
|
+
Add an entry to `registry/index.json`, then rebuild and check the index:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
lunora registry build # regenerate registry/index.json
|
|
77
|
+
lunora registry build --check # verify the index is up to date (CI)
|
|
78
|
+
lunora registry view <name> # preview what `add` would do
|
|
79
|
+
lunora registry add <name> # install into the current project
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Path B: Workspace Package
|
|
83
|
+
|
|
84
|
+
Scaffold a fresh `@lunora/<name>` package with the generator (always use the
|
|
85
|
+
`--name=value` form):
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
vis generate lunora-package --name=search --description='Typed full-text search over Lunora tables'
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This creates `packages/search/` following the repo's package shape: `src/index.ts`,
|
|
92
|
+
`__tests__/`, `vitest.config.ts`, `tsconfig.json` (extends `../../tsconfig.base.json`),
|
|
93
|
+
`project.json` (vis tags `type:package` + `category:<slug>`), `package.json` (ESM,
|
|
94
|
+
`"sideEffects": false`, conditional exports), and `.releaserc.json`.
|
|
95
|
+
|
|
96
|
+
### Repo conventions to honor
|
|
97
|
+
|
|
98
|
+
- **No `.js` extensions** in relative imports (`moduleResolution: "bundler"`).
|
|
99
|
+
The lone exception is `@lunora/codegen`'s emitted output.
|
|
100
|
+
- **No mixed default + named exports** in one file — named-only when there is
|
|
101
|
+
more than one export.
|
|
102
|
+
- **Use the dependency catalogs** in `pnpm-workspace.yaml` (`catalog:test`,
|
|
103
|
+
`catalog:lint`, …) — never hard-code a version that lives in a catalog.
|
|
104
|
+
- Tag `project.json` with `type:package` and a `category:<slug>`.
|
|
105
|
+
|
|
106
|
+
Build and test the new package in isolation:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pnpm --filter "@lunora/search" run lint:types
|
|
110
|
+
pnpm --filter "@lunora/search" run test
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Codegen-Wired Capabilities
|
|
114
|
+
|
|
115
|
+
If your capability surfaces functions on a context (e.g. `ctx.ai`, `ctx.containers`)
|
|
116
|
+
or new generated tables, it must be discoverable by `@lunora/codegen` — codegen
|
|
117
|
+
parses `lunora/schema.ts` and the function files. Document any `lunora/*.ts`
|
|
118
|
+
declaration the user must add so codegen wires the typed surface.
|
|
119
|
+
|
|
120
|
+
## Checklist
|
|
121
|
+
|
|
122
|
+
- [ ] Chose the right shape (registry item, workspace package, or both).
|
|
123
|
+
- [ ] Registry item: `registry.json` + `index.ts` + `README.md` authored;
|
|
124
|
+
`bindings`/`envVars`/`requires`/`files` correct.
|
|
125
|
+
- [ ] `lunora registry build` run; `lunora registry build --check` passes.
|
|
126
|
+
- [ ] Workspace package: scaffolded via `vis generate lunora-package`; no `.js`
|
|
127
|
+
extensions, no mixed default+named exports, catalog versions used.
|
|
128
|
+
- [ ] `lint:types` and `test` pass for the new package.
|
|
129
|
+
- [ ] README documents install, bindings, env vars, and any `lunora/` glue.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lunora-deploy
|
|
3
|
+
description: Deploys a Lunora app to Cloudflare. Use for `lunora deploy`, wrangler.jsonc
|
|
4
|
+
bindings (SHARD/SESSION DOs, D1, R2), provisioning databases/buckets, secrets
|
|
5
|
+
(`wrangler secret` vs `.dev.vars`), the `lunora doctor` preflight, the
|
|
6
|
+
schema-drift gate, and dev-vs-prod separation.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Lunora Deploy
|
|
10
|
+
|
|
11
|
+
Ship a Lunora app to Cloudflare Workers + Durable Objects. Unlike a managed
|
|
12
|
+
backend, deployment owns real Cloudflare resources — Durable Object bindings, a
|
|
13
|
+
D1 database, R2 buckets, and secrets — so the work is mostly making
|
|
14
|
+
`wrangler.jsonc` and the remote resources line up.
|
|
15
|
+
|
|
16
|
+
## When to Use
|
|
17
|
+
|
|
18
|
+
- Deploying to production (or a Cloudflare environment) for the first time.
|
|
19
|
+
- A deploy fails on a binding, a placeholder id, or the schema-drift gate.
|
|
20
|
+
- Provisioning D1 / R2 / secrets for a Lunora app.
|
|
21
|
+
|
|
22
|
+
## When Not to Use
|
|
23
|
+
|
|
24
|
+
- Local development — that is `lunora dev` (see `lunora-quickstart`).
|
|
25
|
+
- A schema/data change that needs migrating — do that first with
|
|
26
|
+
`lunora-migration-helper`, then deploy.
|
|
27
|
+
|
|
28
|
+
## What `lunora deploy` Does
|
|
29
|
+
|
|
30
|
+
`lunora deploy` runs a fixed pipeline:
|
|
31
|
+
|
|
32
|
+
1. **Codegen** — regenerates `lunora/_generated/` and typechecks.
|
|
33
|
+
2. **Validate `wrangler.jsonc`** — required `compatibility_date`, the
|
|
34
|
+
`nodejs_compat` flag, and the `SHARD` Durable Object binding.
|
|
35
|
+
3. **Schema-drift gate** — blocks if the committed baseline
|
|
36
|
+
(`lunora/.lunora-schema.json`) drifted with a breaking change and no
|
|
37
|
+
accompanying migration. The baseline is re-blessed only after the deploy
|
|
38
|
+
succeeds.
|
|
39
|
+
4. **`wrangler deploy`** — builds and pushes the worker (and any container
|
|
40
|
+
images).
|
|
41
|
+
|
|
42
|
+
Useful flags: `--env <name>` (Cloudflare environment), `--migrate` (run pending
|
|
43
|
+
data migrations against the live worker after deploy, with `--migrate-token` /
|
|
44
|
+
`--migrate-url`), `--allow-schema-drift` (override the gate — use sparingly), and
|
|
45
|
+
`--update-schema-baseline` (re-bless the baseline with the current shape).
|
|
46
|
+
|
|
47
|
+
## Preflight: `lunora doctor`
|
|
48
|
+
|
|
49
|
+
Run the read-only preflight before deploying. It checks:
|
|
50
|
+
|
|
51
|
+
- `wrangler.jsonc` present with the `SHARD` durable-object binding.
|
|
52
|
+
- D1 `database_id`s are real, not placeholders (`<replace>` / empty).
|
|
53
|
+
- `send_email` destination addresses aren't placeholders.
|
|
54
|
+
- `.dev.vars` secret-looking keys are filled.
|
|
55
|
+
- Declared containers are exported by the worker entry.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
lunora doctor # FAIL → exit 1; WARN/INFO don't block
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`lunora verify` and `lunora prepare` run related checks (drift gate, wrangler
|
|
62
|
+
validation) — wire `lunora verify` into CI to catch drift before a deploy.
|
|
63
|
+
|
|
64
|
+
## `wrangler.jsonc` — the binding contract
|
|
65
|
+
|
|
66
|
+
A Lunora worker needs the ShardDO (and SessionDO when auth is wired), the
|
|
67
|
+
SQLite-DO migration tag, and whatever D1/R2 the app uses:
|
|
68
|
+
|
|
69
|
+
```jsonc
|
|
70
|
+
{
|
|
71
|
+
"name": "my-app",
|
|
72
|
+
"main": "src/index.ts",
|
|
73
|
+
"compatibility_date": "2026-04-07",
|
|
74
|
+
"compatibility_flags": ["nodejs_compat"],
|
|
75
|
+
"durable_objects": {
|
|
76
|
+
"bindings": [
|
|
77
|
+
{ "name": "SHARD", "class_name": "ShardDO" },
|
|
78
|
+
{ "name": "SESSION", "class_name": "SessionDO" }, // only with @lunora/auth
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["ShardDO", "SessionDO"] }],
|
|
82
|
+
"d1_databases": [{ "binding": "DB", "database_name": "my-app-global", "database_id": "<replace>" }],
|
|
83
|
+
"r2_buckets": [{ "binding": "FILES", "bucket_name": "my-app-files" }],
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`lunora dev` auto-reconciles most of this (and `lunora registry add` adds the
|
|
88
|
+
bindings an item needs), but the **resources themselves** must exist and their
|
|
89
|
+
ids must be filled in:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
wrangler d1 create my-app-global # paste the returned database_id into wrangler.jsonc
|
|
93
|
+
wrangler r2 bucket create my-app-files
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The DO `class_name`s must be exported by your worker entry (the
|
|
97
|
+
`createShardDO()` / generated container exports) — wrangler rejects a binding
|
|
98
|
+
whose class the worker doesn't export. `lunora doctor` surfaces a missing
|
|
99
|
+
container export proactively.
|
|
100
|
+
|
|
101
|
+
## Secrets: `wrangler secret`, not `.dev.vars`
|
|
102
|
+
|
|
103
|
+
`.dev.vars` is **dev only** — it is git-ignored and never deployed. Production
|
|
104
|
+
secrets (`BETTER_AUTH_SECRET`, `RESEND_API_KEY`, provider client secrets, …) go
|
|
105
|
+
into Cloudflare:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
wrangler secret put BETTER_AUTH_SECRET # prompts for the value, stored encrypted
|
|
109
|
+
wrangler secret list
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Set every secret your app reads (mirror the secret-looking keys in `.dev.vars`)
|
|
113
|
+
before the first request hits production.
|
|
114
|
+
|
|
115
|
+
## Dev vs Production
|
|
116
|
+
|
|
117
|
+
- **Development:** `lunora dev` (Vite + workerd + Studio + codegen-on-save). The
|
|
118
|
+
schema baseline and `.dev.vars` belong to dev.
|
|
119
|
+
- **Production:** `lunora deploy`. Separate D1 database / R2 buckets / secrets
|
|
120
|
+
from dev. Never point a dev worker at prod resources.
|
|
121
|
+
|
|
122
|
+
For `.global()` table DDL, generate and commit SQL migrations with `lunora
|
|
123
|
+
migrate generate` before deploying; `@lunora/d1`'s runner applies them. For data
|
|
124
|
+
backfills, deploy first, then `lunora deploy --migrate` (or `lunora migrate up
|
|
125
|
+
--prod`). See `lunora-migration-helper`.
|
|
126
|
+
|
|
127
|
+
## Common Pitfalls
|
|
128
|
+
|
|
129
|
+
1. **Placeholder `database_id`.** The D1 binding ships with `<replace>`; run
|
|
130
|
+
`wrangler d1 create` and paste the id. `lunora doctor` catches this.
|
|
131
|
+
2. **DO class not exported.** `wrangler deploy` fails if a `class_name` isn't
|
|
132
|
+
exported by the worker entry — export `ShardDO`/`SessionDO`/generated
|
|
133
|
+
containers.
|
|
134
|
+
3. **Secrets only in `.dev.vars`.** They never reach production; use `wrangler
|
|
135
|
+
secret put` for every prod secret.
|
|
136
|
+
4. **Bypassing the drift gate.** `--allow-schema-drift` ships a breaking schema
|
|
137
|
+
with no migration — stage the change (`lunora-migration-helper`) instead.
|
|
138
|
+
5. **Deploying with uncommitted codegen.** Commit `lunora/_generated/` and
|
|
139
|
+
`lunora/.lunora-schema.json` so CI and the gate see the same baseline.
|
|
140
|
+
|
|
141
|
+
## Checklist
|
|
142
|
+
|
|
143
|
+
- [ ] `lunora doctor` passes (no FAIL).
|
|
144
|
+
- [ ] `wrangler.jsonc` has `compatibility_date`, `nodejs_compat`, the `SHARD` DO
|
|
145
|
+
binding, and the SQLite migration tag.
|
|
146
|
+
- [ ] D1 / R2 resources created; real ids pasted into `wrangler.jsonc`.
|
|
147
|
+
- [ ] DO + container `class_name`s exported by the worker entry.
|
|
148
|
+
- [ ] Production secrets set via `wrangler secret put`.
|
|
149
|
+
- [ ] Schema changes migrated; the drift gate is green (no `--allow-schema-drift`).
|
|
150
|
+
- [ ] `lunora deploy` succeeded; `--migrate` run if data backfills were pending.
|