@lunora/cli 1.0.0-alpha.37 → 1.0.0-alpha.371
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 +33 -0
- package/README.md +1 -1
- package/dist/bin.mjs +2 -10
- package/dist/index.d.mts +1007 -372
- package/dist/index.d.ts +1007 -372
- package/dist/index.mjs +1 -19
- package/dist/packem_chunks/dispatch.mjs +1 -0
- package/dist/packem_chunks/handler.mjs +1 -160
- package/dist/packem_chunks/handler10.mjs +2 -16
- package/dist/packem_chunks/handler11.mjs +1 -22
- package/dist/packem_chunks/handler12.mjs +1 -192
- package/dist/packem_chunks/handler13.mjs +1 -131
- package/dist/packem_chunks/handler14.mjs +1 -65
- package/dist/packem_chunks/handler15.mjs +3 -58
- package/dist/packem_chunks/handler16.mjs +1 -80
- package/dist/packem_chunks/handler17.mjs +1 -43
- package/dist/packem_chunks/handler18.mjs +1 -105
- package/dist/packem_chunks/handler19.mjs +7 -170
- package/dist/packem_chunks/handler2.mjs +1 -114
- package/dist/packem_chunks/handler20.mjs +1 -94
- package/dist/packem_chunks/handler21.mjs +1 -94
- package/dist/packem_chunks/handler22.mjs +3 -0
- package/dist/packem_chunks/handler23.mjs +3 -0
- package/dist/packem_chunks/handler24.mjs +1 -0
- package/dist/packem_chunks/handler25.mjs +2 -0
- package/dist/packem_chunks/handler26.mjs +1 -0
- package/dist/packem_chunks/handler27.mjs +99 -0
- package/dist/packem_chunks/handler28.mjs +10 -0
- package/dist/packem_chunks/handler29.mjs +1 -0
- package/dist/packem_chunks/handler3.mjs +1 -204
- package/dist/packem_chunks/handler4.mjs +1 -33
- package/dist/packem_chunks/handler5.mjs +1 -49
- package/dist/packem_chunks/handler6.mjs +3 -91
- package/dist/packem_chunks/handler7.mjs +1 -42
- package/dist/packem_chunks/handler8.mjs +1 -174
- package/dist/packem_chunks/handler9.mjs +1 -315
- package/dist/packem_chunks/runCodegenCommand.mjs +4 -52
- package/dist/packem_chunks/runDeployCommand.mjs +11 -594
- package/dist/packem_chunks/runDevCommand.mjs +2 -0
- package/dist/packem_chunks/runInitCommand.mjs +164 -1432
- package/dist/packem_chunks/runResetCommand.mjs +1 -41
- package/dist/packem_chunks/runRpcCommand.mjs +1 -68
- package/dist/packem_shared/COMMANDS-B3ETRBZq.mjs +1 -0
- package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-CyIsI3S1.mjs +1 -0
- package/dist/packem_shared/EXIT_CODE-D_XCixv8.mjs +1 -0
- package/dist/packem_shared/admin-token-VdUnvnKW.mjs +1 -0
- package/dist/packem_shared/admin-url-DJB8P6Jh.mjs +1 -0
- package/dist/packem_shared/binding-manifest-file-CQ1eQYy1.mjs +2 -0
- package/dist/packem_shared/buildRegistryIndex-Dc8D8AR6.mjs +1 -0
- package/dist/packem_shared/catalog-CahzmDLV.mjs +1 -0
- package/dist/packem_shared/cli-DVVI8I8G.mjs +2 -0
- package/dist/packem_shared/cli-manifest-CRet0egA.mjs +1 -0
- package/dist/packem_shared/cloudflare-credentials-lC5JtPLe.mjs +1 -0
- package/dist/packem_shared/cloudflare-rest--YUlUUlB.mjs +1 -0
- package/dist/packem_shared/codegen-error-AmH54ofi.mjs +3 -0
- package/dist/packem_shared/command-CAXl2A1S.mjs +1 -0
- package/dist/packem_shared/commands-DgUDxkA8.mjs +13 -0
- package/dist/packem_shared/createLogger-Cl18I8AX.mjs +2 -0
- package/dist/packem_shared/createRecordingSpawner-Ch7zuQQE.mjs +1 -0
- package/dist/packem_shared/deploy-target-DCWwiuAe.mjs +1 -0
- package/dist/packem_shared/diffSnapshots-WWwx-KvZ.mjs +5 -0
- package/dist/packem_shared/docker-DpVxvYpL.mjs +1 -0
- package/dist/packem_shared/health-probe-Bw9JFfIy.mjs +1 -0
- package/dist/packem_shared/import-BRM1hs7j.mjs +12 -0
- package/dist/packem_shared/index-oHZBGBwB.mjs +1 -0
- package/dist/packem_shared/insertSchemaExtension-DuiV6cba.mjs +8 -0
- package/dist/packem_shared/lint-ignore-report-DKZagpqk.mjs +2 -0
- package/dist/packem_shared/open-url-EnKy--w-.mjs +1 -0
- package/dist/packem_shared/output-format-B4eQHi5y.mjs +2 -0
- package/dist/packem_shared/parseManifest-CwPTKdtS.mjs +1 -0
- package/dist/packem_shared/path-containment-CgxYZggb.mjs +1 -0
- package/dist/packem_shared/plan-DJeIwzFu.mjs +5 -0
- package/dist/packem_shared/planDevCommand-DkaC-fNF.mjs +1 -0
- package/dist/packem_shared/platform-diagnostics-Cn2g6Jh-.mjs +4 -0
- package/dist/packem_shared/prompt-cancelled-BvsNxg_Q.mjs +1 -0
- package/dist/packem_shared/render-lunora-error-C3OJY5wk.mjs +2 -0
- package/dist/packem_shared/resolve-BrsV4Qyc.mjs +5 -0
- package/dist/packem_shared/resolve-target-C_ZloTvd.mjs +1 -0
- package/dist/packem_shared/runAddCommand-DTvfp4h2.mjs +1 -0
- package/dist/packem_shared/runExportCommand-slAq5BCF.mjs +5 -0
- package/dist/packem_shared/runMigrateGenerateCommand-Ii_WMZEA.mjs +11 -0
- package/dist/packem_shared/schemaIrToSnapshot-BjK-0IRo.mjs +1 -0
- package/dist/packem_shared/secret-key-Bc77jm1S.mjs +1 -0
- package/dist/packem_shared/shared-D-zCOmgY.mjs +1 -0
- package/dist/packem_shared/storage-DWhTX8eS.mjs +1 -0
- package/dist/packem_shared/tui-prompts-B3YwUhGw.mjs +4 -0
- package/dist/packem_shared/variables-acxcvVQa.mjs +1 -0
- package/dist/packem_shared/wrangler-name-Dsk5K1f-.mjs +1 -0
- package/dist/packem_shared/wrangler-secrets-3chIVv3H.mjs +1 -0
- package/package.json +39 -19
- package/skills/README.md +35 -17
- package/skills/lunora/SKILL.md +154 -64
- package/skills/lunora-create-package/SKILL.md +97 -84
- package/skills/lunora-deploy/SKILL.md +175 -105
- package/skills/lunora-functions/SKILL.md +362 -56
- package/skills/lunora-migration-helper/SKILL.md +162 -137
- package/skills/lunora-performance-audit/SKILL.md +137 -95
- package/skills/lunora-quickstart/SKILL.md +137 -136
- package/skills/lunora-realtime/SKILL.md +140 -99
- package/skills/lunora-setup-auth/SKILL.md +162 -102
- package/skills/lunora-setup-hyperdrive/SKILL.md +114 -97
- package/skills/lunora-setup-hyperdrive-global/SKILL.md +93 -90
- package/skills/lunora-setup-mail/SKILL.md +85 -113
- package/skills/lunora-setup-scheduler/SKILL.md +132 -96
- package/skills/lunora-setup-storage/SKILL.md +189 -95
- package/dist/packem_chunks/planDevCommand.mjs +0 -541
- package/dist/packem_chunks/runMigrateGenerateCommand.mjs +0 -397
- package/dist/packem_shared/COMMANDS-B0ftFD_3.mjs +0 -948
- package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs +0 -244
- package/dist/packem_shared/admin-url-4UzT-CI4.mjs +0 -19
- package/dist/packem_shared/api-spec-CtA6ilu4.mjs +0 -13
- package/dist/packem_shared/buildRegistryIndex-BcYe607_.mjs +0 -38
- package/dist/packem_shared/codegen-error-DJG-ghs_.mjs +0 -31
- package/dist/packem_shared/command-D3lB_4Az.mjs +0 -19
- package/dist/packem_shared/commands-B-gR09Z_.mjs +0 -845
- package/dist/packem_shared/createLogger-B40gPzQo.mjs +0 -78
- package/dist/packem_shared/createRecordingSpawner-DxI3mebw.mjs +0 -43
- package/dist/packem_shared/detect-package-manager-DYp7n3mJ.mjs +0 -61
- package/dist/packem_shared/diffSnapshots-BeDvvNiF.mjs +0 -161
- package/dist/packem_shared/docker-hMQ97KSQ.mjs +0 -21
- package/dist/packem_shared/insertSchemaExtension-DAqbfr9Z.mjs +0 -64
- package/dist/packem_shared/open-url-Dfq6fAyT.mjs +0 -41
- package/dist/packem_shared/output-format-wUvAN6AL.mjs +0 -17
- package/dist/packem_shared/parseArgs-YXFuKdEk.mjs +0 -56
- package/dist/packem_shared/parseManifest--vZf2FY1.mjs +0 -94
- package/dist/packem_shared/prompt-cancelled-APzX1Im-.mjs +0 -9
- package/dist/packem_shared/resolve-target-qbsJ_5sF.mjs +0 -16
- package/dist/packem_shared/runAddCommand-bnY6-HKb.mjs +0 -4
- package/dist/packem_shared/schema-drift-gate-BtBt0as0.mjs +0 -79
- package/dist/packem_shared/schemaIrToSnapshot-DdsljJT-.mjs +0 -43
- package/dist/packem_shared/storage-BIsph-Vk.mjs +0 -84
- package/dist/packem_shared/tui-prompts-BjEN8XgP.mjs +0 -658
- package/dist/packem_shared/wrangler-name-cy4yhm9j.mjs +0 -12
- package/dist/packem_shared/wrangler-secrets-P2_ZUR-k.mjs +0 -47
|
@@ -1,143 +1,185 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lunora-performance-audit
|
|
3
|
-
description: Diagnoses and fixes Lunora performance problems — full-table scans,
|
|
4
|
-
indexes, OCC write conflicts, oversized subscriptions, and sharding/`.global()`
|
|
5
|
-
scaling. Use when queries are slow, mutations conflict, or `@lunora/advisor`
|
|
6
|
-
flags a table.
|
|
3
|
+
description: Diagnoses and fixes Lunora performance problems — full-table scans and missing indexes, expensive `ctx.db.related` traversals, OCC write conflicts (409 `CONFLICT`), live queries that re-run too often, hot shards, and choosing `.shardBy(key)` or `.global()` to scale. Starts from measured signal (`lunora insights`) and static signal (`lunora advisor`, `@lunora/advisor` lints such as `filter_without_index`, `unbounded_collect`, `unindexed_foreign_key`, `hot_shard`). Use when the user says a query or page is slow, mutations fail with conflicts under load, subscriptions are chatty, asks to "audit performance", "add the right index", or "should I shard this table", or when the Studio Advisors tab or `lunora advisor` flags something.
|
|
7
4
|
---
|
|
8
5
|
|
|
9
6
|
# Lunora Performance Audit
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
Find the measured problem, route it to the matching fix below, and apply the
|
|
9
|
+
fix to every function that touches the same table.
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
If traffic is modest and nothing is measured as slow, prefer simpler code. Don't
|
|
12
|
+
introduce sharding, digest tables or document splits on speculation.
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
- Mutations retry or conflict under load (OCC).
|
|
18
|
-
- Subscriptions fan out updates too broadly or re-run too often.
|
|
19
|
-
- The Lunora Studio **Advisors** tab or `@lunora/advisor` flags a table.
|
|
14
|
+
## Workflow
|
|
20
15
|
|
|
21
|
-
|
|
16
|
+
1. **Scope** one concrete user flow with a clear entry and exit.
|
|
17
|
+
2. **Gather signal** with `lunora insights` (runtime) and `lunora advisor`
|
|
18
|
+
(static).
|
|
19
|
+
3. **Trace** every `ctx.db` read and write in that flow.
|
|
20
|
+
4. **Route** to the problem class below and fix it.
|
|
21
|
+
5. **Fix siblings** so every function reads the table the same indexed way. A
|
|
22
|
+
half-migrated access pattern is its own bug.
|
|
23
|
+
6. **Verify:** behaviour unchanged, the advisor finding cleared, and the
|
|
24
|
+
`insights` row improved after the fix has served traffic.
|
|
25
|
+
|
|
26
|
+
## Signal
|
|
27
|
+
|
|
28
|
+
### Runtime: `lunora insights`
|
|
29
|
+
|
|
30
|
+
Needs a running worker that has served traffic. It reports measured problems:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
lunora insights # local dev worker (http://localhost:8787)
|
|
34
|
+
lunora insights --shard channel:demo # one shard instead of the root
|
|
35
|
+
lunora insights --limit 25 --format json
|
|
36
|
+
LUNORA_ADMIN_TOKEN=… lunora insights --prod --url https://app.example.com
|
|
37
|
+
```
|
|
22
38
|
|
|
23
|
-
-
|
|
24
|
-
|
|
39
|
+
It ranks per-function **write-conflict hot-spots** (go to Write conflicts),
|
|
40
|
+
**error rates**, and **latency outliers** (usually Read amplification). The
|
|
41
|
+
Studio Issues panel and `lunora logs` give error detail once you know where to
|
|
42
|
+
look.
|
|
25
43
|
|
|
26
|
-
|
|
44
|
+
### Static: `lunora advisor` and `@lunora/advisor`
|
|
27
45
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
3. **Route** to the matching problem class below.
|
|
31
|
-
4. **Fix siblings** consistently — every function touching the same table should
|
|
32
|
-
read it the same indexed way.
|
|
33
|
-
5. **Verify** behavior is unchanged and the advisor finding clears.
|
|
46
|
+
No traffic needed. The Studio **Advisors** tab shows the same findings live in
|
|
47
|
+
dev.
|
|
34
48
|
|
|
35
|
-
|
|
49
|
+
```bash
|
|
50
|
+
lunora advisor # score + write lunora.advisor.map.json
|
|
51
|
+
lunora advisor --entry messages#send # one procedure (file#exportName)
|
|
52
|
+
lunora advisor --baseline # CI: fail on regression vs the committed map
|
|
53
|
+
```
|
|
36
54
|
|
|
37
|
-
|
|
55
|
+
Lint ids are snake_case. The performance-relevant ones:
|
|
38
56
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
| Lint | Meaning |
|
|
58
|
+
| --------------------------------- | ---------------------------------------------------------------------------- |
|
|
59
|
+
| `filter_without_index` | A query filters a table with no covering index. |
|
|
60
|
+
| `unbounded_collect` | `.collect()` with no index or filter loads every row (and re-sends it live). |
|
|
61
|
+
| `filter_on_primary_key` | Filtering on `_id` scans; use `ctx.db.get(id)`. |
|
|
62
|
+
| `unindexed_foreign_key` | An FK column has no index. |
|
|
63
|
+
| `unindexed_relation_target` | A `many` relation's target FK has no index, so `with:` reads scan. |
|
|
64
|
+
| `duplicate_index` / `empty_index` | Wasted or malformed index. |
|
|
65
|
+
| `index_utilization` (runtime) | A declared index nothing uses, or a hot table read with none. |
|
|
66
|
+
| `hot_shard` (runtime) | One shard takes a disproportionate share of a sharded function's load. |
|
|
67
|
+
| `fan_out_breadth` (runtime) | A shard set so wide a cross-shard read nears the subrequest limit. |
|
|
50
68
|
|
|
51
|
-
##
|
|
69
|
+
## Read amplification (the common one)
|
|
52
70
|
|
|
53
|
-
**Symptom:** a query reads the whole table to return a few rows;
|
|
54
|
-
`
|
|
71
|
+
**Symptom:** a query reads the whole table to return a few rows; a latency
|
|
72
|
+
outlier in `insights`, or `filter_without_index` / `unbounded_collect`.
|
|
55
73
|
|
|
56
|
-
**Fix:**
|
|
57
|
-
and use `.withIndex()` with an equality/range on the leading columns.
|
|
74
|
+
**Fix:** declare an index and read through it with `.withIndex()`:
|
|
58
75
|
|
|
59
76
|
```ts
|
|
60
|
-
//
|
|
77
|
+
// (auth / org membership check on `orgId` elided — see lunora-functions)
|
|
78
|
+
// Scans every row, then filters in memory.
|
|
61
79
|
const mine = (await ctx.db.query("documents").collect()).filter((d) => d.orgId === orgId);
|
|
62
80
|
|
|
63
|
-
//
|
|
81
|
+
// schema.ts
|
|
64
82
|
documents: defineTable({ orgId: v.string(), createdAt: v.number() /* … */ }).index("by_org_created", ["orgId", "createdAt"]);
|
|
65
83
|
|
|
66
|
-
//
|
|
84
|
+
// Reads only the org's range.
|
|
67
85
|
const mine = await ctx.db
|
|
68
86
|
.query("documents")
|
|
69
87
|
.withIndex("by_org_created", (q) => q.eq("orgId", orgId))
|
|
70
88
|
.collect();
|
|
71
89
|
```
|
|
72
90
|
|
|
73
|
-
|
|
74
|
-
|
|
91
|
+
Put equality columns first, then the range/sort column. For a large range,
|
|
92
|
+
paginate instead of `.collect()`.
|
|
75
93
|
|
|
76
|
-
##
|
|
94
|
+
## Relation traversal cost
|
|
77
95
|
|
|
78
|
-
**Symptom:**
|
|
79
|
-
|
|
80
|
-
overlapping state conflict and retry.
|
|
96
|
+
**Symptom:** a query using `ctx.db.related` is slow, or one page reads far more
|
|
97
|
+
rows than it returns.
|
|
81
98
|
|
|
82
|
-
|
|
99
|
+
A traversal is one batched `findMany` per edge, per hop, and the frontier grows
|
|
100
|
+
multiplicatively. The options are bounded, and an out-of-range value is rejected
|
|
101
|
+
with `BAD_REQUEST` instead of being clamped, so a throwing traversal means the
|
|
102
|
+
request is too big, not that there's a bug to route around.
|
|
83
103
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
hotspot.
|
|
90
|
-
3. **Avoid unbounded counters/aggregates in the hot path.** Accumulate in a
|
|
91
|
-
sharded/append shape and fold lazily rather than serializing every writer
|
|
92
|
-
through one row.
|
|
104
|
+
| Option | Default | Cap | Why |
|
|
105
|
+
| ------------- | ------- | -------- | ------------------------------------------------------------- |
|
|
106
|
+
| `depth` | `1` | `4` | Past 4 hops, request time is the limit, not `limit`. |
|
|
107
|
+
| `limit` | `50` | `200` | Nodes per page. |
|
|
108
|
+
| cursor offset | `0` | `10 000` | The offset is re-walked: each hop reads `N + limit + 1` rows. |
|
|
93
109
|
|
|
94
|
-
|
|
110
|
+
Fixes, in order:
|
|
95
111
|
|
|
96
|
-
**
|
|
97
|
-
|
|
112
|
+
1. **Narrow `edges`** to the `"<table>.<column>"` edges the feature needs. One
|
|
113
|
+
unrelated `v.id(...)` column can double the frontier per hop.
|
|
114
|
+
2. **Set `direction`** to `"in"` or `"out"` instead of the default `"both"`.
|
|
115
|
+
3. **Lower `depth` before raising `limit`.** Hops multiply; a page is linear.
|
|
116
|
+
4. **Index the foreign keys.** Every inward hop is a `WHERE fk IN (…)` read
|
|
117
|
+
(`unindexed_foreign_key`).
|
|
118
|
+
5. **Stop deep-paging.** Needing an offset past 10 000 means the traversal is
|
|
119
|
+
the wrong tool; narrow the walk.
|
|
120
|
+
|
|
121
|
+
## Write conflicts (OCC)
|
|
98
122
|
|
|
99
|
-
**
|
|
123
|
+
**Symptom:** mutations on hot rows fail under concurrency, or a function tops
|
|
124
|
+
the write-conflict list in `insights`.
|
|
100
125
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
nothing; the lever is _how many rows each live query depends on_, not raw
|
|
107
|
-
connection count.
|
|
126
|
+
ShardDO uses optimistic concurrency. When a concurrent write commits first, the
|
|
127
|
+
mutation throws `ConflictError` (`code: "CONFLICT"`, HTTP 409). The runtime does
|
|
128
|
+
not retry it; a mutation body runs at most once per call, and the caller
|
|
129
|
+
decides what to do. On the client, check with `isConflictError(error)` from
|
|
130
|
+
`@lunora/client` (also exported via `lunorash/client`).
|
|
108
131
|
|
|
109
|
-
|
|
132
|
+
Not every 409 is contention. On the server, `ConflictError.kind` is `"occ"` for
|
|
133
|
+
real write contention, and `"unique"`, `"restrict"` or `"trigger"` for
|
|
134
|
+
constraint and guard failures. The client only sees `code: "CONFLICT"` (`kind`
|
|
135
|
+
is not part of the error envelope), so it cannot tell them apart. `insights` counts only `occ` as a write conflict, so fix a `unique`
|
|
136
|
+
409 in the data or the insert logic, not by sharding.
|
|
110
137
|
|
|
111
|
-
|
|
138
|
+
Fixes, in order:
|
|
139
|
+
|
|
140
|
+
1. **Narrow the write.** Patch only changed fields; avoid read-modify-write over
|
|
141
|
+
rows another mutation also writes.
|
|
142
|
+
2. **Partition with `.shardBy(key)`.** Per-user / per-tenant / per-room state
|
|
143
|
+
in its own Durable Object never contends across keys. This is the main
|
|
144
|
+
horizontal-scale lever, since most contention is one hot DO.
|
|
145
|
+
3. **Take counters and aggregates off the hot row.** Append and fold lazily
|
|
146
|
+
instead of serializing every writer through one row.
|
|
147
|
+
|
|
148
|
+
If `hot_shard` fires on an already-sharded table, the key is skewed: re-shard on
|
|
149
|
+
a higher-cardinality key or split the hot entity's state.
|
|
150
|
+
|
|
151
|
+
## Subscription cost
|
|
152
|
+
|
|
153
|
+
**Symptom:** a `useQuery` re-runs and re-pushes to many clients on unrelated
|
|
154
|
+
writes.
|
|
112
155
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
156
|
+
- Scope query args tightly so a live query depends only on the rows it renders;
|
|
157
|
+
a query keyed by `orgId` shouldn't re-run on another org's write.
|
|
158
|
+
- Read through indexes so the dependency is the index range, not the table.
|
|
159
|
+
- Idle WebSockets are hibernated and cost nothing. The lever is how many rows
|
|
160
|
+
each live query depends on, not the connection count.
|
|
117
161
|
|
|
118
|
-
|
|
162
|
+
## Cross-region reads
|
|
119
163
|
|
|
120
|
-
-
|
|
121
|
-
_writes_ (e.g. messages per room). Reads are per-shard.
|
|
122
|
-
- `.global()`: replicates a table to D1 — scales _cross-region reads_ with
|
|
123
|
-
read-your-writes (e.g. a mostly-read catalog).
|
|
124
|
-
- They are not combined on the same table; the default (neither) is a single
|
|
125
|
-
root-scoped ShardDO.
|
|
164
|
+
**Symptom:** read-heavy, rarely-written data is slow for distant users.
|
|
126
165
|
|
|
127
|
-
|
|
166
|
+
**Fix:** `.global()` replicates the table to D1 for low-latency reads from the
|
|
167
|
+
edge, with read-your-writes via D1 sessions. Use it only for read-mostly tables:
|
|
168
|
+
it adds write-path cost and its own DDL flow (`lunora-migration-helper`). If the
|
|
169
|
+
dataset outgrows D1, `.global({ backend: "hyperdrive" })` serves the same
|
|
170
|
+
reactive contract from Postgres/MySQL (`lunora-setup-hyperdrive-global`).
|
|
128
171
|
|
|
129
|
-
|
|
130
|
-
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
match — half-migrated access patterns are their own bug.
|
|
172
|
+
`.shardBy(key)` scales writes (rows in one DO per key); `.global()` scales
|
|
173
|
+
cross-region reads. A table can't have both; the default is the single root
|
|
174
|
+
ShardDO. Switching an existing table's mode moves its rows, which is an
|
|
175
|
+
export/import, not a schema edit (see `lunora-migration-helper`).
|
|
134
176
|
|
|
135
177
|
## Checklist
|
|
136
178
|
|
|
137
|
-
- [ ] Scoped one
|
|
138
|
-
- [ ] Checked
|
|
139
|
-
- [ ]
|
|
140
|
-
- [ ]
|
|
141
|
-
- [ ]
|
|
142
|
-
- [ ]
|
|
143
|
-
- [ ]
|
|
179
|
+
- [ ] Scoped one flow; traced every `ctx.db` read/write.
|
|
180
|
+
- [ ] Checked `lunora insights` (if there's traffic) and `lunora advisor`.
|
|
181
|
+
- [ ] Scans replaced with `.withIndex()` reads; FKs indexed.
|
|
182
|
+
- [ ] Traversals narrowed with `edges` / `direction` before touching `depth`.
|
|
183
|
+
- [ ] Write conflicts confirmed as `occ`, then writes narrowed and/or `.shardBy(key)` applied.
|
|
184
|
+
- [ ] Live queries depend on few rows; `.global()` only on read-mostly tables.
|
|
185
|
+
- [ ] Siblings fixed consistently; behaviour unchanged; finding cleared.
|