world-building-framework 0.2.0 → 0.2.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/dist/cli/commands/create.d.ts +1 -7
- package/dist/cli/commands/publish.js +1 -1
- package/dist/cli/commands/render.js +1 -1
- package/dist/core/config/schema.d.ts +8 -208
- package/dist/core/config/schema.js +2 -2
- package/dist/core/query/dates.d.ts +4 -40
- package/dist/core/query/types.d.ts +5 -59
- package/dist/core/schema/describe.d.ts +29 -3
- package/dist/core/schema/describe.js +66 -18
- package/dist/core/schema/fields.d.ts +28 -220
- package/dist/core/schema/fields.js +3 -3
- package/dist/render-pipeline/build.d.ts +5 -2
- package/dist/render-pipeline/build.js +13 -9
- package/dist/render-pipeline/index.d.ts +1 -0
- package/dist/render-pipeline/index.js +1 -0
- package/dist/render-pipeline/slugs.d.ts +3 -2
- package/dist/render-pipeline/slugs.js +4 -3
- package/dist/render-pipeline/start-page.d.ts +9 -0
- package/dist/render-pipeline/start-page.js +44 -0
- package/package.json +2 -2
|
@@ -4,13 +4,7 @@ import { type ValidationIssue } from "../../core/schema/index.js";
|
|
|
4
4
|
export declare const fileInputSchema: z.ZodObject<{
|
|
5
5
|
frontmatter: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
6
6
|
body: z.ZodDefault<z.ZodString>;
|
|
7
|
-
},
|
|
8
|
-
frontmatter: Record<string, unknown>;
|
|
9
|
-
body: string;
|
|
10
|
-
}, {
|
|
11
|
-
frontmatter: Record<string, unknown>;
|
|
12
|
-
body?: string | undefined;
|
|
13
|
-
}>;
|
|
7
|
+
}, z.core.$strip>;
|
|
14
8
|
export interface CreateOutcome {
|
|
15
9
|
written: boolean;
|
|
16
10
|
id?: string;
|
|
@@ -49,7 +49,7 @@ export async function runPublish(vaultOption, options) {
|
|
|
49
49
|
await setQuartzPageTitle(quartzDir, title);
|
|
50
50
|
for (const name of variantNames) {
|
|
51
51
|
const query = getBuildVariant(config, name);
|
|
52
|
-
const pages = buildDerivedTree(files, query);
|
|
52
|
+
const pages = buildDerivedTree(files, query, { title });
|
|
53
53
|
await syncDerivedTreeToQuartz(path.join(quartzDir, "content"), pages);
|
|
54
54
|
if (options.baseUrl) {
|
|
55
55
|
await setQuartzBaseUrl(quartzDir, `${normalizeBaseUrl(options.baseUrl)}/${name}`);
|
|
@@ -11,7 +11,7 @@ export async function runRender(vaultOption, options) {
|
|
|
11
11
|
const config = await loadWorldConfig(vaultDir);
|
|
12
12
|
const query = getBuildVariant(config, options.variant);
|
|
13
13
|
const files = await readVault(vaultDir);
|
|
14
|
-
const pages = buildDerivedTree(files, query);
|
|
14
|
+
const pages = buildDerivedTree(files, query, { title: worldTitle(config) });
|
|
15
15
|
// Before syncing: a fresh clone must not land on top of a content folder we just wrote.
|
|
16
16
|
if (options.build)
|
|
17
17
|
await ensureQuartz(quartzDir);
|
|
@@ -3,226 +3,26 @@ import { z } from "zod";
|
|
|
3
3
|
* A vault's own project-specific setup parameters (PROJECT_CONFIG.md).
|
|
4
4
|
* `title`, `entityTypes` and `buildVariants` are the fields with a decided consumer
|
|
5
5
|
* so far — tag vocabulary itself (as opposed to where it's used, buildVariants)
|
|
6
|
-
* and relation-field naming are still open, so this stays
|
|
6
|
+
* and relation-field naming are still open, so this stays loose (unknown keys kept) rather
|
|
7
7
|
* than locking in a shape for those prematurely.
|
|
8
8
|
*/
|
|
9
9
|
export declare const worldConfigSchema: z.ZodObject<{
|
|
10
|
-
/** The world's name, shown as the published site's title (every variant and the landing page). */
|
|
11
10
|
title: z.ZodOptional<z.ZodString>;
|
|
12
|
-
entityTypes: z.ZodOptional<z.ZodArray<z.ZodString
|
|
13
|
-
/** Named filter queries (render-pipeline's BuildVariantQuery) — PROJECT_CONFIG.md #3. */
|
|
11
|
+
entityTypes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
14
12
|
buildVariants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
15
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString
|
|
13
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
16
14
|
dateRange: z.ZodOptional<z.ZodObject<{
|
|
17
15
|
from: z.ZodOptional<z.ZodObject<{
|
|
18
16
|
year: z.ZodNumber;
|
|
19
17
|
day: z.ZodOptional<z.ZodNumber>;
|
|
20
|
-
},
|
|
21
|
-
year: number;
|
|
22
|
-
day?: number | undefined;
|
|
23
|
-
}, {
|
|
24
|
-
year: number;
|
|
25
|
-
day?: number | undefined;
|
|
26
|
-
}>>;
|
|
18
|
+
}, z.core.$strip>>;
|
|
27
19
|
to: z.ZodOptional<z.ZodObject<{
|
|
28
20
|
year: z.ZodNumber;
|
|
29
21
|
day: z.ZodOptional<z.ZodNumber>;
|
|
30
|
-
},
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
year: number;
|
|
35
|
-
day?: number | undefined;
|
|
36
|
-
}>>;
|
|
37
|
-
}, "strip", z.ZodTypeAny, {
|
|
38
|
-
from?: {
|
|
39
|
-
year: number;
|
|
40
|
-
day?: number | undefined;
|
|
41
|
-
} | undefined;
|
|
42
|
-
to?: {
|
|
43
|
-
year: number;
|
|
44
|
-
day?: number | undefined;
|
|
45
|
-
} | undefined;
|
|
46
|
-
}, {
|
|
47
|
-
from?: {
|
|
48
|
-
year: number;
|
|
49
|
-
day?: number | undefined;
|
|
50
|
-
} | undefined;
|
|
51
|
-
to?: {
|
|
52
|
-
year: number;
|
|
53
|
-
day?: number | undefined;
|
|
54
|
-
} | undefined;
|
|
55
|
-
}>>;
|
|
56
|
-
}, "strip", z.ZodTypeAny, {
|
|
57
|
-
tags?: string[] | undefined;
|
|
58
|
-
dateRange?: {
|
|
59
|
-
from?: {
|
|
60
|
-
year: number;
|
|
61
|
-
day?: number | undefined;
|
|
62
|
-
} | undefined;
|
|
63
|
-
to?: {
|
|
64
|
-
year: number;
|
|
65
|
-
day?: number | undefined;
|
|
66
|
-
} | undefined;
|
|
67
|
-
} | undefined;
|
|
68
|
-
}, {
|
|
69
|
-
tags?: string[] | undefined;
|
|
70
|
-
dateRange?: {
|
|
71
|
-
from?: {
|
|
72
|
-
year: number;
|
|
73
|
-
day?: number | undefined;
|
|
74
|
-
} | undefined;
|
|
75
|
-
to?: {
|
|
76
|
-
year: number;
|
|
77
|
-
day?: number | undefined;
|
|
78
|
-
} | undefined;
|
|
79
|
-
} | undefined;
|
|
80
|
-
}>>>;
|
|
81
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
82
|
-
/** The world's name, shown as the published site's title (every variant and the landing page). */
|
|
83
|
-
title: z.ZodOptional<z.ZodString>;
|
|
84
|
-
entityTypes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
85
|
-
/** Named filter queries (render-pipeline's BuildVariantQuery) — PROJECT_CONFIG.md #3. */
|
|
86
|
-
buildVariants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
87
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
88
|
-
dateRange: z.ZodOptional<z.ZodObject<{
|
|
89
|
-
from: z.ZodOptional<z.ZodObject<{
|
|
90
|
-
year: z.ZodNumber;
|
|
91
|
-
day: z.ZodOptional<z.ZodNumber>;
|
|
92
|
-
}, "strip", z.ZodTypeAny, {
|
|
93
|
-
year: number;
|
|
94
|
-
day?: number | undefined;
|
|
95
|
-
}, {
|
|
96
|
-
year: number;
|
|
97
|
-
day?: number | undefined;
|
|
98
|
-
}>>;
|
|
99
|
-
to: z.ZodOptional<z.ZodObject<{
|
|
100
|
-
year: z.ZodNumber;
|
|
101
|
-
day: z.ZodOptional<z.ZodNumber>;
|
|
102
|
-
}, "strip", z.ZodTypeAny, {
|
|
103
|
-
year: number;
|
|
104
|
-
day?: number | undefined;
|
|
105
|
-
}, {
|
|
106
|
-
year: number;
|
|
107
|
-
day?: number | undefined;
|
|
108
|
-
}>>;
|
|
109
|
-
}, "strip", z.ZodTypeAny, {
|
|
110
|
-
from?: {
|
|
111
|
-
year: number;
|
|
112
|
-
day?: number | undefined;
|
|
113
|
-
} | undefined;
|
|
114
|
-
to?: {
|
|
115
|
-
year: number;
|
|
116
|
-
day?: number | undefined;
|
|
117
|
-
} | undefined;
|
|
118
|
-
}, {
|
|
119
|
-
from?: {
|
|
120
|
-
year: number;
|
|
121
|
-
day?: number | undefined;
|
|
122
|
-
} | undefined;
|
|
123
|
-
to?: {
|
|
124
|
-
year: number;
|
|
125
|
-
day?: number | undefined;
|
|
126
|
-
} | undefined;
|
|
127
|
-
}>>;
|
|
128
|
-
}, "strip", z.ZodTypeAny, {
|
|
129
|
-
tags?: string[] | undefined;
|
|
130
|
-
dateRange?: {
|
|
131
|
-
from?: {
|
|
132
|
-
year: number;
|
|
133
|
-
day?: number | undefined;
|
|
134
|
-
} | undefined;
|
|
135
|
-
to?: {
|
|
136
|
-
year: number;
|
|
137
|
-
day?: number | undefined;
|
|
138
|
-
} | undefined;
|
|
139
|
-
} | undefined;
|
|
140
|
-
}, {
|
|
141
|
-
tags?: string[] | undefined;
|
|
142
|
-
dateRange?: {
|
|
143
|
-
from?: {
|
|
144
|
-
year: number;
|
|
145
|
-
day?: number | undefined;
|
|
146
|
-
} | undefined;
|
|
147
|
-
to?: {
|
|
148
|
-
year: number;
|
|
149
|
-
day?: number | undefined;
|
|
150
|
-
} | undefined;
|
|
151
|
-
} | undefined;
|
|
152
|
-
}>>>;
|
|
153
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
154
|
-
/** The world's name, shown as the published site's title (every variant and the landing page). */
|
|
155
|
-
title: z.ZodOptional<z.ZodString>;
|
|
156
|
-
entityTypes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
157
|
-
/** Named filter queries (render-pipeline's BuildVariantQuery) — PROJECT_CONFIG.md #3. */
|
|
158
|
-
buildVariants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
159
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
160
|
-
dateRange: z.ZodOptional<z.ZodObject<{
|
|
161
|
-
from: z.ZodOptional<z.ZodObject<{
|
|
162
|
-
year: z.ZodNumber;
|
|
163
|
-
day: z.ZodOptional<z.ZodNumber>;
|
|
164
|
-
}, "strip", z.ZodTypeAny, {
|
|
165
|
-
year: number;
|
|
166
|
-
day?: number | undefined;
|
|
167
|
-
}, {
|
|
168
|
-
year: number;
|
|
169
|
-
day?: number | undefined;
|
|
170
|
-
}>>;
|
|
171
|
-
to: z.ZodOptional<z.ZodObject<{
|
|
172
|
-
year: z.ZodNumber;
|
|
173
|
-
day: z.ZodOptional<z.ZodNumber>;
|
|
174
|
-
}, "strip", z.ZodTypeAny, {
|
|
175
|
-
year: number;
|
|
176
|
-
day?: number | undefined;
|
|
177
|
-
}, {
|
|
178
|
-
year: number;
|
|
179
|
-
day?: number | undefined;
|
|
180
|
-
}>>;
|
|
181
|
-
}, "strip", z.ZodTypeAny, {
|
|
182
|
-
from?: {
|
|
183
|
-
year: number;
|
|
184
|
-
day?: number | undefined;
|
|
185
|
-
} | undefined;
|
|
186
|
-
to?: {
|
|
187
|
-
year: number;
|
|
188
|
-
day?: number | undefined;
|
|
189
|
-
} | undefined;
|
|
190
|
-
}, {
|
|
191
|
-
from?: {
|
|
192
|
-
year: number;
|
|
193
|
-
day?: number | undefined;
|
|
194
|
-
} | undefined;
|
|
195
|
-
to?: {
|
|
196
|
-
year: number;
|
|
197
|
-
day?: number | undefined;
|
|
198
|
-
} | undefined;
|
|
199
|
-
}>>;
|
|
200
|
-
}, "strip", z.ZodTypeAny, {
|
|
201
|
-
tags?: string[] | undefined;
|
|
202
|
-
dateRange?: {
|
|
203
|
-
from?: {
|
|
204
|
-
year: number;
|
|
205
|
-
day?: number | undefined;
|
|
206
|
-
} | undefined;
|
|
207
|
-
to?: {
|
|
208
|
-
year: number;
|
|
209
|
-
day?: number | undefined;
|
|
210
|
-
} | undefined;
|
|
211
|
-
} | undefined;
|
|
212
|
-
}, {
|
|
213
|
-
tags?: string[] | undefined;
|
|
214
|
-
dateRange?: {
|
|
215
|
-
from?: {
|
|
216
|
-
year: number;
|
|
217
|
-
day?: number | undefined;
|
|
218
|
-
} | undefined;
|
|
219
|
-
to?: {
|
|
220
|
-
year: number;
|
|
221
|
-
day?: number | undefined;
|
|
222
|
-
} | undefined;
|
|
223
|
-
} | undefined;
|
|
224
|
-
}>>>;
|
|
225
|
-
}, z.ZodTypeAny, "passthrough">>;
|
|
22
|
+
}, z.core.$strip>>;
|
|
23
|
+
}, z.core.$strip>>;
|
|
24
|
+
}, z.core.$strip>>>;
|
|
25
|
+
}, z.core.$loose>;
|
|
226
26
|
export type WorldConfig = z.infer<typeof worldConfigSchema>;
|
|
227
27
|
export declare const DEFAULT_WORLD_TITLE = "World Wiki";
|
|
228
28
|
/** The world's display title, falling back to a neutral default (never Quartz's own "Quartz 5"). */
|
|
@@ -4,7 +4,7 @@ import { buildVariantQuerySchema } from "../query/index.js";
|
|
|
4
4
|
* A vault's own project-specific setup parameters (PROJECT_CONFIG.md).
|
|
5
5
|
* `title`, `entityTypes` and `buildVariants` are the fields with a decided consumer
|
|
6
6
|
* so far — tag vocabulary itself (as opposed to where it's used, buildVariants)
|
|
7
|
-
* and relation-field naming are still open, so this stays
|
|
7
|
+
* and relation-field naming are still open, so this stays loose (unknown keys kept) rather
|
|
8
8
|
* than locking in a shape for those prematurely.
|
|
9
9
|
*/
|
|
10
10
|
export const worldConfigSchema = z
|
|
@@ -15,7 +15,7 @@ export const worldConfigSchema = z
|
|
|
15
15
|
/** Named filter queries (render-pipeline's BuildVariantQuery) — PROJECT_CONFIG.md #3. */
|
|
16
16
|
buildVariants: z.record(z.string(), buildVariantQuerySchema).optional(),
|
|
17
17
|
})
|
|
18
|
-
.
|
|
18
|
+
.loose();
|
|
19
19
|
export const DEFAULT_WORLD_TITLE = "World Wiki";
|
|
20
20
|
/** The world's display title, falling back to a neutral default (never Quartz's own "Quartz 5"). */
|
|
21
21
|
export function worldTitle(config) {
|
|
@@ -2,54 +2,18 @@ import { z } from "zod";
|
|
|
2
2
|
export declare const dateBoundSchema: z.ZodObject<{
|
|
3
3
|
year: z.ZodNumber;
|
|
4
4
|
day: z.ZodOptional<z.ZodNumber>;
|
|
5
|
-
},
|
|
6
|
-
year: number;
|
|
7
|
-
day?: number | undefined;
|
|
8
|
-
}, {
|
|
9
|
-
year: number;
|
|
10
|
-
day?: number | undefined;
|
|
11
|
-
}>;
|
|
5
|
+
}, z.core.$strip>;
|
|
12
6
|
export type DateBound = z.infer<typeof dateBoundSchema>;
|
|
13
7
|
export declare const dateRangeQuerySchema: z.ZodObject<{
|
|
14
8
|
from: z.ZodOptional<z.ZodObject<{
|
|
15
9
|
year: z.ZodNumber;
|
|
16
10
|
day: z.ZodOptional<z.ZodNumber>;
|
|
17
|
-
},
|
|
18
|
-
year: number;
|
|
19
|
-
day?: number | undefined;
|
|
20
|
-
}, {
|
|
21
|
-
year: number;
|
|
22
|
-
day?: number | undefined;
|
|
23
|
-
}>>;
|
|
11
|
+
}, z.core.$strip>>;
|
|
24
12
|
to: z.ZodOptional<z.ZodObject<{
|
|
25
13
|
year: z.ZodNumber;
|
|
26
14
|
day: z.ZodOptional<z.ZodNumber>;
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
day?: number | undefined;
|
|
30
|
-
}, {
|
|
31
|
-
year: number;
|
|
32
|
-
day?: number | undefined;
|
|
33
|
-
}>>;
|
|
34
|
-
}, "strip", z.ZodTypeAny, {
|
|
35
|
-
from?: {
|
|
36
|
-
year: number;
|
|
37
|
-
day?: number | undefined;
|
|
38
|
-
} | undefined;
|
|
39
|
-
to?: {
|
|
40
|
-
year: number;
|
|
41
|
-
day?: number | undefined;
|
|
42
|
-
} | undefined;
|
|
43
|
-
}, {
|
|
44
|
-
from?: {
|
|
45
|
-
year: number;
|
|
46
|
-
day?: number | undefined;
|
|
47
|
-
} | undefined;
|
|
48
|
-
to?: {
|
|
49
|
-
year: number;
|
|
50
|
-
day?: number | undefined;
|
|
51
|
-
} | undefined;
|
|
52
|
-
}>;
|
|
15
|
+
}, z.core.$strip>>;
|
|
16
|
+
}, z.core.$strip>;
|
|
53
17
|
export type DateRangeQuery = z.infer<typeof dateRangeQuerySchema>;
|
|
54
18
|
/** An in-world date, as stored in an event's `date` field. */
|
|
55
19
|
export interface DateTuple {
|
|
@@ -6,70 +6,16 @@ import { z } from "zod";
|
|
|
6
6
|
* its `buildVariants` entries against — one definition, no separate DSL to parse.
|
|
7
7
|
*/
|
|
8
8
|
export declare const buildVariantQuerySchema: z.ZodObject<{
|
|
9
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString
|
|
9
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
10
10
|
dateRange: z.ZodOptional<z.ZodObject<{
|
|
11
11
|
from: z.ZodOptional<z.ZodObject<{
|
|
12
12
|
year: z.ZodNumber;
|
|
13
13
|
day: z.ZodOptional<z.ZodNumber>;
|
|
14
|
-
},
|
|
15
|
-
year: number;
|
|
16
|
-
day?: number | undefined;
|
|
17
|
-
}, {
|
|
18
|
-
year: number;
|
|
19
|
-
day?: number | undefined;
|
|
20
|
-
}>>;
|
|
14
|
+
}, z.core.$strip>>;
|
|
21
15
|
to: z.ZodOptional<z.ZodObject<{
|
|
22
16
|
year: z.ZodNumber;
|
|
23
17
|
day: z.ZodOptional<z.ZodNumber>;
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}, {
|
|
28
|
-
year: number;
|
|
29
|
-
day?: number | undefined;
|
|
30
|
-
}>>;
|
|
31
|
-
}, "strip", z.ZodTypeAny, {
|
|
32
|
-
from?: {
|
|
33
|
-
year: number;
|
|
34
|
-
day?: number | undefined;
|
|
35
|
-
} | undefined;
|
|
36
|
-
to?: {
|
|
37
|
-
year: number;
|
|
38
|
-
day?: number | undefined;
|
|
39
|
-
} | undefined;
|
|
40
|
-
}, {
|
|
41
|
-
from?: {
|
|
42
|
-
year: number;
|
|
43
|
-
day?: number | undefined;
|
|
44
|
-
} | undefined;
|
|
45
|
-
to?: {
|
|
46
|
-
year: number;
|
|
47
|
-
day?: number | undefined;
|
|
48
|
-
} | undefined;
|
|
49
|
-
}>>;
|
|
50
|
-
}, "strip", z.ZodTypeAny, {
|
|
51
|
-
tags?: string[] | undefined;
|
|
52
|
-
dateRange?: {
|
|
53
|
-
from?: {
|
|
54
|
-
year: number;
|
|
55
|
-
day?: number | undefined;
|
|
56
|
-
} | undefined;
|
|
57
|
-
to?: {
|
|
58
|
-
year: number;
|
|
59
|
-
day?: number | undefined;
|
|
60
|
-
} | undefined;
|
|
61
|
-
} | undefined;
|
|
62
|
-
}, {
|
|
63
|
-
tags?: string[] | undefined;
|
|
64
|
-
dateRange?: {
|
|
65
|
-
from?: {
|
|
66
|
-
year: number;
|
|
67
|
-
day?: number | undefined;
|
|
68
|
-
} | undefined;
|
|
69
|
-
to?: {
|
|
70
|
-
year: number;
|
|
71
|
-
day?: number | undefined;
|
|
72
|
-
} | undefined;
|
|
73
|
-
} | undefined;
|
|
74
|
-
}>;
|
|
18
|
+
}, z.core.$strip>>;
|
|
19
|
+
}, z.core.$strip>>;
|
|
20
|
+
}, z.core.$strip>;
|
|
75
21
|
export type BuildVariantQuery = z.infer<typeof buildVariantQuerySchema>;
|
|
@@ -1,8 +1,34 @@
|
|
|
1
|
+
/** Example `wbf create` inputs, one per kind. A test validates them, so they can't drift from the schema either. */
|
|
2
|
+
export declare const SCHEMA_EXAMPLES: {
|
|
3
|
+
entity: {
|
|
4
|
+
frontmatter: {
|
|
5
|
+
type: string;
|
|
6
|
+
entityType: string;
|
|
7
|
+
title: string;
|
|
8
|
+
tags: string[];
|
|
9
|
+
home: string;
|
|
10
|
+
};
|
|
11
|
+
body: string;
|
|
12
|
+
};
|
|
13
|
+
event: {
|
|
14
|
+
frontmatter: {
|
|
15
|
+
type: string;
|
|
16
|
+
title: string;
|
|
17
|
+
date: {
|
|
18
|
+
year: number;
|
|
19
|
+
day: number;
|
|
20
|
+
};
|
|
21
|
+
after: string[];
|
|
22
|
+
entities: string[];
|
|
23
|
+
};
|
|
24
|
+
body: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
1
27
|
/**
|
|
2
|
-
* Prints the live, current schema: field names
|
|
3
|
-
* schemas in fields.ts, so this can't structurally drift from what
|
|
28
|
+
* Prints the live, current schema: field names and value shapes come straight
|
|
29
|
+
* from the zod schemas in fields.ts, so this can't structurally drift from what
|
|
4
30
|
* validateFrontmatter actually enforces. This is what the CLI's `schema`
|
|
5
|
-
* command exposes so
|
|
31
|
+
* command exposes so an agent always sees the real, current rules — see
|
|
6
32
|
* claude-dev/PROJECT_STRUCTURE.md.
|
|
7
33
|
*/
|
|
8
34
|
export declare function describeSchema(entityTypeVocabulary?: string[]): string;
|
|
@@ -1,45 +1,92 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { commonFieldsSchema, entityFrontmatterSchema, eventFrontmatterSchema, wikilinkSchema, } from "./fields.js";
|
|
2
3
|
const FIELD_MEANINGS = {
|
|
3
|
-
type:
|
|
4
|
-
title: "Human-readable display name (
|
|
4
|
+
type: "Top-level kind.",
|
|
5
|
+
title: "Human-readable display name (filenames are UUIDs, this is the real name).",
|
|
5
6
|
tags: "Project-defined tags used for build-time filtering.",
|
|
6
7
|
aliases: "Alternate names/spellings, for search recall and Obsidian autocomplete only — not used for link resolution.",
|
|
7
8
|
entityType: "Category (e.g. character, location). Lint-checked against this vault's declared vocabulary, if any.",
|
|
8
9
|
supersedes: "Marks this file as a new version of an existing entity — points at the file it replaces.",
|
|
9
10
|
causedBy: "The event responsible for this version existing. Required alongside supersedes, and vice versa.",
|
|
10
11
|
after: "Causal predecessor event(s) — the required ordering/branching backbone.",
|
|
11
|
-
date: "
|
|
12
|
+
date: "In-world date anchor. Give both year and day.",
|
|
12
13
|
entities: "The entities this event is tied to.",
|
|
13
14
|
};
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
/** Example `wbf create` inputs, one per kind. A test validates them, so they can't drift from the schema either. */
|
|
16
|
+
export const SCHEMA_EXAMPLES = {
|
|
17
|
+
entity: {
|
|
18
|
+
frontmatter: {
|
|
19
|
+
type: "entity",
|
|
20
|
+
entityType: "character",
|
|
21
|
+
title: "Alea Marrow",
|
|
22
|
+
tags: ["player"],
|
|
23
|
+
home: "[[66a7bd3f-a744-4565-acbe-006957625792|Port Kavas]]",
|
|
24
|
+
},
|
|
25
|
+
body: "Captain of the *Gull*.",
|
|
26
|
+
},
|
|
27
|
+
event: {
|
|
28
|
+
frontmatter: {
|
|
29
|
+
type: "event",
|
|
30
|
+
title: "The Great Storm",
|
|
31
|
+
date: { year: 150, day: 200 },
|
|
32
|
+
after: ["[[bb1ddebd-3a7c-4b6c-847a-b4de59a71e66|Alea takes command of the Gull]]"],
|
|
33
|
+
entities: ["[[adfdd8db-0bf1-4053-928f-52709232859f|Alea Marrow]]"],
|
|
34
|
+
},
|
|
35
|
+
body: "A three-day storm floods the lower docks.",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
/** A schema's value shape in words, read from the zod schema itself. */
|
|
39
|
+
function describeShape(schema) {
|
|
40
|
+
if (schema === wikilinkSchema)
|
|
41
|
+
return "link";
|
|
42
|
+
if (schema instanceof z.ZodOptional)
|
|
43
|
+
return describeShape(schema.unwrap());
|
|
44
|
+
if (schema instanceof z.ZodLiteral)
|
|
45
|
+
return schema.values.size === 1 ? JSON.stringify([...schema.values][0]) : "literal";
|
|
46
|
+
if (schema instanceof z.ZodString)
|
|
47
|
+
return "string";
|
|
48
|
+
if (schema instanceof z.ZodNumber)
|
|
49
|
+
return schema.isInt ? "integer" : "number";
|
|
50
|
+
if (schema instanceof z.ZodArray)
|
|
51
|
+
return `list of ${describeShape(schema.element)}s`;
|
|
52
|
+
if (schema instanceof z.ZodObject) {
|
|
53
|
+
const keys = Object.entries(schema.shape).map(([key, value]) => `${key}${value instanceof z.ZodOptional ? "?" : ""}: ${describeShape(value)}`);
|
|
54
|
+
return `{ ${keys.join(", ")} }`;
|
|
55
|
+
}
|
|
56
|
+
return "value";
|
|
16
57
|
}
|
|
17
|
-
function describeFields(names) {
|
|
18
|
-
return names.map((name) =>
|
|
58
|
+
function describeFields(shape, names) {
|
|
59
|
+
return names.map((name) => {
|
|
60
|
+
const schema = shape[name];
|
|
61
|
+
const required = schema instanceof z.ZodOptional ? "optional" : "required";
|
|
62
|
+
return ` - ${name} (${describeShape(schema)}, ${required}): ${FIELD_MEANINGS[name] ?? "(see SCHEMA.md)"}`;
|
|
63
|
+
});
|
|
19
64
|
}
|
|
20
65
|
/**
|
|
21
|
-
* Prints the live, current schema: field names
|
|
22
|
-
* schemas in fields.ts, so this can't structurally drift from what
|
|
66
|
+
* Prints the live, current schema: field names and value shapes come straight
|
|
67
|
+
* from the zod schemas in fields.ts, so this can't structurally drift from what
|
|
23
68
|
* validateFrontmatter actually enforces. This is what the CLI's `schema`
|
|
24
|
-
* command exposes so
|
|
69
|
+
* command exposes so an agent always sees the real, current rules — see
|
|
25
70
|
* claude-dev/PROJECT_STRUCTURE.md.
|
|
26
71
|
*/
|
|
27
72
|
export function describeSchema(entityTypeVocabulary) {
|
|
28
|
-
const commonKeys =
|
|
73
|
+
const commonKeys = Object.keys(commonFieldsSchema.shape);
|
|
29
74
|
const excluded = new Set([...commonKeys, "type"]);
|
|
30
|
-
const entityKeys =
|
|
31
|
-
const eventKeys =
|
|
75
|
+
const entityKeys = Object.keys(entityFrontmatterSchema.shape).filter((key) => !excluded.has(key));
|
|
76
|
+
const eventKeys = Object.keys(eventFrontmatterSchema.shape).filter((key) => !excluded.has(key));
|
|
32
77
|
const lines = [
|
|
33
78
|
"Common fields (every file):",
|
|
34
|
-
|
|
79
|
+
` - type ("entity" or "event", required): ${FIELD_MEANINGS.type}`,
|
|
80
|
+
...describeFields(commonFieldsSchema.shape, commonKeys),
|
|
35
81
|
"",
|
|
36
82
|
"Entity-only fields:",
|
|
37
|
-
...describeFields(entityKeys),
|
|
83
|
+
...describeFields(entityFrontmatterSchema.shape, entityKeys),
|
|
38
84
|
"",
|
|
39
85
|
"Event-only fields:",
|
|
40
|
-
...describeFields(eventKeys),
|
|
86
|
+
...describeFields(eventFrontmatterSchema.shape, eventKeys),
|
|
41
87
|
"",
|
|
42
|
-
'
|
|
88
|
+
'A link is a string "[[uuid|Display Name]]". A list field takes a list even for one link: after: ["[[uuid|Name]]"].',
|
|
89
|
+
"Any other frontmatter field whose value is a link is automatically a relation.",
|
|
43
90
|
];
|
|
44
91
|
if (entityTypeVocabulary && entityTypeVocabulary.length > 0) {
|
|
45
92
|
lines.push("", `This vault's declared entityType vocabulary: ${entityTypeVocabulary.join(", ")}`);
|
|
@@ -47,5 +94,6 @@ export function describeSchema(entityTypeVocabulary) {
|
|
|
47
94
|
else {
|
|
48
95
|
lines.push("", "This vault has not declared an entityType vocabulary — any entityType value is accepted.");
|
|
49
96
|
}
|
|
97
|
+
lines.push("", "Example input for `wbf create` (replace the UUIDs with real ids, e.g. from `wbf search`):", ` entity: ${JSON.stringify(SCHEMA_EXAMPLES.entity)}`, ` event: ${JSON.stringify(SCHEMA_EXAMPLES.event)}`);
|
|
50
98
|
return lines.join("\n");
|
|
51
99
|
}
|
|
@@ -1,249 +1,57 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
/** Any field whose value looks like this is auto-detected as a graph edge (SCHEMA.md rule 2) — no fixed vocabulary of relation-field names. */
|
|
3
|
-
export declare const wikilinkSchema: z.
|
|
3
|
+
export declare const wikilinkSchema: z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>;
|
|
4
4
|
export declare const inWorldDateSchema: z.ZodObject<{
|
|
5
5
|
year: z.ZodNumber;
|
|
6
6
|
day: z.ZodNumber;
|
|
7
|
-
},
|
|
8
|
-
year: number;
|
|
9
|
-
day: number;
|
|
10
|
-
}, {
|
|
11
|
-
year: number;
|
|
12
|
-
day: number;
|
|
13
|
-
}>;
|
|
7
|
+
}, z.core.$strip>;
|
|
14
8
|
/** Fields every knowledge file carries (SCHEMA.md "Common frontmatter"). */
|
|
15
9
|
export declare const commonFieldsSchema: z.ZodObject<{
|
|
16
10
|
title: z.ZodString;
|
|
17
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString
|
|
18
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString
|
|
19
|
-
},
|
|
20
|
-
title: string;
|
|
21
|
-
tags?: string[] | undefined;
|
|
22
|
-
aliases?: string[] | undefined;
|
|
23
|
-
}, {
|
|
24
|
-
title: string;
|
|
25
|
-
tags?: string[] | undefined;
|
|
26
|
-
aliases?: string[] | undefined;
|
|
27
|
-
}>;
|
|
11
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
12
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
13
|
+
}, z.core.$strip>;
|
|
28
14
|
export declare const entityFrontmatterSchema: z.ZodObject<{
|
|
29
15
|
title: z.ZodString;
|
|
30
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString
|
|
31
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString
|
|
32
|
-
} & {
|
|
16
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
17
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
33
18
|
type: z.ZodLiteral<"entity">;
|
|
34
19
|
entityType: z.ZodOptional<z.ZodString>;
|
|
35
|
-
supersedes: z.ZodOptional<z.
|
|
36
|
-
causedBy: z.ZodOptional<z.
|
|
37
|
-
},
|
|
38
|
-
title: z.ZodString;
|
|
39
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
40
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
41
|
-
} & {
|
|
42
|
-
type: z.ZodLiteral<"entity">;
|
|
43
|
-
entityType: z.ZodOptional<z.ZodString>;
|
|
44
|
-
supersedes: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
45
|
-
causedBy: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
46
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
47
|
-
title: z.ZodString;
|
|
48
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
49
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
50
|
-
} & {
|
|
51
|
-
type: z.ZodLiteral<"entity">;
|
|
52
|
-
entityType: z.ZodOptional<z.ZodString>;
|
|
53
|
-
supersedes: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
54
|
-
causedBy: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
55
|
-
}, z.ZodTypeAny, "passthrough">>;
|
|
20
|
+
supersedes: z.ZodOptional<z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>>;
|
|
21
|
+
causedBy: z.ZodOptional<z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>>;
|
|
22
|
+
}, z.core.$loose>;
|
|
56
23
|
export declare const eventFrontmatterSchema: z.ZodObject<{
|
|
57
24
|
title: z.ZodString;
|
|
58
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString
|
|
59
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString
|
|
60
|
-
} & {
|
|
61
|
-
type: z.ZodLiteral<"event">;
|
|
62
|
-
after: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
63
|
-
date: z.ZodOptional<z.ZodObject<{
|
|
64
|
-
year: z.ZodNumber;
|
|
65
|
-
day: z.ZodNumber;
|
|
66
|
-
}, "strip", z.ZodTypeAny, {
|
|
67
|
-
year: number;
|
|
68
|
-
day: number;
|
|
69
|
-
}, {
|
|
70
|
-
year: number;
|
|
71
|
-
day: number;
|
|
72
|
-
}>>;
|
|
73
|
-
entities: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
74
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
75
|
-
title: z.ZodString;
|
|
76
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
77
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
78
|
-
} & {
|
|
79
|
-
type: z.ZodLiteral<"event">;
|
|
80
|
-
after: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
81
|
-
date: z.ZodOptional<z.ZodObject<{
|
|
82
|
-
year: z.ZodNumber;
|
|
83
|
-
day: z.ZodNumber;
|
|
84
|
-
}, "strip", z.ZodTypeAny, {
|
|
85
|
-
year: number;
|
|
86
|
-
day: number;
|
|
87
|
-
}, {
|
|
88
|
-
year: number;
|
|
89
|
-
day: number;
|
|
90
|
-
}>>;
|
|
91
|
-
entities: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
92
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
93
|
-
title: z.ZodString;
|
|
94
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
95
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
96
|
-
} & {
|
|
97
|
-
type: z.ZodLiteral<"event">;
|
|
98
|
-
after: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
99
|
-
date: z.ZodOptional<z.ZodObject<{
|
|
100
|
-
year: z.ZodNumber;
|
|
101
|
-
day: z.ZodNumber;
|
|
102
|
-
}, "strip", z.ZodTypeAny, {
|
|
103
|
-
year: number;
|
|
104
|
-
day: number;
|
|
105
|
-
}, {
|
|
106
|
-
year: number;
|
|
107
|
-
day: number;
|
|
108
|
-
}>>;
|
|
109
|
-
entities: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
110
|
-
}, z.ZodTypeAny, "passthrough">>;
|
|
111
|
-
export declare const knowledgeFileFrontmatterSchema: z.ZodEffects<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
112
|
-
title: z.ZodString;
|
|
113
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
114
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
115
|
-
} & {
|
|
116
|
-
type: z.ZodLiteral<"entity">;
|
|
117
|
-
entityType: z.ZodOptional<z.ZodString>;
|
|
118
|
-
supersedes: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
119
|
-
causedBy: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
120
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
121
|
-
title: z.ZodString;
|
|
122
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
123
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
124
|
-
} & {
|
|
125
|
-
type: z.ZodLiteral<"entity">;
|
|
126
|
-
entityType: z.ZodOptional<z.ZodString>;
|
|
127
|
-
supersedes: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
128
|
-
causedBy: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
129
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
130
|
-
title: z.ZodString;
|
|
131
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
132
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
133
|
-
} & {
|
|
134
|
-
type: z.ZodLiteral<"entity">;
|
|
135
|
-
entityType: z.ZodOptional<z.ZodString>;
|
|
136
|
-
supersedes: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
137
|
-
causedBy: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
138
|
-
}, z.ZodTypeAny, "passthrough">>, z.ZodObject<{
|
|
139
|
-
title: z.ZodString;
|
|
140
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
141
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
142
|
-
} & {
|
|
143
|
-
type: z.ZodLiteral<"event">;
|
|
144
|
-
after: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
145
|
-
date: z.ZodOptional<z.ZodObject<{
|
|
146
|
-
year: z.ZodNumber;
|
|
147
|
-
day: z.ZodNumber;
|
|
148
|
-
}, "strip", z.ZodTypeAny, {
|
|
149
|
-
year: number;
|
|
150
|
-
day: number;
|
|
151
|
-
}, {
|
|
152
|
-
year: number;
|
|
153
|
-
day: number;
|
|
154
|
-
}>>;
|
|
155
|
-
entities: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
156
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
157
|
-
title: z.ZodString;
|
|
158
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
159
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
160
|
-
} & {
|
|
161
|
-
type: z.ZodLiteral<"event">;
|
|
162
|
-
after: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
163
|
-
date: z.ZodOptional<z.ZodObject<{
|
|
164
|
-
year: z.ZodNumber;
|
|
165
|
-
day: z.ZodNumber;
|
|
166
|
-
}, "strip", z.ZodTypeAny, {
|
|
167
|
-
year: number;
|
|
168
|
-
day: number;
|
|
169
|
-
}, {
|
|
170
|
-
year: number;
|
|
171
|
-
day: number;
|
|
172
|
-
}>>;
|
|
173
|
-
entities: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
174
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
175
|
-
title: z.ZodString;
|
|
176
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
177
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
178
|
-
} & {
|
|
179
|
-
type: z.ZodLiteral<"event">;
|
|
180
|
-
after: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
181
|
-
date: z.ZodOptional<z.ZodObject<{
|
|
182
|
-
year: z.ZodNumber;
|
|
183
|
-
day: z.ZodNumber;
|
|
184
|
-
}, "strip", z.ZodTypeAny, {
|
|
185
|
-
year: number;
|
|
186
|
-
day: number;
|
|
187
|
-
}, {
|
|
188
|
-
year: number;
|
|
189
|
-
day: number;
|
|
190
|
-
}>>;
|
|
191
|
-
entities: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
192
|
-
}, z.ZodTypeAny, "passthrough">>]>, z.objectOutputType<{
|
|
193
|
-
title: z.ZodString;
|
|
194
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
195
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
196
|
-
} & {
|
|
197
|
-
type: z.ZodLiteral<"entity">;
|
|
198
|
-
entityType: z.ZodOptional<z.ZodString>;
|
|
199
|
-
supersedes: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
200
|
-
causedBy: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
201
|
-
}, z.ZodTypeAny, "passthrough"> | z.objectOutputType<{
|
|
202
|
-
title: z.ZodString;
|
|
203
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
204
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
205
|
-
} & {
|
|
25
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
26
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
206
27
|
type: z.ZodLiteral<"event">;
|
|
207
|
-
after: z.ZodOptional<z.ZodArray<z.
|
|
28
|
+
after: z.ZodOptional<z.ZodArray<z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>>>;
|
|
208
29
|
date: z.ZodOptional<z.ZodObject<{
|
|
209
30
|
year: z.ZodNumber;
|
|
210
31
|
day: z.ZodNumber;
|
|
211
|
-
},
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
year: number;
|
|
216
|
-
day: number;
|
|
217
|
-
}>>;
|
|
218
|
-
entities: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
219
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
32
|
+
}, z.core.$strip>>;
|
|
33
|
+
entities: z.ZodOptional<z.ZodArray<z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>>>;
|
|
34
|
+
}, z.core.$loose>;
|
|
35
|
+
export declare const knowledgeFileFrontmatterSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
220
36
|
title: z.ZodString;
|
|
221
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString
|
|
222
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString
|
|
223
|
-
} & {
|
|
37
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
38
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
224
39
|
type: z.ZodLiteral<"entity">;
|
|
225
40
|
entityType: z.ZodOptional<z.ZodString>;
|
|
226
|
-
supersedes: z.ZodOptional<z.
|
|
227
|
-
causedBy: z.ZodOptional<z.
|
|
228
|
-
}, z.
|
|
41
|
+
supersedes: z.ZodOptional<z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>>;
|
|
42
|
+
causedBy: z.ZodOptional<z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>>;
|
|
43
|
+
}, z.core.$loose>, z.ZodObject<{
|
|
229
44
|
title: z.ZodString;
|
|
230
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString
|
|
231
|
-
aliases: z.ZodOptional<z.ZodArray<z.ZodString
|
|
232
|
-
} & {
|
|
45
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
46
|
+
aliases: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
233
47
|
type: z.ZodLiteral<"event">;
|
|
234
|
-
after: z.ZodOptional<z.ZodArray<z.
|
|
48
|
+
after: z.ZodOptional<z.ZodArray<z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>>>;
|
|
235
49
|
date: z.ZodOptional<z.ZodObject<{
|
|
236
50
|
year: z.ZodNumber;
|
|
237
51
|
day: z.ZodNumber;
|
|
238
|
-
},
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}, {
|
|
242
|
-
year: number;
|
|
243
|
-
day: number;
|
|
244
|
-
}>>;
|
|
245
|
-
entities: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
246
|
-
}, z.ZodTypeAny, "passthrough">>;
|
|
52
|
+
}, z.core.$strip>>;
|
|
53
|
+
entities: z.ZodOptional<z.ZodArray<z.ZodString & z.ZodType<string, string, z.core.$ZodTypeInternals<string, string>>>>;
|
|
54
|
+
}, z.core.$loose>], "type">;
|
|
247
55
|
export type EntityFrontmatter = z.infer<typeof entityFrontmatterSchema>;
|
|
248
56
|
export type EventFrontmatter = z.infer<typeof eventFrontmatterSchema>;
|
|
249
57
|
export type KnowledgeFileFrontmatter = z.infer<typeof knowledgeFileFrontmatterSchema>;
|
|
@@ -21,7 +21,7 @@ export const entityFrontmatterSchema = commonFieldsSchema
|
|
|
21
21
|
supersedes: wikilinkSchema.optional(),
|
|
22
22
|
causedBy: wikilinkSchema.optional(),
|
|
23
23
|
})
|
|
24
|
-
.
|
|
24
|
+
.loose();
|
|
25
25
|
export const eventFrontmatterSchema = commonFieldsSchema
|
|
26
26
|
.extend({
|
|
27
27
|
type: z.literal("event"),
|
|
@@ -29,13 +29,13 @@ export const eventFrontmatterSchema = commonFieldsSchema
|
|
|
29
29
|
date: inWorldDateSchema.optional(),
|
|
30
30
|
entities: z.array(wikilinkSchema).optional(),
|
|
31
31
|
})
|
|
32
|
-
.
|
|
32
|
+
.loose();
|
|
33
33
|
export const knowledgeFileFrontmatterSchema = z
|
|
34
34
|
.discriminatedUnion("type", [entityFrontmatterSchema, eventFrontmatterSchema])
|
|
35
35
|
.superRefine((data, ctx) => {
|
|
36
36
|
if (data.type === "entity" && Boolean(data.supersedes) !== Boolean(data.causedBy)) {
|
|
37
37
|
ctx.addIssue({
|
|
38
|
-
code:
|
|
38
|
+
code: "custom",
|
|
39
39
|
message: '"supersedes" and "causedBy" must both be present, or both absent',
|
|
40
40
|
path: ["causedBy"],
|
|
41
41
|
});
|
|
@@ -4,6 +4,9 @@ import type { BuildVariantQuery, RenderedPage } from "./types.js";
|
|
|
4
4
|
* Stage 1: vault -> derived, Quartz-ready page set (RENDER_PIPELINE.md). Filters
|
|
5
5
|
* by the build variant's query, merges each included entity's version history
|
|
6
6
|
* into one page, and rewrites every link to a slug (or strips it if the target
|
|
7
|
-
* isn't included).
|
|
7
|
+
* isn't included). Adds a start page listing everything included, titled
|
|
8
|
+
* `options.title` (the world's title).
|
|
8
9
|
*/
|
|
9
|
-
export declare function buildDerivedTree(files: VaultFile[], query: BuildVariantQuery
|
|
10
|
+
export declare function buildDerivedTree(files: VaultFile[], query: BuildVariantQuery, options?: {
|
|
11
|
+
title?: string;
|
|
12
|
+
}): RenderedPage[];
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { buildGraph } from "../core/graph/index.js";
|
|
1
|
+
import { buildGraph, topologicalOrder } from "../core/graph/index.js";
|
|
2
2
|
import { filterEvents, filterEntityAnchors } from "./filter.js";
|
|
3
3
|
import { mergeEntityBody } from "./merge.js";
|
|
4
4
|
import { assignSlugs } from "./slugs.js";
|
|
5
5
|
import { rewriteLinks, rewriteFrontmatterLinks } from "./rewrite.js";
|
|
6
6
|
import { renameQuartzReservedFields } from "./quartz-compat.js";
|
|
7
|
+
import { buildStartPage, START_PAGE_SLUG } from "./start-page.js";
|
|
7
8
|
function titleOf(file) {
|
|
8
9
|
return typeof file.frontmatter.title === "string" ? file.frontmatter.title : file.id;
|
|
9
10
|
}
|
|
@@ -11,20 +12,22 @@ function titleOf(file) {
|
|
|
11
12
|
* Stage 1: vault -> derived, Quartz-ready page set (RENDER_PIPELINE.md). Filters
|
|
12
13
|
* by the build variant's query, merges each included entity's version history
|
|
13
14
|
* into one page, and rewrites every link to a slug (or strips it if the target
|
|
14
|
-
* isn't included).
|
|
15
|
+
* isn't included). Adds a start page listing everything included, titled
|
|
16
|
+
* `options.title` (the world's title).
|
|
15
17
|
*/
|
|
16
|
-
export function buildDerivedTree(files, query) {
|
|
18
|
+
export function buildDerivedTree(files, query, options = {}) {
|
|
17
19
|
const graph = buildGraph(files);
|
|
18
20
|
const includedEventIds = filterEvents(graph, files, query);
|
|
19
21
|
const includedAnchorIds = filterEntityAnchors(graph, files, query);
|
|
20
22
|
// Sorted for a deterministic slug assignment, independent of vault read order.
|
|
21
23
|
const includedIds = [...includedEventIds, ...includedAnchorIds].sort();
|
|
22
24
|
const titles = new Map(includedIds.map((id) => [id, titleOf(graph.nodes.get(id))]));
|
|
23
|
-
const slugsById = assignSlugs(titles);
|
|
24
|
-
const
|
|
25
|
-
|
|
25
|
+
const slugsById = assignSlugs(titles, [START_PAGE_SLUG]);
|
|
26
|
+
const eventPages = [];
|
|
27
|
+
const entityPages = [];
|
|
28
|
+
for (const id of topologicalOrder(graph).filter((eventId) => includedEventIds.has(eventId))) {
|
|
26
29
|
const file = graph.nodes.get(id);
|
|
27
|
-
|
|
30
|
+
eventPages.push({
|
|
28
31
|
id,
|
|
29
32
|
slug: slugsById.get(id),
|
|
30
33
|
frontmatter: renameQuartzReservedFields(rewriteFrontmatterLinks(file.frontmatter, slugsById)),
|
|
@@ -34,12 +37,13 @@ export function buildDerivedTree(files, query) {
|
|
|
34
37
|
for (const anchorId of includedAnchorIds) {
|
|
35
38
|
const anchor = graph.nodes.get(anchorId);
|
|
36
39
|
const mergedBody = mergeEntityBody(graph, anchorId, includedEventIds);
|
|
37
|
-
|
|
40
|
+
entityPages.push({
|
|
38
41
|
id: anchorId,
|
|
39
42
|
slug: slugsById.get(anchorId),
|
|
40
43
|
frontmatter: renameQuartzReservedFields(rewriteFrontmatterLinks(anchor.frontmatter, slugsById)),
|
|
41
44
|
body: rewriteLinks(mergedBody, slugsById),
|
|
42
45
|
});
|
|
43
46
|
}
|
|
44
|
-
|
|
47
|
+
const startPage = buildStartPage(options.title ?? "World Wiki", entityPages, eventPages);
|
|
48
|
+
return [...eventPages, ...entityPages, startPage];
|
|
45
49
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Assigns a unique, readable slug per id — vault files are UUID-named (SCHEMA.md),
|
|
3
3
|
* meaningless as a published URL; this is what Stage 1 uses instead for the
|
|
4
4
|
* derived tree. Iterates `titles` in whatever order the caller provides, so pass
|
|
5
|
-
* ids in a deterministic order for reproducible builds.
|
|
5
|
+
* ids in a deterministic order for reproducible builds. No page gets a slug
|
|
6
|
+
* in `reserved` (e.g. the start page's "index").
|
|
6
7
|
*/
|
|
7
|
-
export declare function assignSlugs(titles: Map<string, string
|
|
8
|
+
export declare function assignSlugs(titles: Map<string, string>, reserved?: string[]): Map<string, string>;
|
|
@@ -10,10 +10,11 @@ function slugify(title) {
|
|
|
10
10
|
* Assigns a unique, readable slug per id — vault files are UUID-named (SCHEMA.md),
|
|
11
11
|
* meaningless as a published URL; this is what Stage 1 uses instead for the
|
|
12
12
|
* derived tree. Iterates `titles` in whatever order the caller provides, so pass
|
|
13
|
-
* ids in a deterministic order for reproducible builds.
|
|
13
|
+
* ids in a deterministic order for reproducible builds. No page gets a slug
|
|
14
|
+
* in `reserved` (e.g. the start page's "index").
|
|
14
15
|
*/
|
|
15
|
-
export function assignSlugs(titles) {
|
|
16
|
-
const used = new Set();
|
|
16
|
+
export function assignSlugs(titles, reserved = []) {
|
|
17
|
+
const used = new Set(reserved);
|
|
17
18
|
const slugs = new Map();
|
|
18
19
|
for (const [id, title] of titles) {
|
|
19
20
|
const base = slugify(title);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RenderedPage } from "./types.js";
|
|
2
|
+
/** Quartz builds a site's root page from `content/index.md`; without it, the wiki's own address is a 404. */
|
|
3
|
+
export declare const START_PAGE_SLUG = "index";
|
|
4
|
+
/**
|
|
5
|
+
* The wiki's start page: every included entity, grouped by entityType and sorted
|
|
6
|
+
* by title, then every included event in causal order. `events` must already be
|
|
7
|
+
* in that order; only pages of this build go in, so nothing filtered out shows up.
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildStartPage(title: string, entities: RenderedPage[], events: RenderedPage[]): RenderedPage;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Quartz builds a site's root page from `content/index.md`; without it, the wiki's own address is a 404. */
|
|
2
|
+
export const START_PAGE_SLUG = "index";
|
|
3
|
+
function titleOf(page) {
|
|
4
|
+
return typeof page.frontmatter.title === "string" ? page.frontmatter.title : page.slug;
|
|
5
|
+
}
|
|
6
|
+
function linkTo(page) {
|
|
7
|
+
return `[[${page.slug}|${titleOf(page)}]]`;
|
|
8
|
+
}
|
|
9
|
+
function describeDate(page) {
|
|
10
|
+
const date = page.frontmatter.worldDate;
|
|
11
|
+
if (typeof date?.year !== "number")
|
|
12
|
+
return "";
|
|
13
|
+
return typeof date.day === "number" ? ` (year ${date.year}, day ${date.day})` : ` (year ${date.year})`;
|
|
14
|
+
}
|
|
15
|
+
function heading(entityType) {
|
|
16
|
+
return entityType.charAt(0).toUpperCase() + entityType.slice(1);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The wiki's start page: every included entity, grouped by entityType and sorted
|
|
20
|
+
* by title, then every included event in causal order. `events` must already be
|
|
21
|
+
* in that order; only pages of this build go in, so nothing filtered out shows up.
|
|
22
|
+
*/
|
|
23
|
+
export function buildStartPage(title, entities, events) {
|
|
24
|
+
const groups = new Map();
|
|
25
|
+
for (const page of entities) {
|
|
26
|
+
const entityType = typeof page.frontmatter.entityType === "string" ? page.frontmatter.entityType : "";
|
|
27
|
+
groups.set(entityType, [...(groups.get(entityType) ?? []), page]);
|
|
28
|
+
}
|
|
29
|
+
// Typed groups alphabetically, untyped entities last.
|
|
30
|
+
const groupKeys = [...groups.keys()].sort((a, b) => (a === "" ? 1 : b === "" ? -1 : a.localeCompare(b)));
|
|
31
|
+
const sections = groupKeys.map((key) => {
|
|
32
|
+
const pages = groups.get(key).sort((a, b) => titleOf(a).localeCompare(titleOf(b)));
|
|
33
|
+
return `## ${key === "" ? "Other" : heading(key)}\n\n${pages.map((page) => `- ${linkTo(page)}`).join("\n")}`;
|
|
34
|
+
});
|
|
35
|
+
if (events.length > 0) {
|
|
36
|
+
sections.push(`## Events\n\n${events.map((page) => `- ${linkTo(page)}${describeDate(page)}`).join("\n")}`);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
id: START_PAGE_SLUG,
|
|
40
|
+
slug: START_PAGE_SLUG,
|
|
41
|
+
frontmatter: { title },
|
|
42
|
+
body: sections.length > 0 ? `${sections.join("\n\n")}\n` : "Nothing here yet.\n",
|
|
43
|
+
};
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "world-building-framework",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Build, search and publish fictional worlds from a vault of Markdown files: the wbf CLI and its core engine.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"commander": "^15.0.0",
|
|
37
37
|
"gray-matter": "^4.0.3",
|
|
38
38
|
"uuid": "^14.0.2",
|
|
39
|
-
"zod": "^
|
|
39
|
+
"zod": "^4.6.7"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/better-sqlite3": "^9.6.0",
|