contentbase 0.0.2 → 0.0.5
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/CLI.md +593 -0
- package/MCP-SERVER-SPEC.md +453 -0
- package/PRIMER.md +540 -0
- package/README.md +289 -13
- package/docs/README.md +110 -0
- package/docs/TABLE-OF-CONTENTS.md +7 -0
- package/docs/models.ts +38 -0
- package/models.ts +38 -0
- package/package.json +12 -3
- package/src/api/endpoints/actions.ts +34 -0
- package/src/api/endpoints/doc.ts +187 -0
- package/src/api/endpoints/docs-index.ts +26 -0
- package/src/api/endpoints/document.ts +171 -0
- package/src/api/endpoints/documents.ts +71 -0
- package/src/api/endpoints/inspect.ts +16 -0
- package/src/api/endpoints/models.ts +10 -0
- package/src/api/endpoints/query.ts +88 -0
- package/src/api/endpoints/root.ts +7 -0
- package/src/api/endpoints/search-reindex.ts +65 -0
- package/src/api/endpoints/search-status.ts +55 -0
- package/src/api/endpoints/search.ts +104 -0
- package/src/api/endpoints/semantic-search.ts +120 -0
- package/src/api/endpoints/text-search.ts +63 -0
- package/src/api/endpoints/validate.ts +34 -0
- package/src/api/helpers.ts +97 -0
- package/src/base-model.ts +12 -0
- package/src/cli/commands/action.ts +82 -44
- package/src/cli/commands/console.ts +124 -0
- package/src/cli/commands/create.ts +179 -53
- package/src/cli/commands/embed.ts +323 -0
- package/src/cli/commands/export.ts +58 -24
- package/src/cli/commands/extract.ts +174 -0
- package/src/cli/commands/help.ts +81 -0
- package/src/cli/commands/index.ts +17 -0
- package/src/cli/commands/init.ts +72 -48
- package/src/cli/commands/inspect.ts +56 -46
- package/src/cli/commands/mcp.ts +1219 -0
- package/src/cli/commands/search.ts +285 -0
- package/src/cli/commands/serve.ts +348 -0
- package/src/cli/commands/summary.ts +60 -0
- package/src/cli/commands/teach.ts +86 -0
- package/src/cli/commands/text-search.ts +134 -0
- package/src/cli/commands/validate.ts +126 -64
- package/src/cli/index.ts +88 -19
- package/src/cli/load-collection.ts +144 -17
- package/src/cli/registry.ts +28 -0
- package/src/collection.ts +361 -10
- package/src/define-model.ts +101 -4
- package/src/document.ts +47 -1
- package/src/extract-sections.ts +1 -1
- package/src/index.ts +14 -2
- package/src/model-instance.ts +35 -5
- package/src/node-shortcuts.ts +1 -1
- package/src/query/collection-query.ts +96 -9
- package/src/query/index.ts +7 -0
- package/src/query/query-dsl.ts +259 -0
- package/src/relationships/has-many.ts +39 -0
- package/src/relationships/index.ts +7 -2
- package/src/section.ts +2 -0
- package/src/types.ts +23 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/match-pattern.ts +65 -0
- package/src/validator.ts +18 -1
- package/bun.lock +0 -473
- package/examples/sdlc-queries.ts +0 -161
- package/scripts/examples/01-collection-setup.ts +0 -46
- package/scripts/examples/02-querying.ts +0 -67
- package/scripts/examples/03-sections.ts +0 -36
- package/scripts/examples/04-relationships.ts +0 -54
- package/scripts/examples/05-document-api.ts +0 -54
- package/scripts/examples/06-extract-sections.ts +0 -55
- package/scripts/examples/07-validation.ts +0 -46
- package/scripts/examples/08-serialization.ts +0 -51
- package/scripts/examples/lib/format.ts +0 -87
- package/scripts/examples/lib/setup.ts +0 -21
- package/scripts/examples/run-all.ts +0 -43
- package/showcases/national-parks/models.ts +0 -74
- package/showcases/national-parks/parks/acadia.mdx +0 -40
- package/showcases/national-parks/parks/yosemite.mdx +0 -44
- package/showcases/national-parks/parks/zion.mdx +0 -44
- package/showcases/national-parks/queries.ts +0 -103
- package/showcases/national-parks/trails/angels-landing.mdx +0 -19
- package/showcases/national-parks/trails/cathedral-lakes.mdx +0 -19
- package/showcases/national-parks/trails/half-dome.mdx +0 -19
- package/showcases/national-parks/trails/jordan-pond-path.mdx +0 -19
- package/showcases/national-parks/trails/mist-trail.mdx +0 -19
- package/showcases/national-parks/trails/observation-point.mdx +0 -19
- package/showcases/national-parks/trails/precipice-trail.mdx +0 -19
- package/showcases/national-parks/trails/the-narrows.mdx +0 -19
- package/showcases/recipes/cuisines/chinese.mdx +0 -28
- package/showcases/recipes/cuisines/italian.mdx +0 -32
- package/showcases/recipes/cuisines/mexican.mdx +0 -28
- package/showcases/recipes/models.ts +0 -77
- package/showcases/recipes/queries.ts +0 -89
- package/showcases/recipes/recipes/chinese/egg-fried-rice.mdx +0 -43
- package/showcases/recipes/recipes/chinese/mapo-tofu.mdx +0 -47
- package/showcases/recipes/recipes/italian/bruschetta.mdx +0 -38
- package/showcases/recipes/recipes/italian/cacio-e-pepe.mdx +0 -39
- package/showcases/recipes/recipes/italian/tiramisu.mdx +0 -43
- package/showcases/recipes/recipes/mexican/chicken-tinga.mdx +0 -44
- package/showcases/recipes/recipes/mexican/guacamole.mdx +0 -39
- package/showcases/vinyl-collection/albums/bitches-brew.mdx +0 -36
- package/showcases/vinyl-collection/albums/i-put-a-spell-on-you.mdx +0 -35
- package/showcases/vinyl-collection/albums/in-rainbows.mdx +0 -35
- package/showcases/vinyl-collection/albums/kind-of-blue.mdx +0 -32
- package/showcases/vinyl-collection/albums/ok-computer.mdx +0 -37
- package/showcases/vinyl-collection/albums/wild-is-the-wind.mdx +0 -35
- package/showcases/vinyl-collection/artists/miles-davis.mdx +0 -27
- package/showcases/vinyl-collection/artists/nina-simone.mdx +0 -26
- package/showcases/vinyl-collection/artists/radiohead.mdx +0 -27
- package/showcases/vinyl-collection/models.ts +0 -73
- package/showcases/vinyl-collection/queries.ts +0 -87
- package/test/ast-query.test.ts +0 -128
- package/test/collection.test.ts +0 -99
- package/test/define-model.test.ts +0 -78
- package/test/document.test.ts +0 -225
- package/test/extract-sections.test.ts +0 -356
- package/test/fixtures/sdlc/epics/authentication.mdx +0 -42
- package/test/fixtures/sdlc/epics/searching-and-browsing.mdx +0 -21
- package/test/fixtures/sdlc/models.ts +0 -89
- package/test/fixtures/sdlc/stories/authentication/a-user-should-be-able-to-register.mdx +0 -20
- package/test/helpers.ts +0 -21
- package/test/model-instance.test.ts +0 -197
- package/test/query.test.ts +0 -167
- package/test/relationships.test.ts +0 -84
- package/test/section.test.ts +0 -99
- package/test/table-of-contents.test.ts +0 -91
- package/test/validator.test.ts +0 -62
- package/tsconfig.json +0 -18
- package/vitest.config.ts +0 -11
package/examples/sdlc-queries.ts
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Contentbase Example: Querying an SDLC Content Collection
|
|
3
|
-
*
|
|
4
|
-
* This script demonstrates loading a collection of Epics and Stories
|
|
5
|
-
* from markdown files and querying them using the Contentbase API.
|
|
6
|
-
*
|
|
7
|
-
* Run with: bun run examples/sdlc-queries.ts
|
|
8
|
-
*/
|
|
9
|
-
import path from "path";
|
|
10
|
-
import { fileURLToPath } from "url";
|
|
11
|
-
import { Collection, type InferModelInstance } from "../src/index";
|
|
12
|
-
import { Epic, Story, type StoryDef } from "../test/fixtures/sdlc/models";
|
|
13
|
-
|
|
14
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
-
const basePath = path.resolve(__dirname, "../test/fixtures/sdlc");
|
|
16
|
-
|
|
17
|
-
async function main() {
|
|
18
|
-
// 1. Create and load the collection
|
|
19
|
-
const collection = new Collection({
|
|
20
|
-
rootPath: basePath,
|
|
21
|
-
name: "sdlc",
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
collection.register(Epic);
|
|
25
|
-
collection.register(Story);
|
|
26
|
-
await collection.load();
|
|
27
|
-
|
|
28
|
-
console.log("Available documents:", collection.available);
|
|
29
|
-
|
|
30
|
-
// -------------------------------------------------------
|
|
31
|
-
// 2. Get a single model instance by path ID
|
|
32
|
-
// -------------------------------------------------------
|
|
33
|
-
const authEpic = collection.getModel("epics/authentication", Epic);
|
|
34
|
-
|
|
35
|
-
console.log("\n--- Epic: Authentication ---");
|
|
36
|
-
console.log("Title:", authEpic.title);
|
|
37
|
-
console.log("Slug:", authEpic.slug);
|
|
38
|
-
console.log("Status:", authEpic.meta.status);
|
|
39
|
-
console.log("Priority:", authEpic.meta.priority);
|
|
40
|
-
console.log("Is complete?", authEpic.computed.isComplete);
|
|
41
|
-
|
|
42
|
-
// -------------------------------------------------------
|
|
43
|
-
// 3. Query all epics
|
|
44
|
-
// -------------------------------------------------------
|
|
45
|
-
const allEpics = await collection.query(Epic).fetchAll();
|
|
46
|
-
|
|
47
|
-
console.log("\n--- All Epics ---");
|
|
48
|
-
for (const epic of allEpics) {
|
|
49
|
-
console.log(` ${epic.title} (${epic.meta.status})`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// -------------------------------------------------------
|
|
53
|
-
// 4. Filter with where clauses
|
|
54
|
-
// -------------------------------------------------------
|
|
55
|
-
const highPriority = await collection
|
|
56
|
-
.query(Epic)
|
|
57
|
-
.where("meta.priority", "high")
|
|
58
|
-
.fetchAll();
|
|
59
|
-
|
|
60
|
-
console.log("\n--- High Priority Epics ---");
|
|
61
|
-
for (const epic of highPriority) {
|
|
62
|
-
console.log(` ${epic.title} — priority: ${epic.meta.priority}`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// -------------------------------------------------------
|
|
66
|
-
// 5. Query helpers: first, last, count
|
|
67
|
-
// -------------------------------------------------------
|
|
68
|
-
const firstEpic = await collection.query(Epic).first();
|
|
69
|
-
const lastEpic = await collection.query(Epic).last();
|
|
70
|
-
const epicCount = await collection.query(Epic).count();
|
|
71
|
-
|
|
72
|
-
console.log("\n--- Query Helpers ---");
|
|
73
|
-
console.log("First epic:", firstEpic?.title);
|
|
74
|
-
console.log("Last epic:", lastEpic?.title);
|
|
75
|
-
console.log("Total epics:", epicCount);
|
|
76
|
-
|
|
77
|
-
// -------------------------------------------------------
|
|
78
|
-
// 6. Chained where clauses (AND logic)
|
|
79
|
-
// -------------------------------------------------------
|
|
80
|
-
const filtered = await collection
|
|
81
|
-
.query(Epic)
|
|
82
|
-
.where("meta.status", "created")
|
|
83
|
-
.whereExists("meta.priority")
|
|
84
|
-
.fetchAll();
|
|
85
|
-
|
|
86
|
-
console.log("\n--- Created Epics with Priority Set ---");
|
|
87
|
-
for (const epic of filtered) {
|
|
88
|
-
console.log(` ${epic.title} — ${epic.meta.priority}`);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// -------------------------------------------------------
|
|
92
|
-
// 7. HasMany relationships — Epic -> Stories
|
|
93
|
-
// -------------------------------------------------------
|
|
94
|
-
const stories = authEpic.relationships.stories.fetchAll();
|
|
95
|
-
|
|
96
|
-
console.log("\n--- Stories under Authentication Epic ---");
|
|
97
|
-
for (const story of stories) {
|
|
98
|
-
console.log(` ${story.title}`);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
console.log("First story:", authEpic.relationships.stories.first()?.title);
|
|
102
|
-
console.log("Last story:", authEpic.relationships.stories.last()?.title);
|
|
103
|
-
|
|
104
|
-
// -------------------------------------------------------
|
|
105
|
-
// 8. BelongsTo relationships — Story -> Epic
|
|
106
|
-
// -------------------------------------------------------
|
|
107
|
-
const registerStory: InferModelInstance<StoryDef> = collection.getModel(
|
|
108
|
-
"stories/authentication/a-user-should-be-able-to-register",
|
|
109
|
-
Story
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
const parentEpic = registerStory.relationships.epic.fetch();
|
|
113
|
-
|
|
114
|
-
console.log("\n--- Story -> Epic (belongsTo) ---");
|
|
115
|
-
console.log(`"${registerStory.title}" belongs to "${parentEpic.title}"`);
|
|
116
|
-
|
|
117
|
-
// -------------------------------------------------------
|
|
118
|
-
// 9. Sections — structured data extracted from headings
|
|
119
|
-
// -------------------------------------------------------
|
|
120
|
-
console.log("\n--- Sections: Acceptance Criteria ---");
|
|
121
|
-
for (const criterion of registerStory.sections.acceptanceCriteria) {
|
|
122
|
-
console.log(` • ${criterion}`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
console.log("\n--- Sections: Mockups ---");
|
|
126
|
-
for (const [label, url] of Object.entries(registerStory.sections.mockups)) {
|
|
127
|
-
console.log(` ${label}: ${url}`);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// -------------------------------------------------------
|
|
131
|
-
// 10. Validation
|
|
132
|
-
// -------------------------------------------------------
|
|
133
|
-
const result = await registerStory.validate();
|
|
134
|
-
|
|
135
|
-
console.log("\n--- Validation ---");
|
|
136
|
-
console.log("Valid?", result.valid);
|
|
137
|
-
console.log("Error count:", result.errors.length);
|
|
138
|
-
|
|
139
|
-
// -------------------------------------------------------
|
|
140
|
-
// 11. Serialization
|
|
141
|
-
// -------------------------------------------------------
|
|
142
|
-
const json = authEpic.toJSON({
|
|
143
|
-
computed: ["isComplete"],
|
|
144
|
-
related: ["stories"],
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
console.log("\n--- toJSON ---");
|
|
148
|
-
console.log(JSON.stringify(json, null, 2));
|
|
149
|
-
|
|
150
|
-
// -------------------------------------------------------
|
|
151
|
-
// 12. Working with the raw Document
|
|
152
|
-
// -------------------------------------------------------
|
|
153
|
-
const doc = authEpic.document;
|
|
154
|
-
|
|
155
|
-
console.log("\n--- Raw Document ---");
|
|
156
|
-
console.log("Headings:", doc.nodes.headings.length);
|
|
157
|
-
console.log("Links:", doc.nodes.links.length);
|
|
158
|
-
console.log("Lists:", doc.nodes.lists.length);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
main().catch(console.error);
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { scriptTitle, demo, kv, list } from "./lib/format";
|
|
2
|
-
import { createDemoCollection, Epic, Story } from "./lib/setup";
|
|
3
|
-
|
|
4
|
-
export async function main() {
|
|
5
|
-
scriptTitle("01", "Collection Setup");
|
|
6
|
-
const collection = await createDemoCollection();
|
|
7
|
-
|
|
8
|
-
await demo({
|
|
9
|
-
title: "Create and load a collection",
|
|
10
|
-
description: "Register models, load markdown files from disk.",
|
|
11
|
-
code: `const collection = new Collection({ rootPath: "./content" });
|
|
12
|
-
collection.register(Epic);
|
|
13
|
-
collection.register(Story);
|
|
14
|
-
await collection.load();`,
|
|
15
|
-
run: () => {
|
|
16
|
-
return [
|
|
17
|
-
kv("Documents loaded", collection.available.length),
|
|
18
|
-
kv("Models registered", collection.modelDefinitions.length),
|
|
19
|
-
].join("\n");
|
|
20
|
-
},
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
await demo({
|
|
24
|
-
title: "List available documents",
|
|
25
|
-
code: `collection.available`,
|
|
26
|
-
run: () => {
|
|
27
|
-
return list(collection.available);
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
await demo({
|
|
32
|
-
title: "Get a typed model instance",
|
|
33
|
-
code: `const epic = collection.getModel("epics/authentication", Epic);`,
|
|
34
|
-
run: () => {
|
|
35
|
-
const epic = collection.getModel("epics/authentication", Epic);
|
|
36
|
-
return [
|
|
37
|
-
kv("Title", epic.title),
|
|
38
|
-
kv("Slug", epic.slug),
|
|
39
|
-
kv("Status", epic.meta.status),
|
|
40
|
-
kv("Priority", epic.meta.priority),
|
|
41
|
-
].join("\n");
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (import.meta.main) main().catch(console.error);
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { scriptTitle, demo, kv, list } from "./lib/format";
|
|
2
|
-
import { createDemoCollection, Epic, Story } from "./lib/setup";
|
|
3
|
-
|
|
4
|
-
export async function main() {
|
|
5
|
-
scriptTitle("02", "Querying");
|
|
6
|
-
const collection = await createDemoCollection();
|
|
7
|
-
|
|
8
|
-
await demo({
|
|
9
|
-
title: "Fetch all instances of a model",
|
|
10
|
-
code: `const epics = await collection.query(Epic).fetchAll();`,
|
|
11
|
-
run: async () => {
|
|
12
|
-
const epics = await collection.query(Epic).fetchAll();
|
|
13
|
-
return list(epics.map((e) => `${e.title} (${e.meta.status})`));
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
await demo({
|
|
18
|
-
title: "Filter with where()",
|
|
19
|
-
code: `const high = await collection.query(Epic)
|
|
20
|
-
.where("meta.priority", "high")
|
|
21
|
-
.fetchAll();`,
|
|
22
|
-
run: async () => {
|
|
23
|
-
const high = await collection
|
|
24
|
-
.query(Epic)
|
|
25
|
-
.where("meta.priority", "high")
|
|
26
|
-
.fetchAll();
|
|
27
|
-
return list(high.map((e) => `${e.title} — priority: ${e.meta.priority}`));
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
await demo({
|
|
32
|
-
title: "Chained where + whereExists",
|
|
33
|
-
code: `const filtered = await collection.query(Epic)
|
|
34
|
-
.where("meta.status", "created")
|
|
35
|
-
.whereExists("meta.priority")
|
|
36
|
-
.fetchAll();`,
|
|
37
|
-
run: async () => {
|
|
38
|
-
const filtered = await collection
|
|
39
|
-
.query(Epic)
|
|
40
|
-
.where("meta.status", "created")
|
|
41
|
-
.whereExists("meta.priority")
|
|
42
|
-
.fetchAll();
|
|
43
|
-
return list(
|
|
44
|
-
filtered.map((e) => `${e.title} — ${e.meta.status}, ${e.meta.priority}`)
|
|
45
|
-
);
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
await demo({
|
|
50
|
-
title: "Query helpers: first, last, count",
|
|
51
|
-
code: `const first = await collection.query(Epic).first();
|
|
52
|
-
const last = await collection.query(Epic).last();
|
|
53
|
-
const count = await collection.query(Epic).count();`,
|
|
54
|
-
run: async () => {
|
|
55
|
-
const first = await collection.query(Epic).first();
|
|
56
|
-
const last = await collection.query(Epic).last();
|
|
57
|
-
const count = await collection.query(Epic).count();
|
|
58
|
-
return [
|
|
59
|
-
kv("First", first?.title),
|
|
60
|
-
kv("Last", last?.title),
|
|
61
|
-
kv("Count", count),
|
|
62
|
-
].join("\n");
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (import.meta.main) main().catch(console.error);
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { scriptTitle, demo, kv, list } from "./lib/format";
|
|
2
|
-
import { createDemoCollection, Story } from "./lib/setup";
|
|
3
|
-
|
|
4
|
-
export async function main() {
|
|
5
|
-
scriptTitle("03", "Sections");
|
|
6
|
-
const collection = await createDemoCollection();
|
|
7
|
-
const story = collection.getModel(
|
|
8
|
-
"stories/authentication/a-user-should-be-able-to-register",
|
|
9
|
-
Story
|
|
10
|
-
);
|
|
11
|
-
|
|
12
|
-
await demo({
|
|
13
|
-
title: "Typed section access: acceptanceCriteria",
|
|
14
|
-
description: "Sections are defined in the model with section() helpers and extracted from headings.",
|
|
15
|
-
code: `const story = collection.getModel(
|
|
16
|
-
"stories/authentication/a-user-should-be-able-to-register",
|
|
17
|
-
Story
|
|
18
|
-
);
|
|
19
|
-
story.sections.acceptanceCriteria;`,
|
|
20
|
-
run: () => {
|
|
21
|
-
return list(story.sections.acceptanceCriteria);
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
await demo({
|
|
26
|
-
title: "Typed section access: mockups",
|
|
27
|
-
description: "The mockups section extracts links as a key-value record.",
|
|
28
|
-
code: `story.sections.mockups;`,
|
|
29
|
-
run: () => {
|
|
30
|
-
const entries = Object.entries(story.sections.mockups);
|
|
31
|
-
return entries.map(([label, url]) => kv(label, url)).join("\n");
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (import.meta.main) main().catch(console.error);
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { scriptTitle, demo, kv, list } from "./lib/format";
|
|
2
|
-
import { createDemoCollection, Epic, Story } from "./lib/setup";
|
|
3
|
-
|
|
4
|
-
export async function main() {
|
|
5
|
-
scriptTitle("04", "Relationships");
|
|
6
|
-
const collection = await createDemoCollection();
|
|
7
|
-
const epic = collection.getModel("epics/authentication", Epic);
|
|
8
|
-
|
|
9
|
-
await demo({
|
|
10
|
-
title: "hasMany: Epic → Stories",
|
|
11
|
-
description: "Navigate from an epic to its child stories.",
|
|
12
|
-
code: `const epic = collection.getModel("epics/authentication", Epic);
|
|
13
|
-
const stories = epic.relationships.stories.fetchAll();`,
|
|
14
|
-
run: () => {
|
|
15
|
-
const stories = epic.relationships.stories.fetchAll();
|
|
16
|
-
return list(stories.map((s: any) => s.title));
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
await demo({
|
|
21
|
-
title: "hasMany helpers: first() and last()",
|
|
22
|
-
code: `epic.relationships.stories.first()?.title;
|
|
23
|
-
epic.relationships.stories.last()?.title;`,
|
|
24
|
-
run: () => {
|
|
25
|
-
return [
|
|
26
|
-
kv("First story", epic.relationships.stories.first()?.title),
|
|
27
|
-
kv("Last story", epic.relationships.stories.last()?.title),
|
|
28
|
-
].join("\n");
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
await demo({
|
|
33
|
-
title: "belongsTo: Story → Epic",
|
|
34
|
-
description: "Navigate from a story back to its parent epic.",
|
|
35
|
-
code: `const story = collection.getModel(
|
|
36
|
-
"stories/authentication/a-user-should-be-able-to-register",
|
|
37
|
-
Story
|
|
38
|
-
);
|
|
39
|
-
const parent = story.relationships.epic.fetch();`,
|
|
40
|
-
run: () => {
|
|
41
|
-
const story = collection.getModel(
|
|
42
|
-
"stories/authentication/a-user-should-be-able-to-register",
|
|
43
|
-
Story
|
|
44
|
-
);
|
|
45
|
-
const parent = story.relationships.epic.fetch();
|
|
46
|
-
return [
|
|
47
|
-
kv("Story", story.title),
|
|
48
|
-
kv("Parent epic", parent.title),
|
|
49
|
-
].join("\n");
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (import.meta.main) main().catch(console.error);
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import { scriptTitle, demo, kv } from "./lib/format";
|
|
3
|
-
import { createDemoCollection, FIXTURES_PATH } from "./lib/setup";
|
|
4
|
-
import { parse } from "../../src/index";
|
|
5
|
-
|
|
6
|
-
export async function main() {
|
|
7
|
-
scriptTitle("05", "Document API");
|
|
8
|
-
const collection = await createDemoCollection();
|
|
9
|
-
const filePath = path.join(FIXTURES_PATH, "epics/authentication.mdx");
|
|
10
|
-
|
|
11
|
-
await demo({
|
|
12
|
-
title: "parse() a standalone file",
|
|
13
|
-
description: "Parse any markdown file without needing a collection.",
|
|
14
|
-
code: `import { parse } from "contentbase";
|
|
15
|
-
const doc = await parse("./epics/authentication.mdx");`,
|
|
16
|
-
run: async () => {
|
|
17
|
-
const doc = await parse(filePath);
|
|
18
|
-
return [
|
|
19
|
-
kv("Title", doc.title),
|
|
20
|
-
kv("Headings", doc.nodes.headings.length),
|
|
21
|
-
kv("Links", doc.nodes.links.length),
|
|
22
|
-
kv("Lists", doc.nodes.lists.length),
|
|
23
|
-
].join("\n");
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
await demo({
|
|
28
|
-
title: "Document outline",
|
|
29
|
-
description: "Generate an indented heading outline of any document.",
|
|
30
|
-
code: `const doc = collection.document("epics/authentication");
|
|
31
|
-
doc.toOutline();`,
|
|
32
|
-
run: () => {
|
|
33
|
-
const doc = collection.document("epics/authentication");
|
|
34
|
-
return doc.toOutline();
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
await demo({
|
|
39
|
-
title: "Immutable section removal",
|
|
40
|
-
description: "removeSection() returns a new document — the original is unchanged.",
|
|
41
|
-
code: `const original = collection.document("epics/authentication");
|
|
42
|
-
const trimmed = original.removeSection("Stories");`,
|
|
43
|
-
run: () => {
|
|
44
|
-
const original = collection.document("epics/authentication");
|
|
45
|
-
const trimmed = original.removeSection("Stories");
|
|
46
|
-
return [
|
|
47
|
-
kv("Original headings", original.nodes.headings.length),
|
|
48
|
-
kv("After removeSection", trimmed.nodes.headings.length),
|
|
49
|
-
].join("\n");
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (import.meta.main) main().catch(console.error);
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { scriptTitle, demo } from "./lib/format";
|
|
2
|
-
import { createDemoCollection, Epic } from "./lib/setup";
|
|
3
|
-
import { extractSections } from "../../src/index";
|
|
4
|
-
|
|
5
|
-
export async function main() {
|
|
6
|
-
scriptTitle("06", "Extract Sections");
|
|
7
|
-
const collection = await createDemoCollection();
|
|
8
|
-
const allEpics = await collection.query(Epic).fetchAll();
|
|
9
|
-
|
|
10
|
-
await demo({
|
|
11
|
-
title: "Grouped mode (default)",
|
|
12
|
-
description: "Combine sections from multiple documents, grouped by source.",
|
|
13
|
-
code: `const combined = extractSections(
|
|
14
|
-
allEpics.map((e) => ({
|
|
15
|
-
source: e.document,
|
|
16
|
-
sections: "Stories",
|
|
17
|
-
})),
|
|
18
|
-
{ title: "All Stories", mode: "grouped" }
|
|
19
|
-
);`,
|
|
20
|
-
run: () => {
|
|
21
|
-
const combined = extractSections(
|
|
22
|
-
allEpics.map((e: any) => ({
|
|
23
|
-
source: e.document,
|
|
24
|
-
sections: "Stories",
|
|
25
|
-
})),
|
|
26
|
-
{ title: "All Stories", mode: "grouped" }
|
|
27
|
-
);
|
|
28
|
-
return combined.content;
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
await demo({
|
|
33
|
-
title: "Flat mode",
|
|
34
|
-
description: "Combine sections without source grouping.",
|
|
35
|
-
code: `const flat = extractSections(
|
|
36
|
-
allEpics.map((e) => ({
|
|
37
|
-
source: e.document,
|
|
38
|
-
sections: "Stories",
|
|
39
|
-
})),
|
|
40
|
-
{ title: "All Stories", mode: "flat" }
|
|
41
|
-
);`,
|
|
42
|
-
run: () => {
|
|
43
|
-
const flat = extractSections(
|
|
44
|
-
allEpics.map((e: any) => ({
|
|
45
|
-
source: e.document,
|
|
46
|
-
sections: "Stories",
|
|
47
|
-
})),
|
|
48
|
-
{ title: "All Stories", mode: "flat" }
|
|
49
|
-
);
|
|
50
|
-
return flat.content;
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (import.meta.main) main().catch(console.error);
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { scriptTitle, demo, kv } from "./lib/format";
|
|
2
|
-
import { createDemoCollection, Story } from "./lib/setup";
|
|
3
|
-
import { validateDocument } from "../../src/index";
|
|
4
|
-
|
|
5
|
-
export async function main() {
|
|
6
|
-
scriptTitle("07", "Validation");
|
|
7
|
-
const collection = await createDemoCollection();
|
|
8
|
-
const story = collection.getModel(
|
|
9
|
-
"stories/authentication/a-user-should-be-able-to-register",
|
|
10
|
-
Story
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
await demo({
|
|
14
|
-
title: "Instance validation",
|
|
15
|
-
description: "Validate a model instance against its Zod schemas.",
|
|
16
|
-
code: `const story = collection.getModel("stories/.../register", Story);
|
|
17
|
-
const result = await story.validate();`,
|
|
18
|
-
run: async () => {
|
|
19
|
-
const result = await story.validate();
|
|
20
|
-
return [
|
|
21
|
-
kv("Valid", result.valid),
|
|
22
|
-
kv("Errors", result.errors.length),
|
|
23
|
-
].join("\n");
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
await demo({
|
|
28
|
-
title: "Standalone validateDocument()",
|
|
29
|
-
description: "Validate any document against a model definition without creating an instance.",
|
|
30
|
-
code: `import { validateDocument } from "contentbase";
|
|
31
|
-
const doc = collection.document("stories/.../register");
|
|
32
|
-
const result = validateDocument(doc, Story);`,
|
|
33
|
-
run: () => {
|
|
34
|
-
const doc = collection.document(
|
|
35
|
-
"stories/authentication/a-user-should-be-able-to-register"
|
|
36
|
-
);
|
|
37
|
-
const result = validateDocument(doc, Story);
|
|
38
|
-
return [
|
|
39
|
-
kv("Valid", result.valid),
|
|
40
|
-
kv("Errors", result.errors.length),
|
|
41
|
-
].join("\n");
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (import.meta.main) main().catch(console.error);
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { scriptTitle, demo, kv } from "./lib/format";
|
|
2
|
-
import { createDemoCollection, Epic } from "./lib/setup";
|
|
3
|
-
|
|
4
|
-
export async function main() {
|
|
5
|
-
scriptTitle("08", "Serialization");
|
|
6
|
-
const collection = await createDemoCollection();
|
|
7
|
-
const epic = collection.getModel("epics/authentication", Epic);
|
|
8
|
-
|
|
9
|
-
await demo({
|
|
10
|
-
title: "toJSON() — basic",
|
|
11
|
-
code: `epic.toJSON();`,
|
|
12
|
-
run: () => epic.toJSON(),
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
await demo({
|
|
16
|
-
title: "toJSON() — with computed and relationships",
|
|
17
|
-
code: `epic.toJSON({
|
|
18
|
-
computed: ["isComplete"],
|
|
19
|
-
related: ["stories"],
|
|
20
|
-
});`,
|
|
21
|
-
run: () =>
|
|
22
|
-
epic.toJSON({
|
|
23
|
-
computed: ["isComplete"],
|
|
24
|
-
related: ["stories"],
|
|
25
|
-
}),
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
await demo({
|
|
29
|
-
title: "Table of contents",
|
|
30
|
-
description: "Generate a markdown TOC with links for the whole collection.",
|
|
31
|
-
code: `collection.tableOfContents({ title: "SDLC Docs" });`,
|
|
32
|
-
run: () => collection.tableOfContents({ title: "SDLC Docs" }),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
await demo({
|
|
36
|
-
title: "Collection export",
|
|
37
|
-
description: "Export all model data as a JSON snapshot.",
|
|
38
|
-
code: `const data = await collection.export();
|
|
39
|
-
Object.keys(data);`,
|
|
40
|
-
run: async () => {
|
|
41
|
-
const data = await collection.export();
|
|
42
|
-
return [
|
|
43
|
-
kv("Keys", Object.keys(data).join(", ")),
|
|
44
|
-
kv("Models", Object.keys(data.modelData as object).join(", ")),
|
|
45
|
-
kv("Item count", (data.itemIds as string[]).length),
|
|
46
|
-
].join("\n");
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (import.meta.main) main().catch(console.error);
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
// ANSI escape codes
|
|
2
|
-
const c = {
|
|
3
|
-
reset: "\x1b[0m",
|
|
4
|
-
bold: "\x1b[1m",
|
|
5
|
-
dim: "\x1b[2m",
|
|
6
|
-
cyan: "\x1b[36m",
|
|
7
|
-
yellow: "\x1b[33m",
|
|
8
|
-
green: "\x1b[32m",
|
|
9
|
-
gray: "\x1b[90m",
|
|
10
|
-
magenta: "\x1b[35m",
|
|
11
|
-
white: "\x1b[37m",
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export function scriptTitle(num: string, title: string) {
|
|
15
|
-
const text = ` ${num}. ${title} `;
|
|
16
|
-
const line = "═".repeat(text.length + 2);
|
|
17
|
-
console.log();
|
|
18
|
-
console.log(`${c.cyan}${c.bold}╔${line}╗${c.reset}`);
|
|
19
|
-
console.log(`${c.cyan}${c.bold}║ ${text} ║${c.reset}`);
|
|
20
|
-
console.log(`${c.cyan}${c.bold}╚${line}╝${c.reset}`);
|
|
21
|
-
console.log();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface DemoBlock {
|
|
25
|
-
title: string;
|
|
26
|
-
description?: string;
|
|
27
|
-
code: string;
|
|
28
|
-
run: () => unknown | Promise<unknown>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function demo(block: DemoBlock) {
|
|
32
|
-
// Title
|
|
33
|
-
console.log(` ${c.yellow}${c.bold}▸ ${block.title}${c.reset}`);
|
|
34
|
-
|
|
35
|
-
// Description
|
|
36
|
-
if (block.description) {
|
|
37
|
-
console.log(` ${c.dim}${block.description}${c.reset}`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Code box
|
|
41
|
-
const codeLines = block.code.split("\n");
|
|
42
|
-
const maxLen = Math.max(...codeLines.map((l) => l.length));
|
|
43
|
-
const border = "─".repeat(maxLen + 2);
|
|
44
|
-
|
|
45
|
-
console.log(` ${c.dim}┌${border}┐${c.reset}`);
|
|
46
|
-
for (const line of codeLines) {
|
|
47
|
-
console.log(` ${c.dim}│${c.reset} ${line.padEnd(maxLen)} ${c.dim}│${c.reset}`);
|
|
48
|
-
}
|
|
49
|
-
console.log(` ${c.dim}└${border}┘${c.reset}`);
|
|
50
|
-
|
|
51
|
-
// Run and display output
|
|
52
|
-
const result = await block.run();
|
|
53
|
-
|
|
54
|
-
if (result != null) {
|
|
55
|
-
let output: string;
|
|
56
|
-
if (typeof result === "string") {
|
|
57
|
-
output = result;
|
|
58
|
-
} else {
|
|
59
|
-
output = JSON.stringify(result, null, 2);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const outLines = output.split("\n");
|
|
63
|
-
const outMaxLen = Math.max(...outLines.map((l) => l.length));
|
|
64
|
-
const outBorder = "─".repeat(outMaxLen + 2);
|
|
65
|
-
|
|
66
|
-
console.log(` ${c.green}┌${outBorder}┐${c.reset}`);
|
|
67
|
-
for (const line of outLines) {
|
|
68
|
-
console.log(` ${c.green}│${c.reset} ${c.green}${line.padEnd(outMaxLen)}${c.reset} ${c.green}│${c.reset}`);
|
|
69
|
-
}
|
|
70
|
-
console.log(` ${c.green}└${outBorder}┘${c.reset}`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
console.log();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function kv(key: string, value: unknown): string {
|
|
77
|
-
return `${c.magenta}${key}:${c.reset} ${value}`;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function list(items: string[]): string {
|
|
81
|
-
return items.map((item) => ` • ${item}`).join("\n");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function heading(text: string) {
|
|
85
|
-
console.log(` ${c.cyan}${c.bold}${text}${c.reset}`);
|
|
86
|
-
console.log();
|
|
87
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import { Collection } from "../../../src/collection";
|
|
3
|
-
import { Epic, Story } from "../../../test/fixtures/sdlc/models";
|
|
4
|
-
|
|
5
|
-
const dir = import.meta.dirname ?? new URL(".", import.meta.url).pathname;
|
|
6
|
-
|
|
7
|
-
const FIXTURES_PATH = path.resolve(dir, "../../../test/fixtures/sdlc");
|
|
8
|
-
|
|
9
|
-
export async function createDemoCollection(): Promise<Collection> {
|
|
10
|
-
const collection = new Collection({
|
|
11
|
-
rootPath: FIXTURES_PATH,
|
|
12
|
-
name: "sdlc",
|
|
13
|
-
});
|
|
14
|
-
collection.register(Epic);
|
|
15
|
-
collection.register(Story);
|
|
16
|
-
await collection.load();
|
|
17
|
-
return collection;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export { Epic, Story };
|
|
21
|
-
export { FIXTURES_PATH };
|