@aotter/mantle 0.0.11-alpha.62 → 0.0.11-alpha.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/harness-cli.d.ts +3 -0
- package/dist/harness-cli.d.ts.map +1 -0
- package/dist/harness-cli.js +209 -0
- package/dist/harness-cli.js.map +1 -0
- package/dist/runtime-testing.d.ts +2 -0
- package/dist/runtime-testing.d.ts.map +1 -0
- package/dist/runtime-testing.js +2 -0
- package/dist/runtime-testing.js.map +1 -0
- package/docs/adapter-guide.md +8 -0
- package/docs/performance-harness.md +104 -0
- package/docs/schema-indexes.md +14 -1
- package/package.json +12 -5
- package/skills/develop/SKILL.md +38 -0
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ Adopters install this one package and import from subpaths. Sub-packages remain
|
|
|
20
20
|
|---|---|
|
|
21
21
|
| `@aotter/mantle/spec` (or root) | Manifest grammar, validators, JSON-Schema→Zod, diagnostic catalog (no env / no IO) |
|
|
22
22
|
| `@aotter/mantle/runtime` | Hexagonal runtime: domain ports, use cases, infrastructure helpers (no adapter deps) |
|
|
23
|
+
| `@aotter/mantle/runtime/testing` | Node-only crowded SQLite planner and HTTP sampling helpers |
|
|
23
24
|
| `@aotter/mantle/cloudflare` | Cloudflare Workers adapter — D1, KV, R2, Better Auth, MCP via `@cloudflare/workers-oauth-provider` |
|
|
24
25
|
| `@aotter/mantle/admin-ui` | Pre-built React 19 admin SPA bundle |
|
|
25
26
|
|
|
@@ -29,6 +30,13 @@ import { createCmsRuntime } from "@aotter/mantle/runtime";
|
|
|
29
30
|
import { mountServerEndpoints } from "@aotter/mantle/cloudflare";
|
|
30
31
|
```
|
|
31
32
|
|
|
33
|
+
The package also installs `mantle-harness`:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pnpm exec mantle-harness indexes --require-public
|
|
37
|
+
pnpm exec mantle-harness http --base-url http://127.0.0.1:8787 --route page=/en/example
|
|
38
|
+
```
|
|
39
|
+
|
|
32
40
|
## Getting started
|
|
33
41
|
|
|
34
42
|
Give the [Mantle repo](https://github.com/aotter/mantle) to a coding agent or
|
|
@@ -97,6 +105,8 @@ The `mantle-runtime` package never imports Cloudflare-specific types — adapter
|
|
|
97
105
|
Queue wiring, retry/DLQ, idempotency, and delivery guarantees)
|
|
98
106
|
- `node_modules/@aotter/mantle/docs/schema-indexes.md` (ordered composite
|
|
99
107
|
JSON-field indexes, D1 query plans, and the safe Procedure SQL helper)
|
|
108
|
+
- `node_modules/@aotter/mantle/docs/performance-harness.md` (crowded SQLite,
|
|
109
|
+
Wrangler-local D1, cache HIT/MISS, and coding-agent guardrails)
|
|
100
110
|
- `node_modules/@aotter/mantle/docs/adr/`
|
|
101
111
|
- `node_modules/@aotter/mantle/skills/develop/SKILL.md`
|
|
102
112
|
- `node_modules/@aotter/mantle/skills/plugin/SKILL.md`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness-cli.d.ts","sourceRoot":"","sources":["../src/harness-cli.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { argv, stderr, stdout } from "node:process";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { ValidateManifestsUseCase, } from "@aotter/mantle-spec";
|
|
5
|
+
import { loadManifestsFromRoot } from "@aotter/mantle-spec/cli";
|
|
6
|
+
import { benchmarkHttpRoutes, inspectIndexCoverage, } from "@aotter/mantle-runtime/testing";
|
|
7
|
+
async function main() {
|
|
8
|
+
const command = argv[2];
|
|
9
|
+
if (!command || command === "--help" || command === "-h") {
|
|
10
|
+
printHelp();
|
|
11
|
+
return command ? 0 : 2;
|
|
12
|
+
}
|
|
13
|
+
if (command === "indexes")
|
|
14
|
+
return runIndexes(argv.slice(3));
|
|
15
|
+
if (command === "http")
|
|
16
|
+
return runHttp(argv.slice(3));
|
|
17
|
+
stderr.write(`Unknown subcommand: ${command}\n`);
|
|
18
|
+
return 2;
|
|
19
|
+
}
|
|
20
|
+
async function runIndexes(rawArgs) {
|
|
21
|
+
let values;
|
|
22
|
+
try {
|
|
23
|
+
({ values } = parseArgs({
|
|
24
|
+
args: [...rawArgs],
|
|
25
|
+
options: {
|
|
26
|
+
manifests: { type: "string" },
|
|
27
|
+
rows: { type: "string" },
|
|
28
|
+
require: { type: "string", multiple: true },
|
|
29
|
+
"require-public": { type: "boolean" },
|
|
30
|
+
format: { type: "string" },
|
|
31
|
+
help: { type: "boolean", short: "h" },
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
stderr.write(`${message(error)}\n`);
|
|
37
|
+
return 2;
|
|
38
|
+
}
|
|
39
|
+
if (values.help) {
|
|
40
|
+
printIndexesHelp();
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
const format = outputFormat(values.format);
|
|
44
|
+
const loaded = await loadManifestsFromRoot(values.manifests ?? "./manifests");
|
|
45
|
+
const validation = ValidateManifestsUseCase.run({
|
|
46
|
+
manifests: loaded.manifests,
|
|
47
|
+
filePaths: loaded.filePaths,
|
|
48
|
+
});
|
|
49
|
+
const errors = [...loaded.parseErrors, ...validation.diagnostics]
|
|
50
|
+
.filter((diagnostic) => diagnostic.severity === "error");
|
|
51
|
+
if (errors.length > 0) {
|
|
52
|
+
emitErrors(errors, format);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
if (loaded.manifests.length === 0) {
|
|
56
|
+
stderr.write(`No manifests found under ${values.manifests ?? "./manifests"}\n`);
|
|
57
|
+
return 2;
|
|
58
|
+
}
|
|
59
|
+
const rows = values.rows === undefined ? undefined : Number(values.rows);
|
|
60
|
+
if (rows !== undefined && (!Number.isFinite(rows) || rows <= 0)) {
|
|
61
|
+
stderr.write("--rows must be a positive number\n");
|
|
62
|
+
return 2;
|
|
63
|
+
}
|
|
64
|
+
const report = await inspectIndexCoverage(loaded.manifests, {
|
|
65
|
+
rowsPerSchema: rows,
|
|
66
|
+
requirePublic: values["require-public"] === true,
|
|
67
|
+
requiredViews: values.require ?? [],
|
|
68
|
+
});
|
|
69
|
+
emitIndexReport(report, format);
|
|
70
|
+
return report.summary.requiredFailures > 0 ? 1 : 0;
|
|
71
|
+
}
|
|
72
|
+
async function runHttp(rawArgs) {
|
|
73
|
+
let values;
|
|
74
|
+
try {
|
|
75
|
+
({ values } = parseArgs({
|
|
76
|
+
args: [...rawArgs],
|
|
77
|
+
options: {
|
|
78
|
+
route: { type: "string", multiple: true },
|
|
79
|
+
"base-url": { type: "string" },
|
|
80
|
+
rounds: { type: "string" },
|
|
81
|
+
warmup: { type: "string" },
|
|
82
|
+
format: { type: "string" },
|
|
83
|
+
help: { type: "boolean", short: "h" },
|
|
84
|
+
},
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
stderr.write(`${message(error)}\n`);
|
|
89
|
+
return 2;
|
|
90
|
+
}
|
|
91
|
+
if (values.help) {
|
|
92
|
+
printHttpHelp();
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
if (!values.route || values.route.length === 0) {
|
|
96
|
+
stderr.write("http requires at least one --route name=/path-or-url\n");
|
|
97
|
+
return 2;
|
|
98
|
+
}
|
|
99
|
+
const format = outputFormat(values.format);
|
|
100
|
+
const baseUrl = (values["base-url"] ?? "").replace(/\/$/, "");
|
|
101
|
+
const targets = values.route.map((raw) => {
|
|
102
|
+
const separator = raw.indexOf("=");
|
|
103
|
+
if (separator < 1)
|
|
104
|
+
throw new Error(`invalid --route ${JSON.stringify(raw)}`);
|
|
105
|
+
const name = raw.slice(0, separator);
|
|
106
|
+
const path = raw.slice(separator + 1);
|
|
107
|
+
return { name, url: path.startsWith("/") ? `${baseUrl}${path}` : path };
|
|
108
|
+
});
|
|
109
|
+
if (targets.some(({ url }) => !/^https?:\/\//.test(url))) {
|
|
110
|
+
stderr.write("route URLs must be absolute, or use --base-url with /paths\n");
|
|
111
|
+
return 2;
|
|
112
|
+
}
|
|
113
|
+
let report;
|
|
114
|
+
try {
|
|
115
|
+
report = await benchmarkHttpRoutes({
|
|
116
|
+
targets,
|
|
117
|
+
rounds: numericOption(values.rounds, "--rounds"),
|
|
118
|
+
warmup: numericOption(values.warmup, "--warmup"),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
stderr.write(`${message(error)}\n`);
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
125
|
+
emitHttpReport(report, format);
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
function emitIndexReport(report, format) {
|
|
129
|
+
if (format === "json") {
|
|
130
|
+
stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
for (const path of report.paths) {
|
|
134
|
+
const status = path.required ? path.passed ? "PASS" : "FAIL" : "ADVISORY";
|
|
135
|
+
const detail = path.findings.length > 0
|
|
136
|
+
? path.findings.join("; ")
|
|
137
|
+
: path.usedIndexes.join(", ") || "planner found no named index";
|
|
138
|
+
stdout.write(`${status} ${path.view}: ${detail}\n`);
|
|
139
|
+
}
|
|
140
|
+
for (const view of report.summary.missingRequiredViews) {
|
|
141
|
+
stdout.write(`FAIL ${view}: required View not found\n`);
|
|
142
|
+
}
|
|
143
|
+
stdout.write(`views=${report.summary.views} required=${report.summary.required} ` +
|
|
144
|
+
`failures=${report.summary.requiredFailures} rows/schema=${report.rowsPerSchema}\n`);
|
|
145
|
+
}
|
|
146
|
+
function emitHttpReport(report, format) {
|
|
147
|
+
if (format === "json") {
|
|
148
|
+
stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
for (const result of report.results) {
|
|
152
|
+
const queries = result.queryCount ? ` queries.p95=${result.queryCount.p95}` : "";
|
|
153
|
+
const rows = result.rowsRead ? ` rows_read.p95=${result.rowsRead.p95}` : "";
|
|
154
|
+
stdout.write(`${result.name}: p50=${result.timingMs.p50.toFixed(2)}ms ` +
|
|
155
|
+
`p95=${result.timingMs.p95.toFixed(2)}ms${queries}${rows}\n`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function emitErrors(errors, format) {
|
|
159
|
+
if (format === "json")
|
|
160
|
+
stdout.write(`${JSON.stringify({ errors }, null, 2)}\n`);
|
|
161
|
+
else
|
|
162
|
+
for (const error of errors)
|
|
163
|
+
stderr.write(`${error.code} ${error.path}: ${error.message}\n`);
|
|
164
|
+
}
|
|
165
|
+
function outputFormat(raw) {
|
|
166
|
+
if (raw === undefined)
|
|
167
|
+
return stdout.isTTY ? "text" : "json";
|
|
168
|
+
if (raw === "json" || raw === "text")
|
|
169
|
+
return raw;
|
|
170
|
+
throw new Error(`--format must be text or json; got ${JSON.stringify(raw)}`);
|
|
171
|
+
}
|
|
172
|
+
function numericOption(raw, name) {
|
|
173
|
+
if (raw === undefined)
|
|
174
|
+
return undefined;
|
|
175
|
+
const value = Number(raw);
|
|
176
|
+
if (!Number.isFinite(value) || value < 0)
|
|
177
|
+
throw new Error(`${name} must be a non-negative number`);
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
function message(error) {
|
|
181
|
+
return error instanceof Error ? error.message : String(error);
|
|
182
|
+
}
|
|
183
|
+
function printHelp() {
|
|
184
|
+
stdout.write(`mantle-harness — measured performance checks\n\n` +
|
|
185
|
+
`Usage: mantle-harness <indexes|http> [options]\n\n` +
|
|
186
|
+
` indexes Execute compiled Views in crowded SQLite and inspect plans\n` +
|
|
187
|
+
` http Sample a running Worker and report p50/p95 + metric headers\n`);
|
|
188
|
+
}
|
|
189
|
+
function printIndexesHelp() {
|
|
190
|
+
stdout.write(`Usage: mantle-harness indexes [options]\n\n` +
|
|
191
|
+
` --manifests <dir> Manifest root (default: ./manifests)\n` +
|
|
192
|
+
` --rows <n> Deterministic rows per Schema (default: 2000)\n` +
|
|
193
|
+
` --require <view> Fail when this View lacks its access path; repeatable\n` +
|
|
194
|
+
` --require-public Apply the gate to public Views\n` +
|
|
195
|
+
` --format <fmt> text or json\n`);
|
|
196
|
+
}
|
|
197
|
+
function printHttpHelp() {
|
|
198
|
+
stdout.write(`Usage: mantle-harness http [options]\n\n` +
|
|
199
|
+
` --base-url <url> Prefix for route paths\n` +
|
|
200
|
+
` --route <name=url> Route label and path/URL; repeatable\n` +
|
|
201
|
+
` --rounds <n> Measured requests per route (default: 20)\n` +
|
|
202
|
+
` --warmup <n> Warmup requests per route (default: 2)\n` +
|
|
203
|
+
` --format <fmt> text or json\n`);
|
|
204
|
+
}
|
|
205
|
+
main().then((code) => { process.exitCode = code; }, (error) => {
|
|
206
|
+
stderr.write(`${message(error)}\n`);
|
|
207
|
+
process.exitCode = 2;
|
|
208
|
+
});
|
|
209
|
+
//# sourceMappingURL=harness-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness-cli.js","sourceRoot":"","sources":["../src/harness-cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-testing.d.ts","sourceRoot":"","sources":["../src/runtime-testing.ts"],"names":[],"mappings":"AAAA,cAAc,gCAAgC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-testing.js","sourceRoot":"","sources":["../src/runtime-testing.ts"],"names":[],"mappings":"AAAA,cAAc,gCAAgC,CAAC"}
|
package/docs/adapter-guide.md
CHANGED
|
@@ -61,6 +61,14 @@ await runtime.bootInit();
|
|
|
61
61
|
|
|
62
62
|
`bootInit()` runs canonical migrations, seeds `siteDefaults`, and validates the manifest set. Call it once before serving CMS traffic. The Cloudflare adapter's reference pattern is `packages/adapters/cloudflare/src/mount/bootRuntimeOnce.ts`.
|
|
63
63
|
|
|
64
|
+
Use purpose-shaped runtime surfaces for canonical data: `runtime.entryReader`
|
|
65
|
+
applies Schema index declarations, locale semantics, and the public `Entry`
|
|
66
|
+
projection; `runtime.siteConfig` owns site settings. `runtime.db` remains only
|
|
67
|
+
for source compatibility and is deprecated. An adapter that owns additional
|
|
68
|
+
tables should retain the `DatabaseDriver` it injected instead of reaching back
|
|
69
|
+
through the assembled runtime. Authoring continues through the pre-wired
|
|
70
|
+
content use cases.
|
|
71
|
+
|
|
64
72
|
## HTTP and MCP surfaces
|
|
65
73
|
|
|
66
74
|
The runtime is a library, not an HTTP server. A new adapter must mount equivalent framework routes:
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Data access, cache policy, and performance harness
|
|
2
|
+
|
|
3
|
+
Mantle site code declares intent; Core owns the shared storage layout and the
|
|
4
|
+
Cloudflare adapter owns provider bindings. A site-building agent should not
|
|
5
|
+
need Mantle table names, generated-column names, KV prefixes, or D1 APIs to
|
|
6
|
+
make a normal content/API/page change.
|
|
7
|
+
|
|
8
|
+
## Ownership
|
|
9
|
+
|
|
10
|
+
| Read or state | Owner | Notes |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| Entry get/list and public slug/data-field/published reads | `DatabaseEntryRepository` through `EntryRepository` / `EntryReader` | Schema-aware field resolution is shared here. |
|
|
13
|
+
| Manifest View execution | `ExecuteViewUseCase` + `ViewSqlCompiler` | The deliberate compiled-query exception; it still resolves declared Schema indexes. |
|
|
14
|
+
| Editable settings and code-owned locale/media policy | `DatabaseSiteConfigRepository` | Editable values and dynamic media tool policy are read fresh; boot-seeded locale policy may be memoized within the runtime instance. |
|
|
15
|
+
| Pending media uploads | `DatabasePendingUploadRepository` | Canonical, read-after-write D1 state; never publish-cache state. |
|
|
16
|
+
| Rendered HTML, Markdown, and `llms.txt` | `HtmlPublishOrchestrator` plus the Cloudflare public-route cache policy | Reproducible derivatives live in KV. Settings updates invalidate through a runtime use case. |
|
|
17
|
+
| D1/KV transport and optional query metrics | Cloudflare bindings | Bindings stay thin. Query/cache policy does not belong in a generic provider `BaseRepository`. |
|
|
18
|
+
|
|
19
|
+
`CmsRuntime.db` remains deprecated compatibility surface. New site code uses
|
|
20
|
+
Manifests, runtime use cases, `entryReader`, and `siteConfig`. A site may own
|
|
21
|
+
additional tables behind its own repository at the composition root, but it
|
|
22
|
+
must not query Mantle-owned tables through `runtime.db`.
|
|
23
|
+
|
|
24
|
+
## Cache contract
|
|
25
|
+
|
|
26
|
+
- D1 is canonical for entries, site settings, media metadata, and pending
|
|
27
|
+
uploads. KV contains only reproducible public artifacts.
|
|
28
|
+
- A public KV hit checks the cache before loading full editable site settings.
|
|
29
|
+
Locale policy is the small boot-seeded exception. A warm entry/page artifact
|
|
30
|
+
therefore performs zero D1 queries.
|
|
31
|
+
- A safe cache miss renders from canonical state and schedules KV write-back
|
|
32
|
+
with the request execution context. It waits inline only when no execution
|
|
33
|
+
context exists, such as a direct unit call.
|
|
34
|
+
- Site-setting writes call the runtime settings use case, which completes
|
|
35
|
+
public-artifact invalidation before reporting success. HTTP routes do not
|
|
36
|
+
scan/delete KV prefixes themselves.
|
|
37
|
+
- Do not cache every repository read. Cross-isolate correctness for editable
|
|
38
|
+
data wins unless a read has a measured hot-path contract and explicit
|
|
39
|
+
invalidation.
|
|
40
|
+
|
|
41
|
+
## Index coverage
|
|
42
|
+
|
|
43
|
+
The Node harness uses the real canonical migrations, generated Schema DDL,
|
|
44
|
+
real View compiler, deterministic skewed rows, and SQLite
|
|
45
|
+
`EXPLAIN QUERY PLAN`:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm exec mantle-harness indexes --manifests ./manifests --format text
|
|
49
|
+
pnpm exec mantle-harness indexes --require-public --format json
|
|
50
|
+
pnpm exec mantle-harness indexes --require account-members --format json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Without `--require-public` or `--require`, findings are advisory. A required
|
|
54
|
+
path fails on an `entries` table scan, a temporary ORDER BY B-tree, or a
|
|
55
|
+
data-field predicate/order that does not use a declared Schema index.
|
|
56
|
+
Projection alone does not require an index. `mantle validate` remains a pure
|
|
57
|
+
correctness check; no performance grammar or manifest atom was added.
|
|
58
|
+
|
|
59
|
+
Use the machine report in CI. It includes the compiled SQL and parameters,
|
|
60
|
+
query-plan details, named indexes, scan/sort flags, result count, SQLite
|
|
61
|
+
version, fixture row count, and required-failure summary.
|
|
62
|
+
|
|
63
|
+
## Worker/API/page sampling
|
|
64
|
+
|
|
65
|
+
Sample any running environment with the public HTTP helper:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pnpm exec mantle-harness http \
|
|
69
|
+
--base-url http://127.0.0.1:8787 \
|
|
70
|
+
--route recent=/api/views/recent-posts \
|
|
71
|
+
--route page=/en/posts/hello \
|
|
72
|
+
--rounds 20 --warmup 2 --format json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Timing always reports p50/p95/max. A test-only Worker wrapper may also return
|
|
76
|
+
`x-mantle-query-count` and `x-mantle-rows-read`; those become distributions in
|
|
77
|
+
the same report. Do not expose these diagnostic headers in production.
|
|
78
|
+
|
|
79
|
+
Core CI runs `pnpm bench:wrangler` against real Wrangler-local D1, KV, Worker
|
|
80
|
+
HTTP routing, View execution, and live page rendering. It compares 100 and
|
|
81
|
+
10,000 row fixtures, then samples page MISS and HIT separately. CI gates
|
|
82
|
+
row-read scaling, endpoint query budgets, and zero-D1 warm hits, not absolute
|
|
83
|
+
milliseconds.
|
|
84
|
+
|
|
85
|
+
## Seven findings: measured disposition
|
|
86
|
+
|
|
87
|
+
Measured on the deterministic 2026-08-01 Wrangler-local fixture; timings are
|
|
88
|
+
diagnostic, while query/row counts are the stable assertions.
|
|
89
|
+
|
|
90
|
+
| Finding | Disposition |
|
|
91
|
+
|---|---|
|
|
92
|
+
| Public KV hits read D1 first | Fixed. A 10,000-row warm page measured 0 queries / 0 rows read. |
|
|
93
|
+
| Slug/locale reads bypass generated indexes | Fixed by the shared schema-aware entry-read boundary. A 10,000-row page MISS measured 2 queries / 5 rows read. |
|
|
94
|
+
| OFFSET pagination | Accepted for the v0.1 bounded-result surfaces: every response is capped at 500 rows and public hot paths must stay shallow. Deep/export workloads require a purpose-shaped cursor API before they are declared hot. |
|
|
95
|
+
| Admin substring search scans | Accepted only for the authenticated Admin collection browser, with a 500-row response cap. Large/search-heavy sites should add a purpose-shaped indexed View or dedicated search service; do not expose this scan publicly. |
|
|
96
|
+
| Published list/sitemap/llms paths lack system indexes | Fixed with measured partial indexes for published global, locale, collection, and collection+locale ordering. The 100-row and 10,000-row API runs both measured 1 query / 20 rows read. |
|
|
97
|
+
| Page MISS waits for KV write-back | Fixed. Reproducible artifacts write through `waitUntil`; regression coverage proves response completion does not await KV. |
|
|
98
|
+
| Benchmark stops at fake in-process dispatch | Fixed by the Node planner and Wrangler-local Worker/API/page layers. The old dispatch microbenchmark remains a narrow CPU signal only. |
|
|
99
|
+
|
|
100
|
+
The retained OFFSET and substring-search trade-offs are visible exceptions,
|
|
101
|
+
not patterns for new public APIs. Re-measure before widening either scope.
|
|
102
|
+
|
|
103
|
+
See also [Schema indexes](./schema-indexes.md) and the official Cloudflare
|
|
104
|
+
[D1 index guidance](https://developers.cloudflare.com/d1/best-practices/use-indexes/).
|
package/docs/schema-indexes.md
CHANGED
|
@@ -141,7 +141,20 @@ site still owns actor resolution, account rules, aggregation, and output.
|
|
|
141
141
|
|
|
142
142
|
## Verify a plan
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
Prefer the shipped crowded-data harness for a manifest View:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pnpm exec mantle-harness indexes --require-public --format text
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
It applies Mantle's real canonical migrations and generated DDL, seeds skewed
|
|
151
|
+
rows, compiles and executes the real View SQL, then records
|
|
152
|
+
`EXPLAIN QUERY PLAN`. By default findings are advisory; use `--require-public`
|
|
153
|
+
or repeat `--require <view-name>` only for paths whose performance is part of
|
|
154
|
+
the contract. See [the performance harness](./performance-harness.md).
|
|
155
|
+
|
|
156
|
+
For site-owned SQL that cannot be represented by a View, inspect the real
|
|
157
|
+
database directly when adding or changing a hot path:
|
|
145
158
|
|
|
146
159
|
```sql
|
|
147
160
|
PRAGMA index_list("entries");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aotter/mantle",
|
|
3
|
-
"version": "0.0.11-alpha.
|
|
3
|
+
"version": "0.0.11-alpha.63",
|
|
4
4
|
"description": "Umbrella entry for @aotter/mantle. Adopters install this one package and import from subpaths: /spec, /runtime, /cloudflare, /admin-ui. Sub-packages remain individually installable on npm for tooling / alt-adapter authors. The Netlify adapter ships as a private workspace stub in v0.1 — its subpath will be added when the impl lands in v0.2.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://mantle.tools/",
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
"sideEffects": false,
|
|
14
14
|
"main": "./dist/spec.js",
|
|
15
15
|
"types": "./dist/spec.d.ts",
|
|
16
|
+
"bin": {
|
|
17
|
+
"mantle-harness": "./dist/harness-cli.js"
|
|
18
|
+
},
|
|
16
19
|
"publishConfig": {
|
|
17
20
|
"access": "public",
|
|
18
21
|
"tag": "alpha",
|
|
@@ -31,6 +34,10 @@
|
|
|
31
34
|
"types": "./dist/runtime.d.ts",
|
|
32
35
|
"import": "./dist/runtime.js"
|
|
33
36
|
},
|
|
37
|
+
"./runtime/testing": {
|
|
38
|
+
"types": "./dist/runtime-testing.d.ts",
|
|
39
|
+
"import": "./dist/runtime-testing.js"
|
|
40
|
+
},
|
|
34
41
|
"./cloudflare": {
|
|
35
42
|
"types": "./dist/cloudflare.d.ts",
|
|
36
43
|
"import": "./dist/cloudflare.js"
|
|
@@ -47,10 +54,10 @@
|
|
|
47
54
|
"README.md"
|
|
48
55
|
],
|
|
49
56
|
"dependencies": {
|
|
50
|
-
"@aotter/mantle-cloudflare": "0.0.11-alpha.
|
|
51
|
-
"@aotter/mantle-
|
|
52
|
-
"@aotter/mantle-
|
|
53
|
-
"@aotter/mantle-
|
|
57
|
+
"@aotter/mantle-cloudflare": "0.0.11-alpha.63",
|
|
58
|
+
"@aotter/mantle-runtime": "0.0.11-alpha.63",
|
|
59
|
+
"@aotter/mantle-admin-ui": "0.0.11-alpha.63",
|
|
60
|
+
"@aotter/mantle-spec": "0.0.11-alpha.63"
|
|
54
61
|
},
|
|
55
62
|
"peerDependencies": {
|
|
56
63
|
"@cloudflare/workers-oauth-provider": "^0.8.0",
|
package/skills/develop/SKILL.md
CHANGED
|
@@ -117,11 +117,49 @@ Do not assume Cloudflare unless the project imports `@aotter/mantle/cloudflare`
|
|
|
117
117
|
or its adapter config is visible. A future Netlify adapter should satisfy the
|
|
118
118
|
same Core workflow through its own ports and provider setup.
|
|
119
119
|
|
|
120
|
+
Site code is a consumer of this abstraction. Use Manifests, runtime use cases,
|
|
121
|
+
`entryReader`, and `siteConfig`; do not query Mantle-owned `entries` or
|
|
122
|
+
`site_config`, reach through deprecated `runtime.db`, copy generated-column
|
|
123
|
+
names, or construct SDK KV keys. Cloudflare bindings belong only at the
|
|
124
|
+
composition root. If a normal feature cannot be expressed through a
|
|
125
|
+
purpose-shaped surface, treat that as a Core abstraction gap instead of
|
|
126
|
+
teaching the project Mantle internals.
|
|
127
|
+
|
|
128
|
+
## Performance Loop
|
|
129
|
+
|
|
130
|
+
After changing a Schema index, View filter/order, public API, or rendered page,
|
|
131
|
+
run the project's index check when present. Otherwise run the installed
|
|
132
|
+
harness directly:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pnpm exec mantle-harness indexes --require-public --format text
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The check uses crowded real SQLite and the shipped compiler. It complements
|
|
139
|
+
`pnpm validate`; it does not replace correctness validation. Declare the
|
|
140
|
+
smallest ordered index justified by the measured path and respect SQLite's
|
|
141
|
+
leftmost-prefix rule. Do not change user-visible filter or ordering semantics
|
|
142
|
+
just to make the gate pass. Do not add every permutation or cache every read.
|
|
143
|
+
|
|
144
|
+
For relevant Cloudflare serving changes, start the project and sample the
|
|
145
|
+
actual routes:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
pnpm exec mantle-harness http \
|
|
149
|
+
--base-url http://127.0.0.1:8787 \
|
|
150
|
+
--route page=/en/example \
|
|
151
|
+
--rounds 20 --warmup 2 --format text
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Prefer query plan, query count, `rows_read` scaling, and cache MISS/HIT
|
|
155
|
+
evidence. Do not create CI gates from absolute local milliseconds.
|
|
156
|
+
|
|
120
157
|
## Loop
|
|
121
158
|
|
|
122
159
|
```bash
|
|
123
160
|
pnpm install --frozen-lockfile
|
|
124
161
|
pnpm validate
|
|
162
|
+
pnpm check:indexes # when the project provides it
|
|
125
163
|
pnpm typecheck
|
|
126
164
|
pnpm check
|
|
127
165
|
```
|