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,161 @@
|
|
|
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);
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "contentbase",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"contentbase": "./src/cli/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"test:watch": "vitest",
|
|
13
|
+
"typecheck": "tsc --noEmit"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"citty": "^0.1.6",
|
|
17
|
+
"gray-matter": "^4.0.3",
|
|
18
|
+
"js-yaml": "^4.1.0",
|
|
19
|
+
"mdast-util-mdxjs-esm": "^2.0.1",
|
|
20
|
+
"mdast-util-to-markdown": "^2.1.2",
|
|
21
|
+
"mdast-util-to-string": "^4.0.0",
|
|
22
|
+
"remark-gfm": "^4.0.0",
|
|
23
|
+
"remark-parse": "^11.0.0",
|
|
24
|
+
"remark-stringify": "^11.0.0",
|
|
25
|
+
"unified": "^11.0.5",
|
|
26
|
+
"unist-util-find-after": "^5.0.0",
|
|
27
|
+
"unist-util-find-all-after": "^5.0.0",
|
|
28
|
+
"unist-util-find-all-before": "^5.0.0",
|
|
29
|
+
"unist-util-find-before": "^4.0.0",
|
|
30
|
+
"unist-util-select": "^5.1.0",
|
|
31
|
+
"unist-util-visit": "^5.0.0",
|
|
32
|
+
"zod": "^3.23.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/js-yaml": "^4.0.9",
|
|
36
|
+
"@types/mdast": "^4.0.4",
|
|
37
|
+
"bun-types": "^1.1.0",
|
|
38
|
+
"typescript": "^5.4.0",
|
|
39
|
+
"vitest": "^1.6.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineModel,
|
|
3
|
+
section,
|
|
4
|
+
hasMany,
|
|
5
|
+
belongsTo,
|
|
6
|
+
z,
|
|
7
|
+
} from "../../src/index";
|
|
8
|
+
import { toString } from "mdast-util-to-string";
|
|
9
|
+
import { parseTable } from "../../src/utils/parse-table";
|
|
10
|
+
|
|
11
|
+
// ─── Park (parent) ───
|
|
12
|
+
|
|
13
|
+
export const Park = defineModel("Park", {
|
|
14
|
+
prefix: "parks",
|
|
15
|
+
meta: z.object({
|
|
16
|
+
state: z.string(),
|
|
17
|
+
region: z.enum(["west", "southwest", "southeast", "northeast", "midwest"]),
|
|
18
|
+
established: z.number(),
|
|
19
|
+
area: z.string(),
|
|
20
|
+
visitors: z.number(),
|
|
21
|
+
fee: z.number(),
|
|
22
|
+
}),
|
|
23
|
+
sections: {
|
|
24
|
+
wildlife: section("Wildlife", {
|
|
25
|
+
extract: (q) => q.selectAll("listItem").map((n) => toString(n)),
|
|
26
|
+
schema: z.array(z.string()),
|
|
27
|
+
}),
|
|
28
|
+
seasons: section("Best Seasons", {
|
|
29
|
+
extract: (q) => {
|
|
30
|
+
const tables = q.selectAll("table");
|
|
31
|
+
if (tables.length > 0) {
|
|
32
|
+
return parseTable(tables[0]);
|
|
33
|
+
}
|
|
34
|
+
return [];
|
|
35
|
+
},
|
|
36
|
+
schema: z.array(z.record(z.string(), z.string())),
|
|
37
|
+
}),
|
|
38
|
+
},
|
|
39
|
+
relationships: {
|
|
40
|
+
trails: hasMany(() => Trail, { heading: "Trails" }),
|
|
41
|
+
},
|
|
42
|
+
computed: {
|
|
43
|
+
isPopular: (self: any) => self.meta.visitors > 3_000_000,
|
|
44
|
+
ageYears: (self: any) => new Date().getFullYear() - self.meta.established,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// ─── Trail ───
|
|
49
|
+
|
|
50
|
+
export const Trail = defineModel("Trail", {
|
|
51
|
+
prefix: "trails",
|
|
52
|
+
meta: z.object({
|
|
53
|
+
park: z.string(),
|
|
54
|
+
distance: z.string(),
|
|
55
|
+
elevationGain: z.string(),
|
|
56
|
+
difficulty: z.enum(["easy", "moderate", "strenuous"]),
|
|
57
|
+
type: z.enum(["out-and-back", "loop", "point-to-point"]),
|
|
58
|
+
dogs: z.boolean().default(false),
|
|
59
|
+
}),
|
|
60
|
+
sections: {
|
|
61
|
+
highlights: section("Highlights", {
|
|
62
|
+
extract: (q) => q.selectAll("listItem").map((n) => toString(n)),
|
|
63
|
+
schema: z.array(z.string()),
|
|
64
|
+
}),
|
|
65
|
+
},
|
|
66
|
+
relationships: {
|
|
67
|
+
park: belongsTo(() => Park, {
|
|
68
|
+
foreignKey: (doc) => doc.meta.park as string,
|
|
69
|
+
}),
|
|
70
|
+
},
|
|
71
|
+
computed: {
|
|
72
|
+
isLong: (self: any) => parseFloat(self.meta.distance) > 10,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
state: Maine
|
|
3
|
+
region: northeast
|
|
4
|
+
established: 1919
|
|
5
|
+
area: "49,071 acres"
|
|
6
|
+
visitors: 4069000
|
|
7
|
+
fee: 30
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Acadia
|
|
11
|
+
|
|
12
|
+
The only national park in the northeastern United States. Rocky coastline, granite peaks, and boreal forest crammed onto an island off the coast of Maine. Small in acreage but staggering in density of beauty.
|
|
13
|
+
|
|
14
|
+
## Wildlife
|
|
15
|
+
|
|
16
|
+
- Moose
|
|
17
|
+
- Harbor seal
|
|
18
|
+
- Bald eagle
|
|
19
|
+
- Puffin (offshore)
|
|
20
|
+
- Snowshoe hare
|
|
21
|
+
- Peregrine falcon
|
|
22
|
+
|
|
23
|
+
## Best Seasons
|
|
24
|
+
|
|
25
|
+
| Season | Conditions | Crowds |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| Spring | Mud season, wildflowers begin | Light |
|
|
28
|
+
| Summer | Warm, long days, all carriage roads open | Heavy |
|
|
29
|
+
| Fall | Peak foliage, crisp air, perfect hiking | Heavy |
|
|
30
|
+
| Winter | Snow, cross-country skiing, solitude | Very light |
|
|
31
|
+
|
|
32
|
+
## Trails
|
|
33
|
+
|
|
34
|
+
### Precipice Trail
|
|
35
|
+
|
|
36
|
+
Iron rungs and ladders bolted into a cliff face. More climb than hike. Seasonal closures for nesting peregrines.
|
|
37
|
+
|
|
38
|
+
### Jordan Pond Path
|
|
39
|
+
|
|
40
|
+
A flat, easy loop around the clearest lake in the park. End with popovers at Jordan Pond House.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
state: California
|
|
3
|
+
region: west
|
|
4
|
+
established: 1890
|
|
5
|
+
area: "748,436 acres"
|
|
6
|
+
visitors: 3690000
|
|
7
|
+
fee: 35
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Yosemite
|
|
11
|
+
|
|
12
|
+
Granite cliffs, ancient sequoias, and waterfalls that drop thousands of feet. Yosemite Valley alone is worth the trip, but the backcountry — Tuolumne Meadows, the high Sierra — is where the park reveals its full scale.
|
|
13
|
+
|
|
14
|
+
## Wildlife
|
|
15
|
+
|
|
16
|
+
- Black bear
|
|
17
|
+
- Mule deer
|
|
18
|
+
- Peregrine falcon
|
|
19
|
+
- Sierra Nevada bighorn sheep
|
|
20
|
+
- Great gray owl
|
|
21
|
+
- Pacific fisher
|
|
22
|
+
|
|
23
|
+
## Best Seasons
|
|
24
|
+
|
|
25
|
+
| Season | Conditions | Crowds |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| Spring | Waterfalls at peak flow, some roads closed | Moderate |
|
|
28
|
+
| Summer | Warm, all facilities open | Heavy |
|
|
29
|
+
| Fall | Cool, fewer crowds, fall color | Light |
|
|
30
|
+
| Winter | Snow, Tioga Road closed, skiing | Very light |
|
|
31
|
+
|
|
32
|
+
## Trails
|
|
33
|
+
|
|
34
|
+
### Mist Trail
|
|
35
|
+
|
|
36
|
+
The park's most iconic hike — steep granite stairs through the spray of Vernal Fall and on to Nevada Fall.
|
|
37
|
+
|
|
38
|
+
### Half Dome
|
|
39
|
+
|
|
40
|
+
The bucket-list summit. Cables, permits, and 14+ miles of exertion for a view you'll never forget.
|
|
41
|
+
|
|
42
|
+
### Cathedral Lakes
|
|
43
|
+
|
|
44
|
+
A high-country gem in Tuolumne Meadows — alpine lakes ringed by granite peaks.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
state: Utah
|
|
3
|
+
region: southwest
|
|
4
|
+
established: 1919
|
|
5
|
+
area: "147,242 acres"
|
|
6
|
+
visitors: 4692000
|
|
7
|
+
fee: 35
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Zion
|
|
11
|
+
|
|
12
|
+
Massive sandstone walls in shades of red, pink, and cream tower above the Virgin River. The canyon is narrow enough to feel intimate and vast enough to feel humbling. Zion manages to be both.
|
|
13
|
+
|
|
14
|
+
## Wildlife
|
|
15
|
+
|
|
16
|
+
- Desert bighorn sheep
|
|
17
|
+
- California condor
|
|
18
|
+
- Ringtail cat
|
|
19
|
+
- Collared lizard
|
|
20
|
+
- Mexican spotted owl
|
|
21
|
+
- Canyon tree frog
|
|
22
|
+
|
|
23
|
+
## Best Seasons
|
|
24
|
+
|
|
25
|
+
| Season | Conditions | Crowds |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| Spring | Wildflowers, moderate temps, some flash flood risk | Moderate |
|
|
28
|
+
| Summer | Hot (100°F+), best for The Narrows | Very heavy |
|
|
29
|
+
| Fall | Perfect temps, fall color in the canyon | Moderate |
|
|
30
|
+
| Winter | Cool, snow on rims, peaceful | Light |
|
|
31
|
+
|
|
32
|
+
## Trails
|
|
33
|
+
|
|
34
|
+
### Angels Landing
|
|
35
|
+
|
|
36
|
+
A chain-assisted climb along a narrow ridge with 1,500-foot dropoffs on either side. Not for the faint-hearted.
|
|
37
|
+
|
|
38
|
+
### The Narrows
|
|
39
|
+
|
|
40
|
+
Wade upstream through the Virgin River between thousand-foot canyon walls. The ultimate slot canyon experience.
|
|
41
|
+
|
|
42
|
+
### Observation Point
|
|
43
|
+
|
|
44
|
+
The highest viewpoint in the main canyon — looks down on Angels Landing from above.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example queries for the National Parks showcase.
|
|
3
|
+
*
|
|
4
|
+
* Run with: bun showcases/national-parks/queries.ts
|
|
5
|
+
*/
|
|
6
|
+
import { Collection } from "../../src/index";
|
|
7
|
+
import { Park, Trail } from "./models";
|
|
8
|
+
|
|
9
|
+
const collection = new Collection({
|
|
10
|
+
rootPath: new URL(".", import.meta.url).pathname,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
collection.register(Park);
|
|
14
|
+
collection.register(Trail);
|
|
15
|
+
await collection.load();
|
|
16
|
+
|
|
17
|
+
// ── All parks ──
|
|
18
|
+
const allParks = await collection.query(Park).fetchAll();
|
|
19
|
+
console.log(`Total parks: ${allParks.length}`);
|
|
20
|
+
for (const park of allParks) {
|
|
21
|
+
console.log(
|
|
22
|
+
` ${park.title} (${park.meta.state}) — ${park.meta.area}, est. ${park.meta.established}`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ── Filter by region ──
|
|
27
|
+
const western = await collection
|
|
28
|
+
.query(Park)
|
|
29
|
+
.whereIn("meta.region", ["west", "southwest"])
|
|
30
|
+
.fetchAll();
|
|
31
|
+
console.log(
|
|
32
|
+
`\nWestern parks: ${western.map((p) => p.title).join(", ")}`
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// ── Popular parks (>3M visitors) ──
|
|
36
|
+
console.log(`\nPopular parks (>3M visitors):`);
|
|
37
|
+
for (const park of allParks) {
|
|
38
|
+
if (park.computed.isPopular) {
|
|
39
|
+
console.log(
|
|
40
|
+
` ${park.title} — ${park.meta.visitors.toLocaleString()} visitors`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Park age ──
|
|
46
|
+
console.log(`\nPark ages:`);
|
|
47
|
+
for (const park of allParks) {
|
|
48
|
+
console.log(` ${park.title}: ${park.computed.ageYears} years old`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── All trails ──
|
|
52
|
+
const allTrails = await collection.query(Trail).fetchAll();
|
|
53
|
+
console.log(`\nTotal trails: ${allTrails.length}`);
|
|
54
|
+
|
|
55
|
+
// ── Filter by difficulty ──
|
|
56
|
+
const strenuous = await collection
|
|
57
|
+
.query(Trail)
|
|
58
|
+
.where("meta.difficulty", "strenuous")
|
|
59
|
+
.fetchAll();
|
|
60
|
+
console.log(
|
|
61
|
+
`\nStrenuous trails: ${strenuous.map((t) => t.title).join(", ")}`
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const easy = await collection
|
|
65
|
+
.query(Trail)
|
|
66
|
+
.where("meta.difficulty", "easy")
|
|
67
|
+
.fetchAll();
|
|
68
|
+
console.log(`Easy trails: ${easy.map((t) => t.title).join(", ")}`);
|
|
69
|
+
|
|
70
|
+
// ── Dog-friendly trails ──
|
|
71
|
+
const dogFriendly = await collection
|
|
72
|
+
.query(Trail)
|
|
73
|
+
.where("meta.dogs", true)
|
|
74
|
+
.fetchAll();
|
|
75
|
+
console.log(
|
|
76
|
+
`\nDog-friendly trails: ${dogFriendly.map((t) => t.title).join(", ")}`
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// ── Sections as structured data ──
|
|
80
|
+
const yosemite = collection.getModel("parks/yosemite", Park);
|
|
81
|
+
console.log(`\n--- ${yosemite.title} ---`);
|
|
82
|
+
console.log("Wildlife:", yosemite.sections.wildlife);
|
|
83
|
+
console.log("Best seasons:", yosemite.sections.seasons);
|
|
84
|
+
|
|
85
|
+
// ── Park → Trails relationship (hasMany) ──
|
|
86
|
+
const zion = collection.getModel("parks/zion", Park);
|
|
87
|
+
const zionTrails = zion.relationships.trails.fetchAll();
|
|
88
|
+
console.log(
|
|
89
|
+
`\n${zion.title} trails: ${zionTrails.map((t) => t.title).join(", ")}`
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// ── Trail → Park relationship (belongsTo) ──
|
|
93
|
+
const narrows = collection.getModel("trails/the-narrows", Trail);
|
|
94
|
+
const parentPark = narrows.relationships.park.fetch();
|
|
95
|
+
console.log(`\n${narrows.title} is in ${parentPark.title} (${parentPark.meta.state})`);
|
|
96
|
+
console.log("Highlights:", narrows.sections.highlights);
|
|
97
|
+
|
|
98
|
+
// ── Serialize ──
|
|
99
|
+
const json = yosemite.toJSON({
|
|
100
|
+
sections: ["wildlife", "seasons"],
|
|
101
|
+
computed: ["isPopular", "ageYears"],
|
|
102
|
+
});
|
|
103
|
+
console.log(`\nYosemite as JSON:`, JSON.stringify(json, null, 2));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
park: zion
|
|
3
|
+
distance: "5.4 mi"
|
|
4
|
+
elevationGain: "1,488 ft"
|
|
5
|
+
difficulty: strenuous
|
|
6
|
+
type: out-and-back
|
|
7
|
+
dogs: false
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Angels Landing
|
|
11
|
+
|
|
12
|
+
Zion's most famous trail — and one of the most exposed hikes in any national park. The final half-mile follows a knife-edge ridge with chains for handholds and sheer dropoffs on both sides. Permit required.
|
|
13
|
+
|
|
14
|
+
## Highlights
|
|
15
|
+
|
|
16
|
+
- Walter's Wiggles — 21 tight switchbacks carved into the cliff
|
|
17
|
+
- Scout Lookout — a wide saddle with stunning views even if you turn back here
|
|
18
|
+
- Chain section along the narrow ridge
|
|
19
|
+
- Panoramic views of Zion Canyon from the 5,790 ft summit
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
park: yosemite
|
|
3
|
+
distance: "7.0 mi"
|
|
4
|
+
elevationGain: "1,000 ft"
|
|
5
|
+
difficulty: moderate
|
|
6
|
+
type: out-and-back
|
|
7
|
+
dogs: false
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Cathedral Lakes
|
|
11
|
+
|
|
12
|
+
A high-country classic in Tuolumne Meadows. Two pristine alpine lakes sit in granite bowls below Cathedral Peak. The trail winds through subalpine meadows dotted with wildflowers in midsummer.
|
|
13
|
+
|
|
14
|
+
## Highlights
|
|
15
|
+
|
|
16
|
+
- Lower Cathedral Lake — wide and open with Cathedral Peak reflected in the water
|
|
17
|
+
- Upper Cathedral Lake — smaller, more secluded, tucked under the peak
|
|
18
|
+
- Wildflower meadows in July and August
|
|
19
|
+
- Views of Cathedral Peak and Eichorn Pinnacle
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
park: yosemite
|
|
3
|
+
distance: "14.2 mi"
|
|
4
|
+
elevationGain: "4,800 ft"
|
|
5
|
+
difficulty: strenuous
|
|
6
|
+
type: out-and-back
|
|
7
|
+
dogs: false
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Half Dome
|
|
11
|
+
|
|
12
|
+
The defining Yosemite summit. A full-day endurance test that follows the Mist Trail, then climbs through the sub-dome before ascending the final 400 feet on steel cables bolted into granite. Permits required.
|
|
13
|
+
|
|
14
|
+
## Highlights
|
|
15
|
+
|
|
16
|
+
- Cable section on the final ascent — thrilling and exposed
|
|
17
|
+
- Summit views of Tenaya Canyon, Clouds Rest, and the entire valley
|
|
18
|
+
- Passes both Vernal and Nevada Falls on the way up
|
|
19
|
+
- Sub-dome scramble over smooth granite slabs
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
park: acadia
|
|
3
|
+
distance: "3.3 mi"
|
|
4
|
+
elevationGain: "105 ft"
|
|
5
|
+
difficulty: easy
|
|
6
|
+
type: loop
|
|
7
|
+
dogs: true
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Jordan Pond Path
|
|
11
|
+
|
|
12
|
+
A flat, family-friendly loop around the clearest lake in Acadia. The path hugs the shoreline through mixed forest, with iconic views of the Bubbles — two rounded granite peaks — reflected in the water.
|
|
13
|
+
|
|
14
|
+
## Highlights
|
|
15
|
+
|
|
16
|
+
- Mirror-like reflections of the Bubbles on calm mornings
|
|
17
|
+
- Boardwalk sections over the boggy north shore
|
|
18
|
+
- Jordan Pond House for tea and popovers after the hike
|
|
19
|
+
- Accessible for all skill levels and ages
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
park: yosemite
|
|
3
|
+
distance: "5.4 mi"
|
|
4
|
+
elevationGain: "1,000 ft"
|
|
5
|
+
difficulty: strenuous
|
|
6
|
+
type: out-and-back
|
|
7
|
+
dogs: false
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Mist Trail
|
|
11
|
+
|
|
12
|
+
Yosemite's most iconic day hike. Steep granite stairs climb through the spray of Vernal Fall, then continue along the Merced River to the top of Nevada Fall. You will get wet.
|
|
13
|
+
|
|
14
|
+
## Highlights
|
|
15
|
+
|
|
16
|
+
- Vernal Fall (317 ft) — close enough to feel the mist
|
|
17
|
+
- Nevada Fall (594 ft) — a massive cascade visible from the bridge above
|
|
18
|
+
- Emerald Pool between the two falls
|
|
19
|
+
- Granite staircase carved into the cliff face
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
park: zion
|
|
3
|
+
distance: "8.0 mi"
|
|
4
|
+
elevationGain: "2,148 ft"
|
|
5
|
+
difficulty: strenuous
|
|
6
|
+
type: out-and-back
|
|
7
|
+
dogs: false
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Observation Point
|
|
11
|
+
|
|
12
|
+
The highest viewpoint accessible by trail in Zion Canyon. From the top, you look directly down on Angels Landing — a perspective that makes you appreciate how exposed that ridge really is.
|
|
13
|
+
|
|
14
|
+
## Highlights
|
|
15
|
+
|
|
16
|
+
- Echo Canyon section — narrow side canyon with weeping rock walls
|
|
17
|
+
- Views down onto Angels Landing and the Great White Throne
|
|
18
|
+
- Cable Mountain and the historic cable works ruins nearby
|
|
19
|
+
- Slickrock terrain near the summit
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
park: acadia
|
|
3
|
+
distance: "1.6 mi"
|
|
4
|
+
elevationGain: "1,058 ft"
|
|
5
|
+
difficulty: strenuous
|
|
6
|
+
type: out-and-back
|
|
7
|
+
dogs: false
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Precipice Trail
|
|
11
|
+
|
|
12
|
+
Acadia's most thrilling route — iron rungs, ladders, and narrow ledges bolted into the cliff face of Champlain Mountain. More via ferrata than hiking trail. Closed spring through midsummer for peregrine falcon nesting.
|
|
13
|
+
|
|
14
|
+
## Highlights
|
|
15
|
+
|
|
16
|
+
- Iron rung ladders on near-vertical rock faces
|
|
17
|
+
- Exposed traverses with ocean views
|
|
18
|
+
- Summit views of Frenchman Bay and the Porcupine Islands
|
|
19
|
+
- Peregrine falcons nesting on the cliff (visible from below during closures)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
park: zion
|
|
3
|
+
distance: "9.4 mi"
|
|
4
|
+
elevationGain: "334 ft"
|
|
5
|
+
difficulty: strenuous
|
|
6
|
+
type: out-and-back
|
|
7
|
+
dogs: false
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# The Narrows
|
|
11
|
+
|
|
12
|
+
A river hike through the narrowest section of Zion Canyon. You wade — and occasionally swim — upstream through the Virgin River between sandstone walls up to 1,000 feet high. Entirely unlike any other trail in the park system.
|
|
13
|
+
|
|
14
|
+
## Highlights
|
|
15
|
+
|
|
16
|
+
- Wall Street section — walls narrow to 20 feet with the river filling the entire canyon floor
|
|
17
|
+
- Orderville Canyon junction — a side canyon worth exploring
|
|
18
|
+
- Hanging gardens of ferns and columbine on the canyon walls
|
|
19
|
+
- The interplay of light and shadow in the narrow slot
|