@content-collections/core 0.1.2 → 0.3.0

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.
@@ -0,0 +1,186 @@
1
+ import { z } from 'zod';
2
+ import { ZodObject } from 'zod';
3
+ import { ZodRawShape } from 'zod';
4
+ import { ZodString } from 'zod';
5
+ import { ZodTypeAny } from 'zod';
6
+
7
+ declare type AddContent<TShape extends ZodRawShape> = TShape extends {
8
+ content: ZodTypeAny;
9
+ } ? TShape : TShape & WithContent;
10
+
11
+ export declare type AnyCollection = Collection<any, ZodRawShape, any, any, any>;
12
+
13
+ export declare type AnyConfiguration = Configuration<Array<AnyCollection>>;
14
+
15
+ export declare type Builder = Awaited<ReturnType<typeof createBuilder>>;
16
+
17
+ export declare type BuilderEvents = {
18
+ "builder:start": {
19
+ startedAt: number;
20
+ };
21
+ "builder:end": {
22
+ startedAt: number;
23
+ endedAt: number;
24
+ };
25
+ };
26
+
27
+ export declare class CollectError extends Error {
28
+ type: ErrorType$2;
29
+ constructor(type: ErrorType$2, message: string);
30
+ }
31
+
32
+ export declare type Collection<TName extends string, TShape extends ZodRawShape, TSchema, TTransformResult, TDocument> = Omit<CollectionRequest<TName, TShape, TSchema, TTransformResult, TDocument>, "schema"> & {
33
+ typeName: string;
34
+ schema: TShape;
35
+ };
36
+
37
+ declare type CollectionByName<TConfiguration extends AnyConfiguration> = {
38
+ [TCollection in TConfiguration["collections"][number] as TCollection["name"]]: TCollection;
39
+ };
40
+
41
+ declare type CollectionFile = {
42
+ data: {
43
+ content: string;
44
+ [key: string]: unknown;
45
+ };
46
+ path: string;
47
+ };
48
+
49
+ export declare type CollectionRequest<TName extends string, TShape extends ZodRawShape, TSchema, TTransformResult, TDocument> = {
50
+ /**
51
+ * The name of the collection
52
+ */
53
+ name: TName;
54
+ /**
55
+ * The name of the generated TypeScript type.
56
+ * If the typeName is undefined the pluralized name of the collection will be used.
57
+ */
58
+ typeName?: string;
59
+ schema: (z: Z) => TShape;
60
+ transform?: (context: Context, data: TSchema) => TTransformResult;
61
+ directory: string | string[];
62
+ include: string | string[];
63
+ onSuccess?: (documents: Array<TDocument>) => void | Promise<void>;
64
+ };
65
+
66
+ declare type CollectorEvents = {
67
+ "collector:read-error": {
68
+ filePath: string;
69
+ error: CollectError;
70
+ };
71
+ "collector:parse-error": {
72
+ filePath: string;
73
+ error: CollectError;
74
+ };
75
+ };
76
+
77
+ export declare type Configuration<TCollections extends Array<AnyCollection>> = {
78
+ collections: TCollections;
79
+ };
80
+
81
+ export declare class ConfigurationError extends Error {
82
+ type: ErrorType;
83
+ constructor(type: ErrorType, message: string);
84
+ }
85
+
86
+ export declare type Context = {
87
+ documents<TCollection extends AnyCollection>(collection: TCollection): Array<Schema<TCollection["schema"]>>;
88
+ };
89
+
90
+ export declare function createBuilder(configurationPath: string, options?: Options): Promise<{
91
+ sync: (modification: Modification, filePath: string) => Promise<boolean>;
92
+ build: () => Promise<void>;
93
+ watch: () => Promise<{
94
+ unsubscribe: () => Promise<void>;
95
+ }>;
96
+ on: {
97
+ <TKey extends "builder:start" | "builder:end" | "collector:read-error" | "collector:parse-error" | "transformer:validation-error" | "transformer:error" | "watcher:file-changed">(key: TKey, listener: (event: Events[TKey]) => void): void;
98
+ <TKey_1 extends "_error" | "_all">(key: TKey_1, listener: (event: SystemEvents[TKey_1]) => void): void;
99
+ };
100
+ }>;
101
+
102
+ export declare function defineCollection<TName extends string, TShape extends ZodRawShape, TSchema = Schema<TShape>, TTransformResult = never, TDocument = [TTransformResult] extends [never] ? Schema<TShape> : Awaited<TTransformResult>>(collection: CollectionRequest<TName, TShape, TSchema, TTransformResult, TDocument>): Collection<TName, TShape, TSchema, TTransformResult, TDocument>;
103
+
104
+ export declare function defineConfig<TConfig extends AnyConfiguration>(config: TConfig): TConfig;
105
+
106
+ declare type ErrorEvent = EventWithError & SystemEvent;
107
+
108
+ declare type ErrorType$1 = "Validation" | "Configuration" | "Transform";
109
+
110
+ declare type ErrorType$2 = "Parse" | "Read";
111
+
112
+ declare type ErrorType = "Read" | "Compile";
113
+
114
+ declare type Events = BuilderEvents & CollectorEvents & TransformerEvents & WatcherEvents;
115
+
116
+ declare type EventWithError = {
117
+ error: Error;
118
+ };
119
+
120
+ declare type GetDocument<TCollection extends AnyCollection> = TCollection extends Collection<any, ZodRawShape, any, any, infer TDocument> ? TDocument : never;
121
+
122
+ export declare type GetTypeByName<TConfiguration extends AnyConfiguration, TName extends keyof CollectionByName<TConfiguration>, TCollection = CollectionByName<TConfiguration>[TName]> = TCollection extends AnyCollection ? GetDocument<TCollection> : never;
123
+
124
+ export declare type Meta = {
125
+ filePath: string;
126
+ fileName: string;
127
+ directory: string;
128
+ path: string;
129
+ extension: string;
130
+ };
131
+
132
+ export declare type Modification = "create" | "update" | "delete";
133
+
134
+ declare type Options$1 = {
135
+ configName: string;
136
+ cacheDir?: string;
137
+ };
138
+
139
+ declare type Options = Options$1 & {
140
+ outputDir?: string;
141
+ };
142
+
143
+ export declare type Schema<TShape extends ZodRawShape> = z.infer<ZodObject<AddContent<TShape>>> & {
144
+ _meta: Meta;
145
+ };
146
+
147
+ declare type SystemEvent = {
148
+ _event: string;
149
+ };
150
+
151
+ declare type SystemEvents = {
152
+ _error: ErrorEvent;
153
+ _all: SystemEvent;
154
+ };
155
+
156
+ declare type TransformerEvents = {
157
+ "transformer:validation-error": {
158
+ collection: AnyCollection;
159
+ file: CollectionFile;
160
+ error: TransformError;
161
+ };
162
+ "transformer:error": {
163
+ collection: AnyCollection;
164
+ error: TransformError;
165
+ };
166
+ };
167
+
168
+ export declare class TransformError extends Error {
169
+ type: ErrorType$1;
170
+ constructor(type: ErrorType$1, message: string);
171
+ }
172
+
173
+ declare type WatcherEvents = {
174
+ "watcher:file-changed": {
175
+ filePath: string;
176
+ modification: Modification;
177
+ };
178
+ };
179
+
180
+ declare type WithContent = {
181
+ content: ZodString;
182
+ };
183
+
184
+ declare type Z = typeof z;
185
+
186
+ export { }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,27 @@
1
1
  import { ZodRawShape, z, ZodObject, ZodTypeAny, ZodString } from 'zod';
2
+ import { parse } from 'yaml';
3
+
4
+ type Parsers = typeof parsers;
5
+ type Parser = keyof typeof parsers;
6
+ declare function frontmatterParser(fileContent: string): {
7
+ content: string;
8
+ };
9
+ declare const parsers: {
10
+ readonly frontmatter: {
11
+ readonly hasContent: true;
12
+ readonly parse: typeof frontmatterParser;
13
+ };
14
+ readonly json: {
15
+ readonly hasContent: false;
16
+ readonly parse: (text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined) => any;
17
+ };
18
+ readonly yaml: {
19
+ readonly hasContent: false;
20
+ readonly parse: typeof parse;
21
+ };
22
+ };
23
+
24
+ type CacheFn = <TInput, TOutput>(input: TInput, compute: (input: TInput) => Promise<TOutput> | TOutput) => Promise<TOutput>;
2
25
 
3
26
  type Meta = {
4
27
  filePath: string;
@@ -13,30 +36,37 @@ type WithContent = {
13
36
  type AddContent<TShape extends ZodRawShape> = TShape extends {
14
37
  content: ZodTypeAny;
15
38
  } ? TShape : TShape & WithContent;
16
- type Schema<TShape extends ZodRawShape> = z.infer<ZodObject<AddContent<TShape>>> & {
39
+ type GetParsedShape<TParser extends Parser, TShape extends ZodRawShape> = Parsers[TParser]["hasContent"] extends true ? AddContent<TShape> : TShape;
40
+ type GetShape<TParser extends Parser | undefined, TShape extends ZodRawShape> = TParser extends Parser ? GetParsedShape<TParser, TShape> : AddContent<TShape>;
41
+ type Schema<TParser extends Parser | undefined, TShape extends ZodRawShape> = z.infer<ZodObject<GetShape<TParser, TShape>>> & {
17
42
  _meta: Meta;
18
43
  };
19
44
  type Context = {
20
- documents<TCollection extends AnyCollection>(collection: TCollection): Array<Schema<TCollection["schema"]>>;
45
+ documents<TCollection extends AnyCollection>(collection: TCollection): Array<Schema<TCollection["parser"], TCollection["schema"]>>;
46
+ cache: CacheFn;
21
47
  };
22
48
  type Z = typeof z;
23
- type CollectionRequest<TName extends string, TShape extends ZodRawShape, TSchema, TTransformResult, TDocument> = {
49
+ type CollectionRequest<TName extends string, TShape extends ZodRawShape, TParser, TSchema, TTransformResult, TDocument> = {
24
50
  name: TName;
51
+ parser?: TParser;
25
52
  typeName?: string;
26
53
  schema: (z: Z) => TShape;
27
- transform?: (context: Context, data: TSchema) => TTransformResult;
54
+ transform?: (data: TSchema, context: Context) => TTransformResult;
28
55
  directory: string;
29
56
  include: string | string[];
30
57
  onSuccess?: (documents: Array<TDocument>) => void | Promise<void>;
31
58
  };
32
- type Collection<TName extends string, TShape extends ZodRawShape, TSchema, TTransformResult, TDocument> = Omit<CollectionRequest<TName, TShape, TSchema, TTransformResult, TDocument>, "schema"> & {
59
+ type Collection<TName extends string, TShape extends ZodRawShape, TParser extends Parser, TSchema, TTransformResult, TDocument> = Omit<CollectionRequest<TName, TShape, TParser, TSchema, TTransformResult, TDocument>, "schema"> & {
33
60
  typeName: string;
34
61
  schema: TShape;
62
+ parser: TParser;
35
63
  };
36
- type AnyCollection = Collection<any, ZodRawShape, any, any, any>;
37
- declare function defineCollection<TName extends string, TShape extends ZodRawShape, TSchema = Schema<TShape>, TTransformResult = never, TDocument = [TTransformResult] extends [never] ? Schema<TShape> : Awaited<TTransformResult>>(collection: CollectionRequest<TName, TShape, TSchema, TTransformResult, TDocument>): Collection<TName, TShape, TSchema, TTransformResult, TDocument>;
64
+ type AnyCollection = Collection<any, ZodRawShape, Parser, any, any, any>;
65
+ declare function defineCollection<TName extends string, TShape extends ZodRawShape, TParser extends Parser = "frontmatter", TSchema = Schema<TParser, TShape>, TTransformResult = never, TDocument = [TTransformResult] extends [never] ? Schema<TParser, TShape> : Awaited<TTransformResult>>(collection: CollectionRequest<TName, TShape, TParser, TSchema, TTransformResult, TDocument>): Collection<TName, TShape, TParser, TSchema, TTransformResult, TDocument>;
66
+ type Cache = "memory" | "file" | "none";
38
67
  type Configuration<TCollections extends Array<AnyCollection>> = {
39
68
  collections: TCollections;
69
+ cache?: Cache;
40
70
  };
41
71
  type AnyConfiguration = Configuration<Array<AnyCollection>>;
42
72
  declare function defineConfig<TConfig extends AnyConfiguration>(config: TConfig): TConfig;
@@ -44,7 +74,7 @@ declare function defineConfig<TConfig extends AnyConfiguration>(config: TConfig)
44
74
  type Modification = "create" | "update" | "delete";
45
75
  type CollectionFile = {
46
76
  data: {
47
- content: string;
77
+ content?: string;
48
78
  [key: string]: unknown;
49
79
  };
50
80
  path: string;
@@ -52,7 +82,7 @@ type CollectionFile = {
52
82
  type CollectionByName<TConfiguration extends AnyConfiguration> = {
53
83
  [TCollection in TConfiguration["collections"][number] as TCollection["name"]]: TCollection;
54
84
  };
55
- type GetDocument<TCollection extends AnyCollection> = TCollection extends Collection<any, ZodRawShape, any, any, infer TDocument> ? TDocument : never;
85
+ type GetDocument<TCollection extends AnyCollection> = TCollection extends Collection<any, ZodRawShape, any, any, any, infer TDocument> ? TDocument : never;
56
86
  type GetTypeByName<TConfiguration extends AnyConfiguration, TName extends keyof CollectionByName<TConfiguration>, TCollection = CollectionByName<TConfiguration>[TName]> = TCollection extends AnyCollection ? GetDocument<TCollection> : never;
57
87
 
58
88
  type CollectorEvents = {
package/dist/index.js CHANGED
@@ -11,6 +11,9 @@ function generateTypeName(name) {
11
11
  function isDefined(value) {
12
12
  return value !== void 0 && value !== null;
13
13
  }
14
+ function orderByPath(a, b) {
15
+ return a.path.localeCompare(b.path);
16
+ }
14
17
 
15
18
  // src/config.ts
16
19
  function defineCollection(collection) {
@@ -18,9 +21,14 @@ function defineCollection(collection) {
18
21
  if (!typeName) {
19
22
  typeName = generateTypeName(collection.name);
20
23
  }
24
+ let parser = collection.parser;
25
+ if (!parser) {
26
+ parser = "frontmatter";
27
+ }
21
28
  return {
22
29
  ...collection,
23
30
  typeName,
31
+ parser,
24
32
  schema: collection.schema(z)
25
33
  };
26
34
  }
@@ -36,7 +44,7 @@ import path from "path";
36
44
  // package.json
37
45
  var package_default = {
38
46
  name: "@content-collections/core",
39
- version: "0.1.1",
47
+ version: "0.3.0",
40
48
  type: "module",
41
49
  main: "dist/index.cjs",
42
50
  types: "./dist/index.d.ts",
@@ -76,12 +84,14 @@ var package_default = {
76
84
  "gray-matter": "^4.0.3",
77
85
  micromatch: "^4.0.5",
78
86
  pluralize: "^8.0.0",
87
+ yaml: "^2.3.4",
79
88
  zod: "^3.22.4"
80
89
  }
81
90
  };
82
91
 
83
92
  // src/configurationReader.ts
84
93
  import { existsSync } from "fs";
94
+ import { createHash } from "crypto";
85
95
  var ConfigurationError = class extends Error {
86
96
  type;
87
97
  constructor(type, message) {
@@ -111,7 +121,10 @@ async function compile(configurationPath, outfile) {
111
121
  }
112
122
  await esbuild.build({
113
123
  entryPoints: [configurationPath],
114
- external: [...Object.keys(package_default.dependencies), "@content-collections/*"],
124
+ external: [
125
+ ...Object.keys(package_default.dependencies),
126
+ "@content-collections/*"
127
+ ],
115
128
  bundle: true,
116
129
  platform: "node",
117
130
  format: "esm",
@@ -141,19 +154,56 @@ function createConfigurationReader() {
141
154
  );
142
155
  }
143
156
  const module = await import(`file://${path.resolve(outfile)}?x=${Date.now()}`);
157
+ const hash = createHash("sha256");
158
+ hash.update(await fs.readFile(outfile, "utf-8"));
159
+ const checksum = hash.digest("hex");
144
160
  return {
145
161
  ...module.default,
146
162
  path: configurationPath,
147
- generateTypes: true
163
+ generateTypes: true,
164
+ checksum
148
165
  };
149
166
  };
150
167
  }
151
168
 
152
169
  // src/collector.ts
153
- import matter from "gray-matter";
154
170
  import fg from "fast-glob";
155
171
  import { readFile } from "fs/promises";
156
172
  import path2 from "path";
173
+
174
+ // src/parser.ts
175
+ import matter from "gray-matter";
176
+ import { parse, stringify } from "yaml";
177
+ function frontmatterParser(fileContent) {
178
+ const { data, content } = matter(fileContent, {
179
+ engines: {
180
+ yaml: {
181
+ parse,
182
+ stringify
183
+ }
184
+ }
185
+ });
186
+ return {
187
+ ...data,
188
+ content: content.trim()
189
+ };
190
+ }
191
+ var parsers = {
192
+ frontmatter: {
193
+ hasContent: true,
194
+ parse: frontmatterParser
195
+ },
196
+ json: {
197
+ hasContent: false,
198
+ parse: JSON.parse
199
+ },
200
+ yaml: {
201
+ hasContent: false,
202
+ parse
203
+ }
204
+ };
205
+
206
+ // src/collector.ts
157
207
  var CollectError = class extends Error {
158
208
  type;
159
209
  constructor(type, message) {
@@ -173,27 +223,21 @@ function createCollector(emitter, baseDirectory = ".") {
173
223
  return null;
174
224
  }
175
225
  }
176
- function parse(file) {
177
- const { data, content } = matter(file);
178
- return {
179
- ...data,
180
- content
181
- };
182
- }
183
- async function collectFile(directory, filePath) {
184
- const file = await read(path2.join(directory, filePath));
226
+ async function collectFile(collection, filePath) {
227
+ const absolutePath = path2.join(baseDirectory, collection.directory, filePath);
228
+ const file = await read(absolutePath);
185
229
  if (!file) {
186
230
  return null;
187
231
  }
188
232
  try {
189
- const data = parse(file);
233
+ const data = parsers[collection.parser].parse(file);
190
234
  return {
191
235
  data,
192
236
  path: filePath
193
237
  };
194
238
  } catch (error) {
195
239
  emitter.emit("collector:parse-error", {
196
- filePath: path2.join(directory, filePath),
240
+ filePath: path2.join(collection.directory, filePath),
197
241
  error: new CollectError("Parse", String(error))
198
242
  });
199
243
  return null;
@@ -207,12 +251,12 @@ function createCollector(emitter, baseDirectory = ".") {
207
251
  absolute: false
208
252
  });
209
253
  const promises = filePaths.map(
210
- (filePath) => collectFile(collectionDirectory, filePath)
254
+ (filePath) => collectFile(collection, filePath)
211
255
  );
212
256
  const files = await Promise.all(promises);
213
257
  return {
214
258
  ...collection,
215
- files: files.filter(isDefined)
259
+ files: files.filter(isDefined).sort(orderByPath)
216
260
  };
217
261
  }
218
262
  async function collect(unresolvedCollections) {
@@ -311,23 +355,27 @@ var TransformError = class extends Error {
311
355
  this.type = type;
312
356
  }
313
357
  };
314
- function createPath(path6, ext) {
315
- let p = path6.slice(0, -ext.length);
358
+ function createPath(path7, ext) {
359
+ let p = path7.slice(0, -ext.length);
316
360
  if (p.endsWith("/index")) {
317
361
  p = p.slice(0, -6);
318
362
  }
319
363
  return p;
320
364
  }
321
- function createTransformer(emitter) {
322
- function createSchema(schema) {
365
+ function createTransformer(emitter, cacheManager) {
366
+ function createSchema(parserName, schema) {
367
+ const parser = parsers[parserName];
368
+ if (!parser.hasContent) {
369
+ return z2.object(schema);
370
+ }
323
371
  return z2.object({
324
372
  content: z2.string(),
325
373
  ...schema
326
374
  });
327
375
  }
328
376
  async function parseFile(collection, file) {
329
- const { data, path: path6 } = file;
330
- const schema = createSchema(collection.schema);
377
+ const { data, path: path7 } = file;
378
+ const schema = createSchema(collection.parser, collection.schema);
331
379
  let parsedData = await schema.safeParseAsync(data);
332
380
  if (!parsedData.success) {
333
381
  emitter.emit("transformer:validation-error", {
@@ -337,7 +385,7 @@ function createTransformer(emitter) {
337
385
  });
338
386
  return null;
339
387
  }
340
- const ext = extname(path6);
388
+ const ext = extname(path7);
341
389
  let extension = ext;
342
390
  if (extension.startsWith(".")) {
343
391
  extension = extension.slice(1);
@@ -345,11 +393,11 @@ function createTransformer(emitter) {
345
393
  const document = {
346
394
  ...parsedData.data,
347
395
  _meta: {
348
- filePath: path6,
349
- fileName: basename(path6),
350
- directory: dirname(path6),
396
+ filePath: path7,
397
+ fileName: basename(path7),
398
+ directory: dirname(path7),
351
399
  extension,
352
- path: createPath(path6, ext)
400
+ path: createPath(path7, ext)
353
401
  }
354
402
  };
355
403
  return {
@@ -365,7 +413,7 @@ function createTransformer(emitter) {
365
413
  documents: (await Promise.all(promises)).filter(isDefined)
366
414
  };
367
415
  }
368
- function createContext(collections) {
416
+ function createContext(collections, cache) {
369
417
  return {
370
418
  documents: (collection) => {
371
419
  const resolved = collections.find((c) => c.name === collection.name);
@@ -376,19 +424,22 @@ function createTransformer(emitter) {
376
424
  );
377
425
  }
378
426
  return resolved.documents.map((doc) => doc.document);
379
- }
427
+ },
428
+ cache: cache.cacheFn
380
429
  };
381
430
  }
382
431
  async function transformCollection(collections, collection) {
383
432
  if (collection.transform) {
384
433
  const docs = [];
385
- const context = createContext(collections);
386
434
  for (const doc of collection.documents) {
435
+ const cache = cacheManager.cache(collection.name, doc.document._meta.path);
436
+ const context = createContext(collections, cache);
387
437
  try {
388
438
  docs.push({
389
439
  ...doc,
390
- document: await collection.transform(context, doc.document)
440
+ document: await collection.transform(doc.document, context)
391
441
  });
442
+ await cache.tidyUp();
392
443
  } catch (error) {
393
444
  if (error instanceof TransformError) {
394
445
  emitter.emit("transformer:error", {
@@ -403,6 +454,7 @@ function createTransformer(emitter) {
403
454
  }
404
455
  }
405
456
  }
457
+ await cacheManager.flush();
406
458
  return docs;
407
459
  }
408
460
  return collection.documents;
@@ -475,15 +527,13 @@ function createSynchronizer(readCollectionFile, collections, baseDirectory = "."
475
527
  const index = collection.files.findIndex(
476
528
  (file2) => file2.path === relativePath
477
529
  );
478
- const file = await readCollectionFile(
479
- path4.join(baseDirectory, collection.directory),
480
- relativePath
481
- );
530
+ const file = await readCollectionFile(collection, relativePath);
482
531
  if (!file) {
483
532
  return false;
484
533
  }
485
534
  if (index === -1) {
486
535
  collection.files.push(file);
536
+ collection.files.sort(orderByPath);
487
537
  } else {
488
538
  collection.files[index] = file;
489
539
  }
@@ -496,7 +546,7 @@ function createSynchronizer(readCollectionFile, collections, baseDirectory = "."
496
546
  }
497
547
 
498
548
  // src/builder.ts
499
- import path5 from "path";
549
+ import path6 from "path";
500
550
 
501
551
  // src/watcher.ts
502
552
  import * as watcher from "@parcel/watcher";
@@ -521,7 +571,7 @@ async function createWatcher(emitter, paths, sync, build2) {
521
571
  }
522
572
  };
523
573
  const subscriptions = await Promise.all(
524
- paths.map((path6) => watcher.subscribe(path6, onChange))
574
+ paths.map((path7) => watcher.subscribe(path7, onChange))
525
575
  );
526
576
  return {
527
577
  unsubscribe: async () => {
@@ -562,12 +612,96 @@ function createEmitter() {
562
612
  };
563
613
  }
564
614
 
615
+ // src/cache.ts
616
+ import path5, { join } from "path";
617
+ import { mkdir, readFile as readFile2, unlink, writeFile } from "fs/promises";
618
+ import { existsSync as existsSync2 } from "fs";
619
+ import { createHash as createHash2 } from "crypto";
620
+ function createKey(config, input) {
621
+ return createHash2("sha256").update(config).update(JSON.stringify(input)).digest("hex");
622
+ }
623
+ async function createCacheDirectory(directory) {
624
+ const cacheDirectory = path5.join(directory, ".content-collections", "cache");
625
+ if (!existsSync2(cacheDirectory)) {
626
+ await mkdir(cacheDirectory, { recursive: true });
627
+ }
628
+ return cacheDirectory;
629
+ }
630
+ function fileName(input) {
631
+ return input.replace(/[^a-z0-9]/gi, "_").toLowerCase();
632
+ }
633
+ async function createCacheManager(baseDirectory, configChecksum) {
634
+ const cacheDirectory = await createCacheDirectory(baseDirectory);
635
+ let mapping = {};
636
+ const mappingPath = join(cacheDirectory, "mapping.json");
637
+ if (existsSync2(mappingPath)) {
638
+ mapping = JSON.parse(await readFile2(mappingPath, "utf-8"));
639
+ }
640
+ async function flush() {
641
+ await writeFile(mappingPath, JSON.stringify(mapping));
642
+ }
643
+ function cache(collection, file) {
644
+ const directory = join(
645
+ cacheDirectory,
646
+ fileName(collection),
647
+ fileName(file)
648
+ );
649
+ let collectionMapping = mapping[collection];
650
+ if (!collectionMapping) {
651
+ collectionMapping = {};
652
+ mapping[collection] = collectionMapping;
653
+ }
654
+ let fileMapping = collectionMapping[file];
655
+ if (!fileMapping) {
656
+ fileMapping = [];
657
+ collectionMapping[file] = fileMapping;
658
+ }
659
+ let newFileMapping = [];
660
+ const cacheFn = async (input, fn) => {
661
+ const key = createKey(configChecksum, input);
662
+ newFileMapping.push(key);
663
+ const filePath = join(directory, `${key}.cache`);
664
+ if (fileMapping?.includes(key) || newFileMapping.includes(key)) {
665
+ if (existsSync2(filePath)) {
666
+ return JSON.parse(await readFile2(filePath, "utf-8"));
667
+ }
668
+ }
669
+ const output = await fn(input);
670
+ if (!existsSync2(directory)) {
671
+ await mkdir(directory, { recursive: true });
672
+ }
673
+ await writeFile(filePath, JSON.stringify(output));
674
+ return output;
675
+ };
676
+ const tidyUp = async () => {
677
+ const filesToDelete = fileMapping?.filter((key) => !newFileMapping.includes(key)) || [];
678
+ for (const key of filesToDelete) {
679
+ const filePath = join(directory, `${key}.cache`);
680
+ if (existsSync2(filePath)) {
681
+ await unlink(filePath);
682
+ }
683
+ }
684
+ if (collectionMapping) {
685
+ collectionMapping[file] = newFileMapping;
686
+ }
687
+ };
688
+ return {
689
+ cacheFn,
690
+ tidyUp
691
+ };
692
+ }
693
+ return {
694
+ cache,
695
+ flush
696
+ };
697
+ }
698
+
565
699
  // src/builder.ts
566
700
  function resolveOutputDir(baseDirectory, options) {
567
701
  if (options.outputDir) {
568
702
  return options.outputDir;
569
703
  }
570
- return path5.join(baseDirectory, ".content-collections", "generated");
704
+ return path6.join(baseDirectory, ".content-collections", "generated");
571
705
  }
572
706
  async function createBuilder(configurationPath, options = {
573
707
  configName: defaultConfigName
@@ -575,7 +709,7 @@ async function createBuilder(configurationPath, options = {
575
709
  const emitter = createEmitter();
576
710
  const readConfiguration = createConfigurationReader();
577
711
  const configuration = await readConfiguration(configurationPath, options);
578
- const baseDirectory = path5.dirname(configurationPath);
712
+ const baseDirectory = path6.dirname(configurationPath);
579
713
  const directory = resolveOutputDir(baseDirectory, options);
580
714
  const collector = createCollector(emitter, baseDirectory);
581
715
  const writer = await createWriter(directory);
@@ -589,7 +723,8 @@ async function createBuilder(configurationPath, options = {
589
723
  resolved,
590
724
  baseDirectory
591
725
  );
592
- const transform = createTransformer(emitter);
726
+ const cacheManager = await createCacheManager(baseDirectory, configuration.checksum);
727
+ const transform = createTransformer(emitter, cacheManager);
593
728
  async function sync(modification, filePath) {
594
729
  if (modification === "delete") {
595
730
  return synchronizer.deleted(filePath);
@@ -614,7 +749,7 @@ async function createBuilder(configurationPath, options = {
614
749
  }
615
750
  async function watch() {
616
751
  const paths = resolved.map(
617
- (collection) => path5.join(baseDirectory, collection.directory)
752
+ (collection) => path6.join(baseDirectory, collection.directory)
618
753
  );
619
754
  const watcher2 = await createWatcher(emitter, paths, sync, build2);
620
755
  return watcher2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@content-collections/core",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "types": "./dist/index.d.ts",
@@ -33,6 +33,7 @@
33
33
  "gray-matter": "^4.0.3",
34
34
  "micromatch": "^4.0.5",
35
35
  "pluralize": "^8.0.0",
36
+ "yaml": "^2.3.4",
36
37
  "zod": "^3.22.4"
37
38
  },
38
39
  "scripts": {