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
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import { Collection } from "../src/collection";
|
|
3
|
-
import { createModelInstance } from "../src/model-instance";
|
|
4
|
-
import { createTestCollection } from "./helpers";
|
|
5
|
-
import { Epic, Story } from "./fixtures/sdlc/models";
|
|
6
|
-
|
|
7
|
-
describe("createModelInstance", () => {
|
|
8
|
-
let collection: Collection;
|
|
9
|
-
|
|
10
|
-
beforeEach(async () => {
|
|
11
|
-
collection = await createTestCollection();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
describe("core properties", () => {
|
|
15
|
-
it("has correct id", () => {
|
|
16
|
-
const doc = collection.document("epics/authentication");
|
|
17
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
18
|
-
expect(instance.id).toBe("epics/authentication");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("has correct title", () => {
|
|
22
|
-
const doc = collection.document("epics/authentication");
|
|
23
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
24
|
-
expect(instance.title).toBe("Authentication");
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("has correct slug", () => {
|
|
28
|
-
const doc = collection.document("epics/authentication");
|
|
29
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
30
|
-
expect(instance.slug).toBe("authentication");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("references the original document", () => {
|
|
34
|
-
const doc = collection.document("epics/authentication");
|
|
35
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
36
|
-
expect(instance.document).toBe(doc);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("references the collection", () => {
|
|
40
|
-
const doc = collection.document("epics/authentication");
|
|
41
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
42
|
-
expect(instance.collection).toBe(collection);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe("meta", () => {
|
|
47
|
-
it("returns Zod-parsed frontmatter", () => {
|
|
48
|
-
const doc = collection.document("epics/authentication");
|
|
49
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
50
|
-
expect(instance.meta.priority).toBe("high");
|
|
51
|
-
expect(instance.meta.status).toBe("created");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("applies defaults where fields are missing", () => {
|
|
55
|
-
const doc = collection.document("epics/searching-and-browsing");
|
|
56
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
57
|
-
expect(instance.meta.status).toBe("created");
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe("sections", () => {
|
|
62
|
-
it("lazily extracts section data", () => {
|
|
63
|
-
const doc = collection.document(
|
|
64
|
-
"stories/authentication/a-user-should-be-able-to-register"
|
|
65
|
-
);
|
|
66
|
-
const instance = createModelInstance(doc, Story, collection);
|
|
67
|
-
const criteria = instance.sections.acceptanceCriteria;
|
|
68
|
-
expect(Array.isArray(criteria)).toBe(true);
|
|
69
|
-
expect(criteria.length).toBe(4);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("extracts mockups as record", () => {
|
|
73
|
-
const doc = collection.document(
|
|
74
|
-
"stories/authentication/a-user-should-be-able-to-register"
|
|
75
|
-
);
|
|
76
|
-
const instance = createModelInstance(doc, Story, collection);
|
|
77
|
-
const mockups = instance.sections.mockups;
|
|
78
|
-
expect(typeof mockups).toBe("object");
|
|
79
|
-
expect(Object.keys(mockups).length).toBeGreaterThan(0);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe("computed", () => {
|
|
84
|
-
it("evaluates computed properties", () => {
|
|
85
|
-
const doc = collection.document("epics/authentication");
|
|
86
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
87
|
-
expect(instance.computed.isComplete).toBe(false);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
describe("relationships", () => {
|
|
92
|
-
it("hasMany fetchAll returns related instances", () => {
|
|
93
|
-
const doc = collection.document("epics/authentication");
|
|
94
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
95
|
-
const stories = instance.relationships.stories.fetchAll();
|
|
96
|
-
expect(stories.length).toBe(2);
|
|
97
|
-
expect(stories[0].title).toBeDefined();
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it("hasMany first returns first child", () => {
|
|
101
|
-
const doc = collection.document("epics/authentication");
|
|
102
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
103
|
-
const first = instance.relationships.stories.first();
|
|
104
|
-
expect(first).toBeDefined();
|
|
105
|
-
expect(first!.title).toContain("register");
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("hasMany last returns last child", () => {
|
|
109
|
-
const doc = collection.document("epics/authentication");
|
|
110
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
111
|
-
const last = instance.relationships.stories.last();
|
|
112
|
-
expect(last).toBeDefined();
|
|
113
|
-
expect(last!.title).toContain("login");
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("belongsTo fetch returns parent", () => {
|
|
117
|
-
const doc = collection.document(
|
|
118
|
-
"stories/authentication/a-user-should-be-able-to-register"
|
|
119
|
-
);
|
|
120
|
-
const instance = createModelInstance(doc, Story, collection);
|
|
121
|
-
const epic = instance.relationships.epic.fetch();
|
|
122
|
-
expect(epic.title).toBe("Authentication");
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
describe("validation", () => {
|
|
127
|
-
it("returns valid for good data", async () => {
|
|
128
|
-
const doc = collection.document("epics/authentication");
|
|
129
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
130
|
-
const result = await instance.validate();
|
|
131
|
-
expect(result.valid).toBe(true);
|
|
132
|
-
expect(result.errors.length).toBe(0);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it("returns errors for bad meta", async () => {
|
|
136
|
-
const doc = collection.createDocument({
|
|
137
|
-
id: "test/bad",
|
|
138
|
-
content: "# Bad Doc\n",
|
|
139
|
-
meta: { status: "INVALID_STATUS" },
|
|
140
|
-
});
|
|
141
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
142
|
-
const result = await instance.validate();
|
|
143
|
-
expect(result.valid).toBe(false);
|
|
144
|
-
expect(result.errors.length).toBeGreaterThan(0);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("populates errors map", async () => {
|
|
148
|
-
const doc = collection.createDocument({
|
|
149
|
-
id: "test/bad",
|
|
150
|
-
content: "# Bad Doc\n",
|
|
151
|
-
meta: { status: "INVALID_STATUS" },
|
|
152
|
-
});
|
|
153
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
154
|
-
await instance.validate();
|
|
155
|
-
expect(instance.hasErrors).toBe(true);
|
|
156
|
-
expect(instance.errors.size).toBeGreaterThan(0);
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
describe("toJSON", () => {
|
|
161
|
-
it("returns id, title, meta by default", () => {
|
|
162
|
-
const doc = collection.document("epics/authentication");
|
|
163
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
164
|
-
const json = instance.toJSON();
|
|
165
|
-
expect(json.id).toBe("epics/authentication");
|
|
166
|
-
expect(json.title).toBe("Authentication");
|
|
167
|
-
expect(json.meta).toBeDefined();
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it("includes requested sections", () => {
|
|
171
|
-
const doc = collection.document(
|
|
172
|
-
"stories/authentication/a-user-should-be-able-to-register"
|
|
173
|
-
);
|
|
174
|
-
const instance = createModelInstance(doc, Story, collection);
|
|
175
|
-
const json = instance.toJSON({
|
|
176
|
-
sections: ["acceptanceCriteria"],
|
|
177
|
-
});
|
|
178
|
-
expect(json.acceptanceCriteria).toBeDefined();
|
|
179
|
-
expect(Array.isArray(json.acceptanceCriteria)).toBe(true);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it("includes requested computed values", () => {
|
|
183
|
-
const doc = collection.document("epics/authentication");
|
|
184
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
185
|
-
const json = instance.toJSON({ computed: ["isComplete"] });
|
|
186
|
-
expect(json.isComplete).toBe(false);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it("includes requested relationships", () => {
|
|
190
|
-
const doc = collection.document("epics/authentication");
|
|
191
|
-
const instance = createModelInstance(doc, Epic, collection);
|
|
192
|
-
const json = instance.toJSON({ related: ["stories"] });
|
|
193
|
-
expect(json.stories).toBeDefined();
|
|
194
|
-
expect(Array.isArray(json.stories)).toBe(true);
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
});
|
package/test/query.test.ts
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import { QueryBuilder } from "../src/query/query-builder";
|
|
3
|
-
import { operators } from "../src/query/operators";
|
|
4
|
-
import { Collection } from "../src/collection";
|
|
5
|
-
import { createTestCollection } from "./helpers";
|
|
6
|
-
import { Epic } from "./fixtures/sdlc/models";
|
|
7
|
-
|
|
8
|
-
describe("QueryBuilder", () => {
|
|
9
|
-
it("builds eq conditions with two args", () => {
|
|
10
|
-
const qb = new QueryBuilder();
|
|
11
|
-
qb.where("meta.status", "active");
|
|
12
|
-
expect(qb.conditions).toEqual([
|
|
13
|
-
{ path: "meta.status", operator: "eq", value: "active" },
|
|
14
|
-
]);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("builds conditions with explicit operator", () => {
|
|
18
|
-
const qb = new QueryBuilder();
|
|
19
|
-
qb.where("meta.count", "gt", 5);
|
|
20
|
-
expect(qb.conditions[0].operator).toBe("gt");
|
|
21
|
-
expect(qb.conditions[0].value).toBe(5);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("builds conditions from object shorthand", () => {
|
|
25
|
-
const qb = new QueryBuilder();
|
|
26
|
-
qb.where({ "meta.status": "active", "meta.type": "post" });
|
|
27
|
-
expect(qb.conditions.length).toBe(2);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("whereIn adds in condition", () => {
|
|
31
|
-
const qb = new QueryBuilder();
|
|
32
|
-
qb.whereIn("meta.tags", ["a", "b"]);
|
|
33
|
-
expect(qb.conditions[0].operator).toBe("in");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("whereNotIn adds notIn condition", () => {
|
|
37
|
-
const qb = new QueryBuilder();
|
|
38
|
-
qb.whereNotIn("meta.status", ["archived"]);
|
|
39
|
-
expect(qb.conditions[0].operator).toBe("notIn");
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("supports all chainable methods", () => {
|
|
43
|
-
const qb = new QueryBuilder();
|
|
44
|
-
const result = qb
|
|
45
|
-
.where("a", "1")
|
|
46
|
-
.whereGt("b", 2)
|
|
47
|
-
.whereLt("c", 3)
|
|
48
|
-
.whereGte("d", 4)
|
|
49
|
-
.whereLte("e", 5)
|
|
50
|
-
.whereContains("f", "hello")
|
|
51
|
-
.whereStartsWith("g", "pre")
|
|
52
|
-
.whereEndsWith("h", "suf")
|
|
53
|
-
.whereRegex("i", /test/)
|
|
54
|
-
.whereExists("j")
|
|
55
|
-
.whereNotExists("k");
|
|
56
|
-
|
|
57
|
-
expect(result).toBe(qb); // chaining returns this
|
|
58
|
-
expect(qb.conditions.length).toBe(11);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("where with three args returns this (bug fix)", () => {
|
|
62
|
-
const qb = new QueryBuilder();
|
|
63
|
-
const result = qb.where("path", "neq", "value");
|
|
64
|
-
expect(result).toBe(qb);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe("operators", () => {
|
|
69
|
-
it("eq handles primitive equality", () => {
|
|
70
|
-
expect(operators.eq("a", "a")).toBe(true);
|
|
71
|
-
expect(operators.eq("a", "b")).toBe(false);
|
|
72
|
-
expect(operators.eq(1, 1)).toBe(true);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("eq handles deep equality", () => {
|
|
76
|
-
expect(operators.eq({ a: 1 }, { a: 1 })).toBe(true);
|
|
77
|
-
expect(operators.eq({ a: 1 }, { a: 2 })).toBe(false);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("neq is negation of eq", () => {
|
|
81
|
-
expect(operators.neq("a", "b")).toBe(true);
|
|
82
|
-
expect(operators.neq("a", "a")).toBe(false);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("in checks membership", () => {
|
|
86
|
-
expect(operators.in("a", ["a", "b"])).toBe(true);
|
|
87
|
-
expect(operators.in("c", ["a", "b"])).toBe(false);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("notIn checks non-membership", () => {
|
|
91
|
-
expect(operators.notIn("c", ["a", "b"])).toBe(true);
|
|
92
|
-
expect(operators.notIn("a", ["a", "b"])).toBe(false);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("gt/lt/gte/lte compare values", () => {
|
|
96
|
-
expect(operators.gt(5, 3)).toBe(true);
|
|
97
|
-
expect(operators.lt(3, 5)).toBe(true);
|
|
98
|
-
expect(operators.gte(5, 5)).toBe(true);
|
|
99
|
-
expect(operators.lte(5, 5)).toBe(true);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("contains checks string inclusion", () => {
|
|
103
|
-
expect(operators.contains("hello world", "world")).toBe(true);
|
|
104
|
-
expect(operators.contains("hello", "world")).toBe(false);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("startsWith/endsWith check string prefixes/suffixes", () => {
|
|
108
|
-
expect(operators.startsWith("hello", "hel")).toBe(true);
|
|
109
|
-
expect(operators.endsWith("hello", "llo")).toBe(true);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it("regex tests patterns", () => {
|
|
113
|
-
expect(operators.regex("hello123", /\d+/)).toBe(true);
|
|
114
|
-
expect(operators.regex("hello123", "\\d+")).toBe(true);
|
|
115
|
-
expect(operators.regex("hello", /\d+/)).toBe(false);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("exists checks for defined values", () => {
|
|
119
|
-
expect(operators.exists("value", true)).toBe(true);
|
|
120
|
-
expect(operators.exists(null, true)).toBe(false);
|
|
121
|
-
expect(operators.exists(undefined, true)).toBe(false);
|
|
122
|
-
expect(operators.exists(null, false)).toBe(true);
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
describe("CollectionQuery", () => {
|
|
127
|
-
let collection: Collection;
|
|
128
|
-
|
|
129
|
-
beforeEach(async () => {
|
|
130
|
-
collection = await createTestCollection();
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("fetchAll returns matching model instances", async () => {
|
|
134
|
-
const epics = await collection.query(Epic).fetchAll();
|
|
135
|
-
expect(epics.length).toBe(2);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it("where filters results", async () => {
|
|
139
|
-
const epics = await collection
|
|
140
|
-
.query(Epic)
|
|
141
|
-
.where("meta.priority", "high")
|
|
142
|
-
.fetchAll();
|
|
143
|
-
expect(epics.length).toBe(1);
|
|
144
|
-
expect(epics[0].meta.priority).toBe("high");
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("first returns first result", async () => {
|
|
148
|
-
const first = await collection.query(Epic).first();
|
|
149
|
-
expect(first).toBeDefined();
|
|
150
|
-
expect(first!.title).toBeDefined();
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("last returns last result", async () => {
|
|
154
|
-
const last = await collection.query(Epic).last();
|
|
155
|
-
expect(last).toBeDefined();
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it("count returns correct count", async () => {
|
|
159
|
-
const count = await collection.query(Epic).count();
|
|
160
|
-
expect(count).toBe(2);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it("empty conditions returns all of model type", async () => {
|
|
164
|
-
const all = await collection.query(Epic).fetchAll();
|
|
165
|
-
expect(all.length).toBe(2);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import { Collection } from "../src/collection";
|
|
3
|
-
import { createModelInstance } from "../src/model-instance";
|
|
4
|
-
import { createTestCollection } from "./helpers";
|
|
5
|
-
import { Epic, Story } from "./fixtures/sdlc/models";
|
|
6
|
-
|
|
7
|
-
describe("HasManyRelationship", () => {
|
|
8
|
-
let collection: Collection;
|
|
9
|
-
|
|
10
|
-
beforeEach(async () => {
|
|
11
|
-
collection = await createTestCollection();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("extracts child headings from parent section", () => {
|
|
15
|
-
const epic = collection.getModel("epics/authentication", Epic);
|
|
16
|
-
const stories = epic.relationships.stories.fetchAll();
|
|
17
|
-
expect(stories.length).toBe(2);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("child instances have correct titles", () => {
|
|
21
|
-
const epic = collection.getModel("epics/authentication", Epic);
|
|
22
|
-
const stories = epic.relationships.stories.fetchAll();
|
|
23
|
-
expect(stories[0].title).toContain("register");
|
|
24
|
-
expect(stories[1].title).toContain("login");
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("first returns first child", () => {
|
|
28
|
-
const epic = collection.getModel("epics/authentication", Epic);
|
|
29
|
-
const first = epic.relationships.stories.first();
|
|
30
|
-
expect(first).toBeDefined();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("last returns last child", () => {
|
|
34
|
-
const epic = collection.getModel("epics/authentication", Epic);
|
|
35
|
-
const last = epic.relationships.stories.last();
|
|
36
|
-
expect(last).toBeDefined();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("works with epic that has multiple stories", () => {
|
|
40
|
-
const epic = collection.getModel(
|
|
41
|
-
"epics/searching-and-browsing",
|
|
42
|
-
Epic
|
|
43
|
-
);
|
|
44
|
-
const stories = epic.relationships.stories.fetchAll();
|
|
45
|
-
expect(stories.length).toBe(3);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("computes IDs as targetPrefix/parentSlug/childSlug", () => {
|
|
49
|
-
const epic = collection.getModel("epics/authentication", Epic);
|
|
50
|
-
const stories = epic.relationships.stories.fetchAll();
|
|
51
|
-
expect(stories[0].id).toContain("stories/");
|
|
52
|
-
expect(stories[0].id).toContain("authentication");
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe("BelongsToRelationship", () => {
|
|
57
|
-
let collection: Collection;
|
|
58
|
-
|
|
59
|
-
beforeEach(async () => {
|
|
60
|
-
collection = await createTestCollection();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("resolves parent by foreign key", () => {
|
|
64
|
-
const story = collection.getModel(
|
|
65
|
-
"stories/authentication/a-user-should-be-able-to-register",
|
|
66
|
-
Story
|
|
67
|
-
);
|
|
68
|
-
const epic = story.relationships.epic.fetch();
|
|
69
|
-
expect(epic.title).toBe("Authentication");
|
|
70
|
-
expect(epic.id).toBe("epics/authentication");
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("throws if parent not found", () => {
|
|
74
|
-
const doc = collection.createDocument({
|
|
75
|
-
id: "test/orphan",
|
|
76
|
-
content: "# Orphan Story\n",
|
|
77
|
-
meta: { epic: "nonexistent" },
|
|
78
|
-
});
|
|
79
|
-
const instance = createModelInstance(doc, Story, collection);
|
|
80
|
-
expect(() => instance.relationships.epic.fetch()).toThrow(
|
|
81
|
-
'Could not find Epic'
|
|
82
|
-
);
|
|
83
|
-
});
|
|
84
|
-
});
|
package/test/section.test.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import { section } from "../src/section";
|
|
3
|
-
import { Collection } from "../src/collection";
|
|
4
|
-
import { createModelInstance } from "../src/model-instance";
|
|
5
|
-
import { createTestCollection } from "./helpers";
|
|
6
|
-
import { Story } from "./fixtures/sdlc/models";
|
|
7
|
-
import { z } from "zod";
|
|
8
|
-
import { toString } from "mdast-util-to-string";
|
|
9
|
-
|
|
10
|
-
describe("section helper", () => {
|
|
11
|
-
it("creates a SectionDefinition with heading and extract", () => {
|
|
12
|
-
const sd = section("My Section", {
|
|
13
|
-
extract: (q) => q.selectAll("listItem"),
|
|
14
|
-
});
|
|
15
|
-
expect(sd.heading).toBe("My Section");
|
|
16
|
-
expect(sd.extract).toBeDefined();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("stores optional schema", () => {
|
|
20
|
-
const schema = z.array(z.string());
|
|
21
|
-
const sd = section("Items", {
|
|
22
|
-
extract: (q) =>
|
|
23
|
-
q.selectAll("listItem").map((n) => toString(n)),
|
|
24
|
-
schema,
|
|
25
|
-
});
|
|
26
|
-
expect(sd.schema).toBe(schema);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("extract receives AstQuery scoped to section content only", async () => {
|
|
30
|
-
const collection = await createTestCollection();
|
|
31
|
-
const doc = collection.document(
|
|
32
|
-
"stories/authentication/a-user-should-be-able-to-register"
|
|
33
|
-
);
|
|
34
|
-
const instance = createModelInstance(doc, Story, collection);
|
|
35
|
-
|
|
36
|
-
// acceptanceCriteria should only contain items from that section
|
|
37
|
-
const criteria = instance.sections.acceptanceCriteria;
|
|
38
|
-
expect(criteria.length).toBe(4);
|
|
39
|
-
expect(criteria[0]).toContain("signup form");
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("section data is lazily computed", async () => {
|
|
43
|
-
const collection = await createTestCollection();
|
|
44
|
-
const doc = collection.document(
|
|
45
|
-
"stories/authentication/a-user-should-be-able-to-register"
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
let extractCalled = 0;
|
|
49
|
-
const TestModel = {
|
|
50
|
-
name: "Test",
|
|
51
|
-
prefix: "test",
|
|
52
|
-
meta: z.looseObject({}),
|
|
53
|
-
schema: z.looseObject({}),
|
|
54
|
-
sections: {
|
|
55
|
-
items: section("Acceptance Criteria", {
|
|
56
|
-
extract: (q) => {
|
|
57
|
-
extractCalled++;
|
|
58
|
-
return q.selectAll("listItem").map((n) => toString(n));
|
|
59
|
-
},
|
|
60
|
-
}),
|
|
61
|
-
},
|
|
62
|
-
relationships: {},
|
|
63
|
-
computed: {},
|
|
64
|
-
} as any;
|
|
65
|
-
|
|
66
|
-
const instance = createModelInstance(doc, TestModel, collection);
|
|
67
|
-
expect(extractCalled).toBe(0); // Not yet accessed
|
|
68
|
-
const _items = instance.sections.items;
|
|
69
|
-
expect(extractCalled).toBe(1); // Now extracted
|
|
70
|
-
const _items2 = instance.sections.items;
|
|
71
|
-
expect(extractCalled).toBe(1); // Cached
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("missing section returns empty data", async () => {
|
|
75
|
-
const collection = await createTestCollection();
|
|
76
|
-
const doc = collection.createDocument({
|
|
77
|
-
id: "test/no-section",
|
|
78
|
-
content: "# Just a title\n\nSome content.\n",
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
const TestModel = {
|
|
82
|
-
name: "Test",
|
|
83
|
-
prefix: "test",
|
|
84
|
-
meta: z.looseObject({}),
|
|
85
|
-
schema: z.looseObject({}),
|
|
86
|
-
sections: {
|
|
87
|
-
missing: section("Nonexistent Section", {
|
|
88
|
-
extract: (q) => q.selectAll("listItem").map((n) => toString(n)),
|
|
89
|
-
}),
|
|
90
|
-
},
|
|
91
|
-
relationships: {},
|
|
92
|
-
computed: {},
|
|
93
|
-
} as any;
|
|
94
|
-
|
|
95
|
-
const instance = createModelInstance(doc, TestModel, collection);
|
|
96
|
-
// Should not crash, just return empty
|
|
97
|
-
expect(instance.sections.missing).toEqual([]);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import { Collection } from "../src/collection";
|
|
3
|
-
import { createTestCollection, FIXTURES_PATH } from "./helpers";
|
|
4
|
-
|
|
5
|
-
let collection: Collection;
|
|
6
|
-
|
|
7
|
-
beforeEach(async () => {
|
|
8
|
-
collection = await createTestCollection();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
describe("Collection.tableOfContents", () => {
|
|
12
|
-
it("generates markdown with model group headings", () => {
|
|
13
|
-
const toc = collection.tableOfContents();
|
|
14
|
-
|
|
15
|
-
expect(toc).toContain("# Epic");
|
|
16
|
-
expect(toc).toContain("# Story");
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("includes document titles as link text", () => {
|
|
20
|
-
const toc = collection.tableOfContents();
|
|
21
|
-
|
|
22
|
-
expect(toc).toContain("[Authentication]");
|
|
23
|
-
expect(toc).toContain("[Searching And Browsing]");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("generates relative links with file extensions", () => {
|
|
27
|
-
const toc = collection.tableOfContents();
|
|
28
|
-
|
|
29
|
-
expect(toc).toContain("(./epics/authentication.mdx)");
|
|
30
|
-
expect(toc).toContain("(./epics/searching-and-browsing.mdx)");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("uses custom basePath for links", () => {
|
|
34
|
-
const toc = collection.tableOfContents({ basePath: "./content" });
|
|
35
|
-
|
|
36
|
-
expect(toc).toContain("(./content/epics/authentication.mdx)");
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("adds a title heading when provided", () => {
|
|
40
|
-
const toc = collection.tableOfContents({ title: "Project Docs" });
|
|
41
|
-
|
|
42
|
-
expect(toc).toContain("# Project Docs");
|
|
43
|
-
// Model group headings shift to h2
|
|
44
|
-
expect(toc).toContain("## Epic");
|
|
45
|
-
expect(toc).toContain("## Story");
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("sorts items alphabetically within groups", () => {
|
|
49
|
-
const toc = collection.tableOfContents();
|
|
50
|
-
const authIndex = toc.indexOf("[Authentication]");
|
|
51
|
-
const searchIndex = toc.indexOf("[Searching And Browsing]");
|
|
52
|
-
|
|
53
|
-
expect(authIndex).toBeLessThan(searchIndex);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("formats entries as markdown list items", () => {
|
|
57
|
-
const toc = collection.tableOfContents();
|
|
58
|
-
const lines = toc.split("\n");
|
|
59
|
-
const listLines = lines.filter((l) => l.startsWith("- ["));
|
|
60
|
-
|
|
61
|
-
expect(listLines.length).toBeGreaterThanOrEqual(3);
|
|
62
|
-
for (const line of listLines) {
|
|
63
|
-
expect(line).toMatch(/^- \[.+\]\(.+\.mdx?\)$/);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("ends with a newline", () => {
|
|
68
|
-
const toc = collection.tableOfContents();
|
|
69
|
-
expect(toc.endsWith("\n")).toBe(true);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("works without models registered (flat list)", async () => {
|
|
73
|
-
const bare = new Collection({ rootPath: FIXTURES_PATH });
|
|
74
|
-
await bare.load();
|
|
75
|
-
|
|
76
|
-
const toc = bare.tableOfContents();
|
|
77
|
-
|
|
78
|
-
// No group headings — just list items
|
|
79
|
-
expect(toc).not.toContain("# Epic");
|
|
80
|
-
expect(toc).toContain("- [");
|
|
81
|
-
expect(toc).toContain("(./epics/authentication.mdx)");
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("throws if collection not loaded", () => {
|
|
85
|
-
const unloaded = new Collection({ rootPath: FIXTURES_PATH });
|
|
86
|
-
|
|
87
|
-
expect(() => unloaded.tableOfContents()).toThrow(
|
|
88
|
-
"Collection has not been loaded"
|
|
89
|
-
);
|
|
90
|
-
});
|
|
91
|
-
});
|