contentbase 0.0.1
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 +460 -0
- package/bun.lock +473 -0
- package/examples/sdlc-queries.ts +161 -0
- package/package.json +41 -0
- package/showcases/national-parks/models.ts +74 -0
- package/showcases/national-parks/parks/acadia.mdx +40 -0
- package/showcases/national-parks/parks/yosemite.mdx +44 -0
- package/showcases/national-parks/parks/zion.mdx +44 -0
- package/showcases/national-parks/queries.ts +103 -0
- package/showcases/national-parks/trails/angels-landing.mdx +19 -0
- package/showcases/national-parks/trails/cathedral-lakes.mdx +19 -0
- package/showcases/national-parks/trails/half-dome.mdx +19 -0
- package/showcases/national-parks/trails/jordan-pond-path.mdx +19 -0
- package/showcases/national-parks/trails/mist-trail.mdx +19 -0
- package/showcases/national-parks/trails/observation-point.mdx +19 -0
- package/showcases/national-parks/trails/precipice-trail.mdx +19 -0
- package/showcases/national-parks/trails/the-narrows.mdx +19 -0
- package/showcases/recipes/cuisines/chinese.mdx +28 -0
- package/showcases/recipes/cuisines/italian.mdx +32 -0
- package/showcases/recipes/cuisines/mexican.mdx +28 -0
- package/showcases/recipes/models.ts +77 -0
- package/showcases/recipes/queries.ts +89 -0
- package/showcases/recipes/recipes/chinese/egg-fried-rice.mdx +43 -0
- package/showcases/recipes/recipes/chinese/mapo-tofu.mdx +47 -0
- package/showcases/recipes/recipes/italian/bruschetta.mdx +38 -0
- package/showcases/recipes/recipes/italian/cacio-e-pepe.mdx +39 -0
- package/showcases/recipes/recipes/italian/tiramisu.mdx +43 -0
- package/showcases/recipes/recipes/mexican/chicken-tinga.mdx +44 -0
- package/showcases/recipes/recipes/mexican/guacamole.mdx +39 -0
- package/showcases/vinyl-collection/albums/bitches-brew.mdx +36 -0
- package/showcases/vinyl-collection/albums/i-put-a-spell-on-you.mdx +35 -0
- package/showcases/vinyl-collection/albums/in-rainbows.mdx +35 -0
- package/showcases/vinyl-collection/albums/kind-of-blue.mdx +32 -0
- package/showcases/vinyl-collection/albums/ok-computer.mdx +37 -0
- package/showcases/vinyl-collection/albums/wild-is-the-wind.mdx +35 -0
- package/showcases/vinyl-collection/artists/miles-davis.mdx +27 -0
- package/showcases/vinyl-collection/artists/nina-simone.mdx +26 -0
- package/showcases/vinyl-collection/artists/radiohead.mdx +27 -0
- package/showcases/vinyl-collection/models.ts +73 -0
- package/showcases/vinyl-collection/queries.ts +87 -0
- package/src/ast-query.ts +132 -0
- package/src/cli/commands/action.ts +44 -0
- package/src/cli/commands/create.ts +59 -0
- package/src/cli/commands/export.ts +24 -0
- package/src/cli/commands/init.ts +75 -0
- package/src/cli/commands/inspect.ts +46 -0
- package/src/cli/commands/validate.ts +75 -0
- package/src/cli/index.ts +20 -0
- package/src/cli/load-collection.ts +53 -0
- package/src/collection.ts +399 -0
- package/src/define-model.ts +80 -0
- package/src/document.ts +468 -0
- package/src/index.ts +47 -0
- package/src/model-instance.ts +227 -0
- package/src/node-shortcuts.ts +87 -0
- package/src/parse.ts +123 -0
- package/src/query/collection-query.ts +149 -0
- package/src/query/index.ts +5 -0
- package/src/query/operators.ts +37 -0
- package/src/query/query-builder.ts +109 -0
- package/src/relationships/belongs-to.ts +50 -0
- package/src/relationships/has-many.ts +136 -0
- package/src/relationships/index.ts +57 -0
- package/src/relationships/types.ts +7 -0
- package/src/section.ts +29 -0
- package/src/types.ts +221 -0
- package/src/utils/index.ts +11 -0
- package/src/utils/inflect.ts +82 -0
- package/src/utils/normalize-headings.ts +31 -0
- package/src/utils/parse-table.ts +30 -0
- package/src/utils/read-directory.ts +35 -0
- package/src/utils/stringify-ast.ts +9 -0
- package/src/validator.ts +52 -0
- package/test/ast-query.test.ts +128 -0
- package/test/collection.test.ts +99 -0
- package/test/define-model.test.ts +78 -0
- package/test/document.test.ts +225 -0
- package/test/fixtures/sdlc/epics/authentication.mdx +42 -0
- package/test/fixtures/sdlc/epics/searching-and-browsing.mdx +21 -0
- package/test/fixtures/sdlc/models.ts +89 -0
- package/test/fixtures/sdlc/stories/authentication/a-user-should-be-able-to-register.mdx +20 -0
- package/test/helpers.ts +21 -0
- package/test/model-instance.test.ts +197 -0
- package/test/query.test.ts +167 -0
- package/test/relationships.test.ts +84 -0
- package/test/section.test.ts +99 -0
- package/test/validator.test.ts +62 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1,99 @@
|
|
|
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.object({}).passthrough(),
|
|
53
|
+
schema: z.object({}).passthrough(),
|
|
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.object({}).passthrough(),
|
|
85
|
+
schema: z.object({}).passthrough(),
|
|
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
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { validateDocument } from "../src/validator";
|
|
3
|
+
import { Collection } from "../src/collection";
|
|
4
|
+
import { createTestCollection } from "./helpers";
|
|
5
|
+
import { Epic, Story } from "./fixtures/sdlc/models";
|
|
6
|
+
|
|
7
|
+
describe("validateDocument", () => {
|
|
8
|
+
let collection: Collection;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
collection = await createTestCollection();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("returns valid for good meta", () => {
|
|
15
|
+
const doc = collection.document("epics/authentication");
|
|
16
|
+
const result = validateDocument(doc, Epic);
|
|
17
|
+
expect(result.valid).toBe(true);
|
|
18
|
+
expect(result.errors.length).toBe(0);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns errors for invalid meta", () => {
|
|
22
|
+
const doc = collection.createDocument({
|
|
23
|
+
id: "test/bad",
|
|
24
|
+
content: "# Bad\n",
|
|
25
|
+
meta: { status: "BOGUS" },
|
|
26
|
+
});
|
|
27
|
+
const result = validateDocument(doc, Epic);
|
|
28
|
+
expect(result.valid).toBe(false);
|
|
29
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("applies defaults before validation", () => {
|
|
33
|
+
const doc = collection.createDocument({
|
|
34
|
+
id: "test/defaults",
|
|
35
|
+
content: "# Defaults\n",
|
|
36
|
+
meta: {},
|
|
37
|
+
});
|
|
38
|
+
const result = validateDocument(doc, Epic);
|
|
39
|
+
expect(result.valid).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("validates sections when schema is present", () => {
|
|
43
|
+
const doc = collection.document(
|
|
44
|
+
"stories/authentication/a-user-should-be-able-to-register"
|
|
45
|
+
);
|
|
46
|
+
const result = validateDocument(doc, Story);
|
|
47
|
+
expect(result.valid).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("section validation errors include section key in path", () => {
|
|
51
|
+
const doc = collection.createDocument({
|
|
52
|
+
id: "test/bad-section",
|
|
53
|
+
content: "# Bad\n\n## Acceptance Criteria\n\nNo list items here.\n",
|
|
54
|
+
meta: { status: "created" },
|
|
55
|
+
});
|
|
56
|
+
const result = validateDocument(doc, Story);
|
|
57
|
+
// acceptanceCriteria extracts listItems; paragraph text won't produce any
|
|
58
|
+
// The schema is z.array(z.string()) which allows empty arrays
|
|
59
|
+
// so this should actually pass - the extract returns []
|
|
60
|
+
expect(result.valid).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"declarationMap": true,
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"outDir": "./dist",
|
|
12
|
+
"rootDir": "./src",
|
|
13
|
+
"types": ["bun-types"],
|
|
14
|
+
"skipLibCheck": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*.ts"],
|
|
17
|
+
"exclude": ["node_modules", "dist", "test"]
|
|
18
|
+
}
|