@elek-io/core 0.16.2 → 0.17.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.
@@ -19,11 +19,12 @@ import PQueue from "p-queue";
19
19
  import { createLogger, format, transports } from "winston";
20
20
  import DailyRotateFile from "winston-daily-rotate-file";
21
21
  import Semver from "semver";
22
+ import { isDeepStrictEqual } from "node:util";
22
23
 
23
24
  //#region package.json
24
25
  var package_default = {
25
26
  name: "@elek-io/core",
26
- version: "0.16.2",
27
+ version: "0.17.0",
27
28
  description: "Handles core functionality of elek.io Projects like file IO and version control.",
28
29
  homepage: "https://elek.io",
29
30
  repository: "https://github.com/elek-io/core",
@@ -218,11 +219,7 @@ const supportedLanguageSchema = z.enum([
218
219
  "sv",
219
220
  "zh"
220
221
  ]);
221
- const supportedIconSchema = z.enum([
222
- "home",
223
- "plus",
224
- "foobar"
225
- ]);
222
+ const supportedIconSchema = z.enum(["home", "plus"]);
226
223
  const objectTypeSchema = z.enum([
227
224
  "project",
228
225
  "asset",
@@ -237,7 +234,7 @@ const logLevelSchema = z.enum([
237
234
  "info",
238
235
  "debug"
239
236
  ]);
240
- const versionSchema = z.string();
237
+ const versionSchema = z.string().refine((version) => /^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/.test(version), "String must follow the Semantic Versioning format (https://semver.org/)");
241
238
  const uuidSchema = z.uuid();
242
239
  /**
243
240
  * A record that can be used to translate a string value into all supported languages
@@ -254,6 +251,41 @@ const translatableBooleanSchema = z.partialRecord(supportedLanguageSchema, z.boo
254
251
  function translatableArrayOf(schema) {
255
252
  return z.partialRecord(supportedLanguageSchema, z.array(schema));
256
253
  }
254
+ const reservedSlugs = new Set([
255
+ "index",
256
+ "new",
257
+ "create",
258
+ "update",
259
+ "delete",
260
+ "edit",
261
+ "list",
262
+ "count",
263
+ "api",
264
+ "admin",
265
+ "collection",
266
+ "collections",
267
+ "entry",
268
+ "entries",
269
+ "asset",
270
+ "assets",
271
+ "project",
272
+ "projects",
273
+ "null",
274
+ "undefined",
275
+ "true",
276
+ "false",
277
+ "constructor",
278
+ "__proto__",
279
+ "prototype",
280
+ "toString",
281
+ "valueOf",
282
+ "login",
283
+ "logout",
284
+ "auth",
285
+ "settings",
286
+ "config"
287
+ ]);
288
+ const slugSchema = z.string().min(1).max(128).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).refine((slug) => !reservedSlugs.has(slug), { message: "This slug is reserved and cannot be used" });
257
289
 
258
290
  //#endregion
259
291
  //#region src/schema/fileSchema.ts
@@ -263,91 +295,36 @@ function translatableArrayOf(schema) {
263
295
  const baseFileSchema = z.object({
264
296
  objectType: objectTypeSchema.readonly(),
265
297
  id: uuidSchema.readonly(),
298
+ coreVersion: versionSchema.readonly(),
266
299
  created: z.string().datetime().readonly(),
267
- updated: z.string().datetime().nullable()
300
+ updated: z.string().datetime().nullable().readonly()
268
301
  });
269
302
  const fileReferenceSchema = z.object({
270
303
  id: uuidSchema,
271
304
  extension: z.string().optional()
272
305
  });
273
-
274
- //#endregion
275
- //#region src/schema/gitSchema.ts
276
306
  /**
277
- * Signature git uses to identify users
307
+ * Schema for the collection index file (collections/index.json).
308
+ * Maps collection UUIDs to their slug.plural values.
309
+ * This is a local performance cache, not git-tracked.
278
310
  */
279
- const gitSignatureSchema = z.object({
280
- name: z.string(),
281
- email: z.string().email()
282
- });
283
- const gitMessageSchema = z.object({
284
- method: z.enum([
285
- "create",
286
- "update",
287
- "delete",
288
- "upgrade"
289
- ]),
290
- reference: z.object({
291
- objectType: objectTypeSchema,
292
- id: uuidSchema,
293
- collectionId: uuidSchema.optional()
294
- })
295
- });
296
- const gitTagSchema = z.object({
297
- id: uuidSchema,
298
- message: z.string(),
299
- author: gitSignatureSchema,
300
- datetime: z.string().datetime()
301
- });
302
- const gitCommitSchema = z.object({
303
- hash: z.string(),
304
- message: gitMessageSchema,
305
- author: gitSignatureSchema,
306
- datetime: z.string().datetime(),
307
- tag: gitTagSchema.nullable()
308
- });
309
- const gitInitOptionsSchema = z.object({ initialBranch: z.string() });
310
- const gitCloneOptionsSchema = z.object({
311
- depth: z.number(),
312
- singleBranch: z.boolean(),
313
- branch: z.string(),
314
- bare: z.boolean()
315
- });
316
- const gitMergeOptionsSchema = z.object({ squash: z.boolean() });
317
- const gitSwitchOptionsSchema = z.object({ isNew: z.boolean().optional() });
318
- const gitLogOptionsSchema = z.object({
319
- limit: z.number().optional(),
320
- between: z.object({
321
- from: z.string(),
322
- to: z.string().optional()
323
- }),
324
- filePath: z.string().optional()
325
- });
326
- const createGitTagSchema = gitTagSchema.pick({ message: true }).extend({
327
- path: z.string(),
328
- hash: z.string().optional()
329
- });
330
- const readGitTagSchema = z.object({
331
- path: z.string(),
332
- id: uuidSchema.readonly()
333
- });
334
- const deleteGitTagSchema = readGitTagSchema.extend({});
335
- const countGitTagsSchema = z.object({ path: z.string() });
311
+ const collectionIndexSchema = z.record(uuidSchema, z.string());
336
312
 
337
313
  //#endregion
338
314
  //#region src/schema/assetSchema.ts
339
315
  const assetFileSchema = baseFileSchema.extend({
340
316
  objectType: z.literal(objectTypeSchema.enum.asset).readonly(),
341
- name: z.string(),
342
- description: z.string(),
317
+ name: z.string().trim().min(1),
318
+ description: z.string().trim().min(1),
343
319
  extension: z.string().readonly(),
344
320
  mimeType: z.string().readonly(),
345
321
  size: z.number().readonly()
346
322
  });
347
- const assetSchema = assetFileSchema.extend({
348
- absolutePath: z.string().readonly(),
349
- history: z.array(gitCommitSchema)
350
- }).openapi("Asset");
323
+ const assetSchema = assetFileSchema.extend({ absolutePath: z.string().readonly() }).openapi("Asset");
324
+ const assetHistorySchema = z.object({
325
+ id: uuidSchema.readonly(),
326
+ projectId: uuidSchema.readonly()
327
+ });
351
328
  const assetExportSchema = assetSchema.extend({});
352
329
  const createAssetSchema = assetFileSchema.pick({
353
330
  name: true,
@@ -377,11 +354,15 @@ const deleteAssetSchema = assetFileSchema.pick({
377
354
  id: true,
378
355
  extension: true
379
356
  }).extend({ projectId: uuidSchema.readonly() });
357
+ const migrateAssetSchema = z.looseObject(assetFileSchema.pick({
358
+ id: true,
359
+ coreVersion: true
360
+ }).shape);
380
361
  const countAssetsSchema = z.object({ projectId: uuidSchema.readonly() });
381
362
 
382
363
  //#endregion
383
364
  //#region src/schema/valueSchema.ts
384
- const ValueTypeSchema = z.enum([
365
+ const valueTypeSchema = z.enum([
385
366
  "string",
386
367
  "number",
387
368
  "boolean",
@@ -396,20 +377,17 @@ const valueContentReferenceSchema = z.union([
396
377
  valueContentReferenceToCollectionSchema,
397
378
  valueContentReferenceToEntrySchema
398
379
  ]);
399
- const directValueBaseSchema = z.object({
400
- objectType: z.literal(objectTypeSchema.enum.value).readonly(),
401
- fieldDefinitionId: uuidSchema.readonly()
402
- });
380
+ const directValueBaseSchema = z.object({ objectType: z.literal(objectTypeSchema.enum.value).readonly() });
403
381
  const directStringValueSchema = directValueBaseSchema.extend({
404
- valueType: z.literal(ValueTypeSchema.enum.string).readonly(),
382
+ valueType: z.literal(valueTypeSchema.enum.string).readonly(),
405
383
  content: translatableStringSchema
406
384
  });
407
385
  const directNumberValueSchema = directValueBaseSchema.extend({
408
- valueType: z.literal(ValueTypeSchema.enum.number).readonly(),
386
+ valueType: z.literal(valueTypeSchema.enum.number).readonly(),
409
387
  content: translatableNumberSchema
410
388
  });
411
389
  const directBooleanValueSchema = directValueBaseSchema.extend({
412
- valueType: z.literal(ValueTypeSchema.enum.boolean).readonly(),
390
+ valueType: z.literal(valueTypeSchema.enum.boolean).readonly(),
413
391
  content: translatableBooleanSchema
414
392
  });
415
393
  const directValueSchema = z.union([
@@ -419,8 +397,7 @@ const directValueSchema = z.union([
419
397
  ]);
420
398
  const referencedValueSchema = z.object({
421
399
  objectType: z.literal(objectTypeSchema.enum.value).readonly(),
422
- fieldDefinitionId: uuidSchema.readonly(),
423
- valueType: z.literal(ValueTypeSchema.enum.reference).readonly(),
400
+ valueType: z.literal(valueTypeSchema.enum.reference).readonly(),
424
401
  content: translatableArrayOf(valueContentReferenceSchema)
425
402
  });
426
403
  const valueSchema = z.union([directValueSchema, referencedValueSchema]);
@@ -435,19 +412,25 @@ const valueSchema = z.union([directValueSchema, referencedValueSchema]);
435
412
  //#region src/schema/entrySchema.ts
436
413
  const entryFileSchema = baseFileSchema.extend({
437
414
  objectType: z.literal(objectTypeSchema.enum.entry).readonly(),
438
- values: z.array(valueSchema)
415
+ values: z.record(slugSchema, valueSchema)
416
+ });
417
+ const entrySchema = entryFileSchema.openapi("Entry");
418
+ const entryHistorySchema = z.object({
419
+ id: uuidSchema.readonly(),
420
+ projectId: uuidSchema.readonly(),
421
+ collectionId: uuidSchema.readonly()
439
422
  });
440
- const entrySchema = entryFileSchema.extend({ history: z.array(gitCommitSchema) }).openapi("Entry");
441
423
  const entryExportSchema = entrySchema.extend({});
442
424
  const createEntrySchema = entryFileSchema.omit({
443
425
  id: true,
444
426
  objectType: true,
427
+ coreVersion: true,
445
428
  created: true,
446
429
  updated: true
447
430
  }).extend({
448
431
  projectId: uuidSchema.readonly(),
449
432
  collectionId: uuidSchema.readonly(),
450
- values: z.array(valueSchema)
433
+ values: z.record(slugSchema, valueSchema)
451
434
  });
452
435
  const readEntrySchema = z.object({
453
436
  id: uuidSchema.readonly(),
@@ -457,6 +440,7 @@ const readEntrySchema = z.object({
457
440
  });
458
441
  const updateEntrySchema = entryFileSchema.omit({
459
442
  objectType: true,
443
+ coreVersion: true,
460
444
  created: true,
461
445
  updated: true
462
446
  }).extend({
@@ -464,6 +448,10 @@ const updateEntrySchema = entryFileSchema.omit({
464
448
  collectionId: uuidSchema.readonly()
465
449
  });
466
450
  const deleteEntrySchema = readEntrySchema.extend({});
451
+ const migrateEntrySchema = z.looseObject(entryFileSchema.pick({
452
+ id: true,
453
+ coreVersion: true
454
+ }).shape);
467
455
  const countEntriesSchema = z.object({
468
456
  projectId: uuidSchema.readonly(),
469
457
  collectionId: uuidSchema.readonly()
@@ -471,7 +459,7 @@ const countEntriesSchema = z.object({
471
459
 
472
460
  //#endregion
473
461
  //#region src/schema/fieldSchema.ts
474
- const FieldTypeSchema = z.enum([
462
+ const fieldTypeSchema = z.enum([
475
463
  "text",
476
464
  "textarea",
477
465
  "email",
@@ -487,64 +475,65 @@ const FieldTypeSchema = z.enum([
487
475
  "asset",
488
476
  "entry"
489
477
  ]);
490
- const FieldWidthSchema = z.enum([
478
+ const fieldWidthSchema = z.enum([
491
479
  "12",
492
480
  "6",
493
481
  "4",
494
482
  "3"
495
483
  ]);
496
- const FieldDefinitionBaseSchema = z.object({
484
+ const fieldDefinitionBaseSchema = z.object({
497
485
  id: uuidSchema.readonly(),
486
+ slug: slugSchema,
498
487
  label: translatableStringSchema,
499
488
  description: translatableStringSchema.nullable(),
500
489
  isRequired: z.boolean(),
501
490
  isDisabled: z.boolean(),
502
491
  isUnique: z.boolean(),
503
- inputWidth: FieldWidthSchema
492
+ inputWidth: fieldWidthSchema
504
493
  });
505
494
  /**
506
495
  * String based Field definitions
507
496
  */
508
- const StringFieldDefinitionBaseSchema = FieldDefinitionBaseSchema.extend({
509
- valueType: z.literal(ValueTypeSchema.enum.string),
497
+ const stringFieldDefinitionBaseSchema = fieldDefinitionBaseSchema.extend({
498
+ valueType: z.literal(valueTypeSchema.enum.string),
510
499
  defaultValue: z.string().nullable()
511
500
  });
512
- const textFieldDefinitionSchema = StringFieldDefinitionBaseSchema.extend({
513
- fieldType: z.literal(FieldTypeSchema.enum.text),
501
+ const textFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
502
+ fieldType: z.literal(fieldTypeSchema.enum.text),
514
503
  min: z.number().nullable(),
515
504
  max: z.number().nullable()
516
505
  });
517
- const textareaFieldDefinitionSchema = StringFieldDefinitionBaseSchema.extend({
518
- fieldType: z.literal(FieldTypeSchema.enum.textarea),
506
+ const textareaFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
507
+ fieldType: z.literal(fieldTypeSchema.enum.textarea),
519
508
  min: z.number().nullable(),
520
509
  max: z.number().nullable()
521
510
  });
522
- const emailFieldDefinitionSchema = StringFieldDefinitionBaseSchema.extend({
523
- fieldType: z.literal(FieldTypeSchema.enum.email),
511
+ const emailFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
512
+ fieldType: z.literal(fieldTypeSchema.enum.email),
524
513
  defaultValue: z.email().nullable()
525
514
  });
526
- const urlFieldDefinitionSchema = StringFieldDefinitionBaseSchema.extend({
527
- fieldType: z.literal(FieldTypeSchema.enum.url),
515
+ const urlFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
516
+ fieldType: z.literal(fieldTypeSchema.enum.url),
528
517
  defaultValue: z.url().nullable()
529
518
  });
530
- const ipv4FieldDefinitionSchema = StringFieldDefinitionBaseSchema.extend({
531
- fieldType: z.literal(FieldTypeSchema.enum.ipv4),
519
+ const ipv4FieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
520
+ fieldType: z.literal(fieldTypeSchema.enum.ipv4),
532
521
  defaultValue: z.ipv4().nullable()
533
522
  });
534
- const dateFieldDefinitionSchema = StringFieldDefinitionBaseSchema.extend({
535
- fieldType: z.literal(FieldTypeSchema.enum.date),
523
+ const dateFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
524
+ fieldType: z.literal(fieldTypeSchema.enum.date),
536
525
  defaultValue: z.iso.date().nullable()
537
526
  });
538
- const timeFieldDefinitionSchema = StringFieldDefinitionBaseSchema.extend({
539
- fieldType: z.literal(FieldTypeSchema.enum.time),
527
+ const timeFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
528
+ fieldType: z.literal(fieldTypeSchema.enum.time),
540
529
  defaultValue: z.iso.time().nullable()
541
530
  });
542
- const datetimeFieldDefinitionSchema = StringFieldDefinitionBaseSchema.extend({
543
- fieldType: z.literal(FieldTypeSchema.enum.datetime),
531
+ const datetimeFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
532
+ fieldType: z.literal(fieldTypeSchema.enum.datetime),
544
533
  defaultValue: z.iso.datetime().nullable()
545
534
  });
546
- const telephoneFieldDefinitionSchema = StringFieldDefinitionBaseSchema.extend({
547
- fieldType: z.literal(FieldTypeSchema.enum.telephone),
535
+ const telephoneFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
536
+ fieldType: z.literal(fieldTypeSchema.enum.telephone),
548
537
  defaultValue: z.e164().nullable()
549
538
  });
550
539
  const stringFieldDefinitionSchema = z.union([
@@ -561,16 +550,16 @@ const stringFieldDefinitionSchema = z.union([
561
550
  /**
562
551
  * Number based Field definitions
563
552
  */
564
- const NumberFieldDefinitionBaseSchema = FieldDefinitionBaseSchema.extend({
565
- valueType: z.literal(ValueTypeSchema.enum.number),
553
+ const numberFieldDefinitionBaseSchema = fieldDefinitionBaseSchema.extend({
554
+ valueType: z.literal(valueTypeSchema.enum.number),
566
555
  min: z.number().nullable(),
567
556
  max: z.number().nullable(),
568
557
  isUnique: z.literal(false),
569
558
  defaultValue: z.number().nullable()
570
559
  });
571
- const numberFieldDefinitionSchema = NumberFieldDefinitionBaseSchema.extend({ fieldType: z.literal(FieldTypeSchema.enum.number) });
572
- const rangeFieldDefinitionSchema = NumberFieldDefinitionBaseSchema.extend({
573
- fieldType: z.literal(FieldTypeSchema.enum.range),
560
+ const numberFieldDefinitionSchema = numberFieldDefinitionBaseSchema.extend({ fieldType: z.literal(fieldTypeSchema.enum.number) });
561
+ const rangeFieldDefinitionSchema = numberFieldDefinitionBaseSchema.extend({
562
+ fieldType: z.literal(fieldTypeSchema.enum.range),
574
563
  isRequired: z.literal(true),
575
564
  min: z.number(),
576
565
  max: z.number(),
@@ -579,24 +568,27 @@ const rangeFieldDefinitionSchema = NumberFieldDefinitionBaseSchema.extend({
579
568
  /**
580
569
  * Boolean based Field definitions
581
570
  */
582
- const BooleanFieldDefinitionBaseSchema = FieldDefinitionBaseSchema.extend({
583
- valueType: z.literal(ValueTypeSchema.enum.boolean),
571
+ const booleanFieldDefinitionBaseSchema = fieldDefinitionBaseSchema.extend({
572
+ valueType: z.literal(valueTypeSchema.enum.boolean),
584
573
  isRequired: z.literal(true),
585
574
  defaultValue: z.boolean(),
586
575
  isUnique: z.literal(false)
587
576
  });
588
- const toggleFieldDefinitionSchema = BooleanFieldDefinitionBaseSchema.extend({ fieldType: z.literal(FieldTypeSchema.enum.toggle) });
577
+ const toggleFieldDefinitionSchema = booleanFieldDefinitionBaseSchema.extend({ fieldType: z.literal(fieldTypeSchema.enum.toggle) });
589
578
  /**
590
579
  * Reference based Field definitions
591
580
  */
592
- const ReferenceFieldDefinitionBaseSchema = FieldDefinitionBaseSchema.extend({ valueType: z.literal(ValueTypeSchema.enum.reference) });
593
- const assetFieldDefinitionSchema = ReferenceFieldDefinitionBaseSchema.extend({
594
- fieldType: z.literal(FieldTypeSchema.enum.asset),
581
+ const referenceFieldDefinitionBaseSchema = fieldDefinitionBaseSchema.extend({
582
+ valueType: z.literal(valueTypeSchema.enum.reference),
583
+ isUnique: z.literal(false)
584
+ });
585
+ const assetFieldDefinitionSchema = referenceFieldDefinitionBaseSchema.extend({
586
+ fieldType: z.literal(fieldTypeSchema.enum.asset),
595
587
  min: z.number().nullable(),
596
588
  max: z.number().nullable()
597
589
  });
598
- const entryFieldDefinitionSchema = ReferenceFieldDefinitionBaseSchema.extend({
599
- fieldType: z.literal(FieldTypeSchema.enum.entry),
590
+ const entryFieldDefinitionSchema = referenceFieldDefinitionBaseSchema.extend({
591
+ fieldType: z.literal(fieldTypeSchema.enum.entry),
600
592
  ofCollections: z.array(uuidSchema),
601
593
  min: z.number().nullable(),
602
594
  max: z.number().nullable()
@@ -619,18 +611,23 @@ const collectionFileSchema = baseFileSchema.extend({
619
611
  plural: translatableStringSchema
620
612
  }),
621
613
  slug: z.object({
622
- singular: z.string(),
623
- plural: z.string()
614
+ singular: slugSchema,
615
+ plural: slugSchema
624
616
  }),
625
617
  description: translatableStringSchema,
626
618
  icon: supportedIconSchema,
627
619
  fieldDefinitions: z.array(fieldDefinitionSchema)
628
620
  });
629
- const collectionSchema = collectionFileSchema.extend({ history: z.array(gitCommitSchema) }).openapi("Collection");
621
+ const collectionSchema = collectionFileSchema.openapi("Collection");
622
+ const collectionHistorySchema = z.object({
623
+ id: uuidSchema.readonly(),
624
+ projectId: uuidSchema.readonly()
625
+ });
630
626
  const collectionExportSchema = collectionSchema.extend({ entries: z.array(entryExportSchema) });
631
627
  const createCollectionSchema = collectionFileSchema.omit({
632
628
  id: true,
633
629
  objectType: true,
630
+ coreVersion: true,
634
631
  created: true,
635
632
  updated: true
636
633
  }).extend({ projectId: uuidSchema.readonly() });
@@ -639,6 +636,11 @@ const readCollectionSchema = z.object({
639
636
  projectId: uuidSchema.readonly(),
640
637
  commitHash: z.string().optional().readonly()
641
638
  });
639
+ const readBySlugCollectionSchema = z.object({
640
+ slug: slugSchema,
641
+ projectId: uuidSchema.readonly(),
642
+ commitHash: z.string().optional().readonly()
643
+ });
642
644
  const updateCollectionSchema = collectionFileSchema.pick({
643
645
  id: true,
644
646
  name: true,
@@ -649,6 +651,14 @@ const updateCollectionSchema = collectionFileSchema.pick({
649
651
  }).extend({ projectId: uuidSchema.readonly() });
650
652
  const deleteCollectionSchema = readCollectionSchema.extend({});
651
653
  const countCollectionsSchema = z.object({ projectId: uuidSchema.readonly() });
654
+ const migrateCollectionSchema = z.looseObject(collectionFileSchema.pick({
655
+ id: true,
656
+ coreVersion: true
657
+ }).shape);
658
+ const resolveCollectionIdSchema = z.object({
659
+ projectId: uuidSchema.readonly(),
660
+ idOrSlug: z.string()
661
+ });
652
662
 
653
663
  //#endregion
654
664
  //#region src/schema/coreSchema.ts
@@ -665,15 +675,88 @@ const constructorElekIoCoreSchema = elekIoCoreOptionsSchema.partial({
665
675
  }).optional();
666
676
 
667
677
  //#endregion
668
- //#region src/schema/projectSchema.ts
669
- const projectStatusSchema = z.enum([
670
- "foo",
671
- "bar",
672
- "todo"
678
+ //#region src/schema/gitSchema.ts
679
+ /**
680
+ * Signature git uses to identify users
681
+ */
682
+ const gitSignatureSchema = z.object({
683
+ name: z.string().regex(/^[^|]+$/, "Name must not contain pipe characters"),
684
+ email: z.string().email()
685
+ });
686
+ const gitMessageSchema = z.object({
687
+ method: z.enum([
688
+ "create",
689
+ "update",
690
+ "delete",
691
+ "upgrade",
692
+ "release"
693
+ ]),
694
+ reference: z.object({
695
+ objectType: objectTypeSchema,
696
+ id: uuidSchema,
697
+ collectionId: uuidSchema.optional()
698
+ })
699
+ });
700
+ const gitTagMessageSchema = z.discriminatedUnion("type", [
701
+ z.object({
702
+ type: z.literal("release"),
703
+ version: versionSchema
704
+ }),
705
+ z.object({
706
+ type: z.literal("preview"),
707
+ version: versionSchema
708
+ }),
709
+ z.object({
710
+ type: z.literal("upgrade"),
711
+ coreVersion: versionSchema
712
+ })
673
713
  ]);
714
+ const gitTagSchema = z.object({
715
+ id: uuidSchema,
716
+ message: gitTagMessageSchema,
717
+ author: gitSignatureSchema,
718
+ datetime: z.iso.datetime()
719
+ });
720
+ const gitCommitSchema = z.object({
721
+ hash: z.hash("sha1"),
722
+ message: gitMessageSchema,
723
+ author: gitSignatureSchema,
724
+ datetime: z.iso.datetime(),
725
+ tag: gitTagSchema.nullable()
726
+ });
727
+ const gitInitOptionsSchema = z.object({ initialBranch: z.string() });
728
+ const gitCloneOptionsSchema = z.object({
729
+ depth: z.number(),
730
+ singleBranch: z.boolean(),
731
+ branch: z.string(),
732
+ bare: z.boolean()
733
+ });
734
+ const gitMergeOptionsSchema = z.object({ squash: z.boolean() });
735
+ const gitSwitchOptionsSchema = z.object({ isNew: z.boolean().optional() });
736
+ const gitLogOptionsSchema = z.object({
737
+ limit: z.number().optional(),
738
+ between: z.object({
739
+ from: z.string(),
740
+ to: z.string().optional()
741
+ }),
742
+ filePath: z.string().optional()
743
+ });
744
+ const createGitTagSchema = gitTagSchema.pick({ message: true }).extend({
745
+ path: z.string(),
746
+ hash: z.string().optional()
747
+ });
748
+ const readGitTagSchema = z.object({
749
+ path: z.string(),
750
+ id: uuidSchema.readonly()
751
+ });
752
+ const deleteGitTagSchema = readGitTagSchema.extend({});
753
+ const countGitTagsSchema = z.object({ path: z.string() });
754
+
755
+ //#endregion
756
+ //#region src/schema/projectSchema.ts
674
757
  const projectSettingsSchema = z.object({ language: z.object({
675
758
  default: supportedLanguageSchema,
676
- supported: z.array(supportedLanguageSchema)
759
+ supported: z.array(supportedLanguageSchema).refine((langs) => new Set(langs).size === langs.length, { message: "Supported languages must not contain duplicates" })
677
760
  }) });
678
761
  const projectFolderSchema = z.enum([
679
762
  "assets",
@@ -684,22 +767,21 @@ const projectFolderSchema = z.enum([
684
767
  const projectBranchSchema = z.enum(["production", "work"]);
685
768
  const projectFileSchema = baseFileSchema.extend({
686
769
  objectType: z.literal(objectTypeSchema.enum.project).readonly(),
687
- coreVersion: versionSchema,
688
770
  name: z.string().trim().min(1),
689
771
  description: z.string().trim().min(1),
690
772
  version: versionSchema,
691
- status: projectStatusSchema,
692
773
  settings: projectSettingsSchema
693
774
  });
694
- const projectSchema = projectFileSchema.extend({
695
- remoteOriginUrl: z.string().nullable().openapi({ description: "URL of the remote Git repository" }),
775
+ const projectSchema = projectFileSchema.extend({ remoteOriginUrl: z.string().nullable().openapi({ description: "URL of the remote Git repository" }) }).openapi("Project");
776
+ const projectHistorySchema = z.object({ id: uuidSchema.readonly() });
777
+ const projectHistoryResultSchema = z.object({
696
778
  history: z.array(gitCommitSchema).openapi({ description: "Commit history of this Project" }),
697
779
  fullHistory: z.array(gitCommitSchema).openapi({ description: "Full commit history of this Project including all Assets, Collections, Entries and other files" })
698
- }).openapi("Project");
699
- const migrateProjectSchema = projectFileSchema.pick({
780
+ });
781
+ const migrateProjectSchema = z.looseObject(projectFileSchema.pick({
700
782
  id: true,
701
783
  coreVersion: true
702
- }).loose();
784
+ }).shape);
703
785
  const projectExportSchema = projectSchema.extend({
704
786
  assets: z.array(assetExportSchema),
705
787
  collections: z.array(collectionExportSchema)
@@ -724,13 +806,6 @@ const upgradeProjectSchema = z.object({
724
806
  force: z.boolean().optional()
725
807
  });
726
808
  const deleteProjectSchema = readProjectSchema.extend({ force: z.boolean().optional() });
727
- const projectUpgradeSchema = z.object({
728
- to: versionSchema.readonly(),
729
- run: z.function({
730
- input: [projectFileSchema],
731
- output: z.promise(z.void())
732
- })
733
- });
734
809
  const cloneProjectSchema = z.object({ url: z.string() });
735
810
  const listBranchesProjectSchema = z.object({ id: uuidSchema.readonly() });
736
811
  const currentBranchProjectSchema = z.object({ id: uuidSchema.readonly() });
@@ -784,29 +859,29 @@ function getNumberValueContentSchemaFromFieldDefinition(fieldDefinition) {
784
859
  function getStringValueContentSchemaFromFieldDefinition(fieldDefinition) {
785
860
  let schema = null;
786
861
  switch (fieldDefinition.fieldType) {
787
- case FieldTypeSchema.enum.email:
862
+ case fieldTypeSchema.enum.email:
788
863
  schema = z.email();
789
864
  break;
790
- case FieldTypeSchema.enum.url:
865
+ case fieldTypeSchema.enum.url:
791
866
  schema = z.url();
792
867
  break;
793
- case FieldTypeSchema.enum.ipv4:
868
+ case fieldTypeSchema.enum.ipv4:
794
869
  schema = z.ipv4();
795
870
  break;
796
- case FieldTypeSchema.enum.date:
871
+ case fieldTypeSchema.enum.date:
797
872
  schema = z.iso.date();
798
873
  break;
799
- case FieldTypeSchema.enum.time:
874
+ case fieldTypeSchema.enum.time:
800
875
  schema = z.iso.time();
801
876
  break;
802
- case FieldTypeSchema.enum.datetime:
877
+ case fieldTypeSchema.enum.datetime:
803
878
  schema = z.iso.datetime();
804
879
  break;
805
- case FieldTypeSchema.enum.telephone:
880
+ case fieldTypeSchema.enum.telephone:
806
881
  schema = z.e164();
807
882
  break;
808
- case FieldTypeSchema.enum.text:
809
- case FieldTypeSchema.enum.textarea:
883
+ case fieldTypeSchema.enum.text:
884
+ case fieldTypeSchema.enum.textarea:
810
885
  schema = z.string().trim();
811
886
  break;
812
887
  }
@@ -822,10 +897,10 @@ function getStringValueContentSchemaFromFieldDefinition(fieldDefinition) {
822
897
  function getReferenceValueContentSchemaFromFieldDefinition(fieldDefinition) {
823
898
  let schema;
824
899
  switch (fieldDefinition.fieldType) {
825
- case FieldTypeSchema.enum.asset:
900
+ case fieldTypeSchema.enum.asset:
826
901
  schema = z.array(valueContentReferenceToAssetSchema);
827
902
  break;
828
- case FieldTypeSchema.enum.entry:
903
+ case fieldTypeSchema.enum.entry:
829
904
  schema = z.array(valueContentReferenceToEntrySchema);
830
905
  break;
831
906
  }
@@ -851,47 +926,46 @@ function getTranslatableReferenceValueContentSchemaFromFieldDefinition(fieldDefi
851
926
  */
852
927
  function getValueSchemaFromFieldDefinition(fieldDefinition) {
853
928
  switch (fieldDefinition.valueType) {
854
- case ValueTypeSchema.enum.boolean: return directBooleanValueSchema.extend({ content: getTranslatableBooleanValueContentSchemaFromFieldDefinition() });
855
- case ValueTypeSchema.enum.number: return directNumberValueSchema.extend({ content: getTranslatableNumberValueContentSchemaFromFieldDefinition(fieldDefinition) });
856
- case ValueTypeSchema.enum.string: return directStringValueSchema.extend({ content: getTranslatableStringValueContentSchemaFromFieldDefinition(fieldDefinition) });
857
- case ValueTypeSchema.enum.reference: return referencedValueSchema.extend({ content: getTranslatableReferenceValueContentSchemaFromFieldDefinition(fieldDefinition) });
929
+ case valueTypeSchema.enum.boolean: return directBooleanValueSchema.extend({ content: getTranslatableBooleanValueContentSchemaFromFieldDefinition() });
930
+ case valueTypeSchema.enum.number: return directNumberValueSchema.extend({ content: getTranslatableNumberValueContentSchemaFromFieldDefinition(fieldDefinition) });
931
+ case valueTypeSchema.enum.string: return directStringValueSchema.extend({ content: getTranslatableStringValueContentSchemaFromFieldDefinition(fieldDefinition) });
932
+ case valueTypeSchema.enum.reference: return referencedValueSchema.extend({ content: getTranslatableReferenceValueContentSchemaFromFieldDefinition(fieldDefinition) });
858
933
  default: throw new Error(`Error generating schema for unsupported ValueType "${fieldDefinition.valueType}"`);
859
934
  }
860
935
  }
861
936
  /**
937
+ * Builds a z.object shape from field definitions, keyed by slug
938
+ */
939
+ function getValuesShapeFromFieldDefinitions(fieldDefinitions) {
940
+ const shape = {};
941
+ for (const fieldDef of fieldDefinitions) shape[fieldDef.slug] = getValueSchemaFromFieldDefinition(fieldDef);
942
+ return shape;
943
+ }
944
+ /**
862
945
  * Generates a schema for an Entry based on the given Field definitions and Values
863
946
  */
864
947
  function getEntrySchemaFromFieldDefinitions(fieldDefinitions) {
865
- const valueSchemas = fieldDefinitions.map((fieldDefinition) => {
866
- return getValueSchemaFromFieldDefinition(fieldDefinition);
867
- });
868
948
  return z.object({
869
949
  ...entrySchema.shape,
870
- values: z.tuple(valueSchemas)
950
+ values: z.object(getValuesShapeFromFieldDefinitions(fieldDefinitions))
871
951
  });
872
952
  }
873
953
  /**
874
954
  * Generates a schema for creating a new Entry based on the given Field definitions and Values
875
955
  */
876
956
  function getCreateEntrySchemaFromFieldDefinitions(fieldDefinitions) {
877
- const valueSchemas = fieldDefinitions.map((fieldDefinition) => {
878
- return getValueSchemaFromFieldDefinition(fieldDefinition);
879
- });
880
957
  return z.object({
881
958
  ...createEntrySchema.shape,
882
- values: z.tuple(valueSchemas)
959
+ values: z.object(getValuesShapeFromFieldDefinitions(fieldDefinitions))
883
960
  });
884
961
  }
885
962
  /**
886
963
  * Generates a schema for updating an existing Entry based on the given Field definitions and Values
887
964
  */
888
965
  function getUpdateEntrySchemaFromFieldDefinitions(fieldDefinitions) {
889
- const valueSchemas = fieldDefinitions.map((fieldDefinition) => {
890
- return getValueSchemaFromFieldDefinition(fieldDefinition);
891
- });
892
966
  return z.object({
893
967
  ...updateEntrySchema.shape,
894
- values: z.tuple(valueSchemas)
968
+ values: z.object(getValuesShapeFromFieldDefinitions(fieldDefinitions))
895
969
  });
896
970
  }
897
971
 
@@ -907,7 +981,8 @@ const serviceTypeSchema = z.enum([
907
981
  "Search",
908
982
  "Collection",
909
983
  "Entry",
910
- "Value"
984
+ "Value",
985
+ "Release"
911
986
  ]);
912
987
  function paginatedListOf(schema) {
913
988
  return z.object({
@@ -930,18 +1005,18 @@ const listGitTagsSchema = z.object({ path: z.string() });
930
1005
 
931
1006
  //#endregion
932
1007
  //#region src/schema/userSchema.ts
933
- const UserTypeSchema = z.enum(["local", "cloud"]);
1008
+ const userTypeSchema = z.enum(["local", "cloud"]);
934
1009
  const baseUserSchema = gitSignatureSchema.extend({
935
- userType: UserTypeSchema,
1010
+ userType: userTypeSchema,
936
1011
  language: supportedLanguageSchema,
937
1012
  localApi: z.object({
938
1013
  isEnabled: z.boolean(),
939
1014
  port: z.number()
940
1015
  })
941
1016
  });
942
- const localUserSchema = baseUserSchema.extend({ userType: z.literal(UserTypeSchema.enum.local) });
1017
+ const localUserSchema = baseUserSchema.extend({ userType: z.literal(userTypeSchema.enum.local) });
943
1018
  const cloudUserSchema = baseUserSchema.extend({
944
- userType: z.literal(UserTypeSchema.enum.cloud),
1019
+ userType: z.literal(userTypeSchema.enum.cloud),
945
1020
  id: uuidSchema
946
1021
  });
947
1022
  const userFileSchema = z.union([localUserSchema, cloudUserSchema]);
@@ -1015,6 +1090,97 @@ const logConsoleTransportSchema = logSchema.extend({
1015
1090
  level: z.string()
1016
1091
  });
1017
1092
 
1093
+ //#endregion
1094
+ //#region src/schema/releaseSchema.ts
1095
+ const semverBumpSchema = z.enum([
1096
+ "major",
1097
+ "minor",
1098
+ "patch"
1099
+ ]);
1100
+ const fieldChangeTypeSchema = z.enum([
1101
+ "added",
1102
+ "deleted",
1103
+ "valueTypeChanged",
1104
+ "fieldTypeChanged",
1105
+ "slugChanged",
1106
+ "minMaxTightened",
1107
+ "isRequiredToNotRequired",
1108
+ "isUniqueToNotUnique",
1109
+ "ofCollectionsChanged",
1110
+ "isNotRequiredToRequired",
1111
+ "isNotUniqueToUnique",
1112
+ "labelChanged",
1113
+ "descriptionChanged",
1114
+ "defaultValueChanged",
1115
+ "inputWidthChanged",
1116
+ "isDisabledChanged",
1117
+ "minMaxLoosened"
1118
+ ]);
1119
+ const fieldChangeSchema = z.object({
1120
+ collectionId: uuidSchema,
1121
+ fieldId: uuidSchema,
1122
+ fieldSlug: z.string(),
1123
+ changeType: fieldChangeTypeSchema,
1124
+ bump: semverBumpSchema
1125
+ });
1126
+ const collectionChangeTypeSchema = z.enum(["added", "deleted"]);
1127
+ const collectionChangeSchema = z.object({
1128
+ collectionId: uuidSchema,
1129
+ changeType: collectionChangeTypeSchema,
1130
+ bump: semverBumpSchema
1131
+ });
1132
+ const projectChangeTypeSchema = z.enum([
1133
+ "nameChanged",
1134
+ "descriptionChanged",
1135
+ "defaultLanguageChanged",
1136
+ "supportedLanguageAdded",
1137
+ "supportedLanguageRemoved"
1138
+ ]);
1139
+ const projectChangeSchema = z.object({
1140
+ changeType: projectChangeTypeSchema,
1141
+ bump: semverBumpSchema
1142
+ });
1143
+ const assetChangeTypeSchema = z.enum([
1144
+ "added",
1145
+ "deleted",
1146
+ "metadataChanged",
1147
+ "binaryChanged"
1148
+ ]);
1149
+ const assetChangeSchema = z.object({
1150
+ assetId: uuidSchema,
1151
+ changeType: assetChangeTypeSchema,
1152
+ bump: semverBumpSchema
1153
+ });
1154
+ const entryChangeTypeSchema = z.enum([
1155
+ "added",
1156
+ "deleted",
1157
+ "modified"
1158
+ ]);
1159
+ const entryChangeSchema = z.object({
1160
+ collectionId: uuidSchema,
1161
+ entryId: uuidSchema,
1162
+ changeType: entryChangeTypeSchema,
1163
+ bump: semverBumpSchema
1164
+ });
1165
+ const releaseDiffSchema = z.object({
1166
+ project: projectSchema,
1167
+ bump: semverBumpSchema.nullable(),
1168
+ currentVersion: versionSchema,
1169
+ nextVersion: versionSchema.nullable(),
1170
+ projectChanges: z.array(projectChangeSchema),
1171
+ collectionChanges: z.array(collectionChangeSchema),
1172
+ fieldChanges: z.array(fieldChangeSchema),
1173
+ assetChanges: z.array(assetChangeSchema),
1174
+ entryChanges: z.array(entryChangeSchema)
1175
+ });
1176
+ const prepareReleaseSchema = z.object({ projectId: uuidSchema.readonly() });
1177
+ const createReleaseSchema = z.object({ projectId: uuidSchema.readonly() });
1178
+ const createPreviewReleaseSchema = z.object({ projectId: uuidSchema.readonly() });
1179
+ const releaseResultSchema = z.object({
1180
+ version: versionSchema,
1181
+ diff: releaseDiffSchema
1182
+ });
1183
+
1018
1184
  //#endregion
1019
1185
  //#region src/api/routes/content/v1/projects.ts
1020
1186
  const tags$3 = ["Content API v1"];
@@ -1136,29 +1302,36 @@ const router$5 = createRouter().openapi(createRoute({
1136
1302
  return c.json(count, 200);
1137
1303
  }).openapi(createRoute({
1138
1304
  summary: "Get one Collection",
1139
- description: "Retrieve a Collection by ID",
1305
+ description: "Retrieve a Collection by UUID or slug",
1140
1306
  method: "get",
1141
- path: "/{projectId}/collections/{collectionId}",
1307
+ path: "/{projectId}/collections/{collectionIdOrSlug}",
1142
1308
  tags: tags$2,
1143
1309
  request: { params: z.object({
1144
1310
  projectId: uuidSchema.openapi({ param: {
1145
1311
  name: "projectId",
1146
1312
  in: "path"
1147
1313
  } }),
1148
- collectionId: uuidSchema.openapi({ param: {
1149
- name: "collectionId",
1150
- in: "path"
1151
- } })
1314
+ collectionIdOrSlug: z.string().openapi({
1315
+ param: {
1316
+ name: "collectionIdOrSlug",
1317
+ in: "path"
1318
+ },
1319
+ description: "Collection UUID or slug"
1320
+ })
1152
1321
  }) },
1153
1322
  responses: { [200]: {
1154
1323
  content: { "application/json": { schema: collectionSchema } },
1155
1324
  description: "The requested Collection"
1156
1325
  } }
1157
1326
  }), async (c) => {
1158
- const { projectId, collectionId } = c.req.valid("param");
1327
+ const { projectId, collectionIdOrSlug } = c.req.valid("param");
1328
+ const resolvedId = await c.var.collectionService.resolveCollectionId({
1329
+ projectId,
1330
+ idOrSlug: collectionIdOrSlug
1331
+ });
1159
1332
  const collection = await c.var.collectionService.read({
1160
1333
  projectId,
1161
- id: collectionId
1334
+ id: resolvedId
1162
1335
  });
1163
1336
  return c.json(collection, 200);
1164
1337
  });
@@ -1170,7 +1343,7 @@ const router$4 = createRouter().openapi(createRoute({
1170
1343
  summary: "List Entries",
1171
1344
  description: "Lists all Entries of the given Projects Collection",
1172
1345
  method: "get",
1173
- path: "/{projectId}/collections/{collectionId}/entries",
1346
+ path: "/{projectId}/collections/{collectionIdOrSlug}/entries",
1174
1347
  tags: tags$1,
1175
1348
  request: {
1176
1349
  params: z.object({
@@ -1178,10 +1351,13 @@ const router$4 = createRouter().openapi(createRoute({
1178
1351
  name: "projectId",
1179
1352
  in: "path"
1180
1353
  } }),
1181
- collectionId: uuidSchema.openapi({ param: {
1182
- name: "collectionId",
1183
- in: "path"
1184
- } })
1354
+ collectionIdOrSlug: z.string().openapi({
1355
+ param: {
1356
+ name: "collectionIdOrSlug",
1357
+ in: "path"
1358
+ },
1359
+ description: "Collection UUID or slug"
1360
+ })
1185
1361
  }),
1186
1362
  query: z.object({
1187
1363
  limit: z.string().pipe(z.coerce.number()).optional().openapi({
@@ -1199,8 +1375,12 @@ const router$4 = createRouter().openapi(createRoute({
1199
1375
  description: "A list of Entries for the given Projects Collection"
1200
1376
  } }
1201
1377
  }), async (c) => {
1202
- const { projectId, collectionId } = c.req.valid("param");
1378
+ const { projectId, collectionIdOrSlug } = c.req.valid("param");
1203
1379
  const { limit, offset } = c.req.valid("query");
1380
+ const collectionId = await c.var.collectionService.resolveCollectionId({
1381
+ projectId,
1382
+ idOrSlug: collectionIdOrSlug
1383
+ });
1204
1384
  const entries = await c.var.entryService.list({
1205
1385
  projectId,
1206
1386
  collectionId,
@@ -1212,24 +1392,31 @@ const router$4 = createRouter().openapi(createRoute({
1212
1392
  summary: "Count Entries",
1213
1393
  description: "Counts all Entries of the given Projects Collection",
1214
1394
  method: "get",
1215
- path: "/{projectId}/collections/{collectionId}/entries/count",
1395
+ path: "/{projectId}/collections/{collectionIdOrSlug}/entries/count",
1216
1396
  tags: tags$1,
1217
1397
  request: { params: z.object({
1218
1398
  projectId: uuidSchema.openapi({ param: {
1219
1399
  name: "projectId",
1220
1400
  in: "path"
1221
1401
  } }),
1222
- collectionId: uuidSchema.openapi({ param: {
1223
- name: "collectionId",
1224
- in: "path"
1225
- } })
1402
+ collectionIdOrSlug: z.string().openapi({
1403
+ param: {
1404
+ name: "collectionIdOrSlug",
1405
+ in: "path"
1406
+ },
1407
+ description: "Collection UUID or slug"
1408
+ })
1226
1409
  }) },
1227
1410
  responses: { [200]: {
1228
1411
  content: { "application/json": { schema: z.number() } },
1229
1412
  description: "The number of Entries of the given Projects Collection"
1230
1413
  } }
1231
1414
  }), async (c) => {
1232
- const { projectId, collectionId } = c.req.valid("param");
1415
+ const { projectId, collectionIdOrSlug } = c.req.valid("param");
1416
+ const collectionId = await c.var.collectionService.resolveCollectionId({
1417
+ projectId,
1418
+ idOrSlug: collectionIdOrSlug
1419
+ });
1233
1420
  const count = await c.var.entryService.count({
1234
1421
  projectId,
1235
1422
  collectionId
@@ -1239,17 +1426,20 @@ const router$4 = createRouter().openapi(createRoute({
1239
1426
  summary: "Get one Entry",
1240
1427
  description: "Retrieve an Entry by ID",
1241
1428
  method: "get",
1242
- path: "/{projectId}/collections/{collectionId}/entries/{entryId}",
1429
+ path: "/{projectId}/collections/{collectionIdOrSlug}/entries/{entryId}",
1243
1430
  tags: tags$1,
1244
1431
  request: { params: z.object({
1245
1432
  projectId: uuidSchema.openapi({ param: {
1246
1433
  name: "projectId",
1247
1434
  in: "path"
1248
1435
  } }),
1249
- collectionId: uuidSchema.openapi({ param: {
1250
- name: "collectionId",
1251
- in: "path"
1252
- } }),
1436
+ collectionIdOrSlug: z.string().openapi({
1437
+ param: {
1438
+ name: "collectionIdOrSlug",
1439
+ in: "path"
1440
+ },
1441
+ description: "Collection UUID or slug"
1442
+ }),
1253
1443
  entryId: uuidSchema.openapi({ param: {
1254
1444
  name: "entryId",
1255
1445
  in: "path"
@@ -1260,7 +1450,11 @@ const router$4 = createRouter().openapi(createRoute({
1260
1450
  description: "The requested Entry"
1261
1451
  } }
1262
1452
  }), async (c) => {
1263
- const { projectId, collectionId, entryId } = c.req.valid("param");
1453
+ const { projectId, collectionIdOrSlug, entryId } = c.req.valid("param");
1454
+ const collectionId = await c.var.collectionService.resolveCollectionId({
1455
+ projectId,
1456
+ idOrSlug: collectionIdOrSlug
1457
+ });
1264
1458
  const entry = await c.var.entryService.read({
1265
1459
  projectId,
1266
1460
  collectionId,
@@ -1527,6 +1721,9 @@ const pathTo = {
1527
1721
  collectionFile: (projectId, id) => {
1528
1722
  return Path.join(pathTo.collection(projectId, id), "collection.json");
1529
1723
  },
1724
+ collectionIndex: (projectId) => {
1725
+ return Path.join(pathTo.collections(projectId), "index.json");
1726
+ },
1530
1727
  entries: (projectId, collectionId) => {
1531
1728
  return Path.join(pathTo.collection(projectId, collectionId));
1532
1729
  },
@@ -1749,6 +1946,38 @@ var AbstractCrudService = class {
1749
1946
  }
1750
1947
  };
1751
1948
 
1949
+ //#endregion
1950
+ //#region src/service/migrations/applyMigrations.ts
1951
+ function applyMigrations(data, migrations, targetVersion) {
1952
+ let current = structuredClone(data);
1953
+ while (current["coreVersion"] !== targetVersion) {
1954
+ const migration = migrations.find((m) => m.from === current["coreVersion"]);
1955
+ if (!migration) {
1956
+ current["coreVersion"] = targetVersion;
1957
+ break;
1958
+ }
1959
+ current = migration.run(current);
1960
+ current["coreVersion"] = migration.to;
1961
+ }
1962
+ return current;
1963
+ }
1964
+
1965
+ //#endregion
1966
+ //#region src/service/migrations/assetMigrations.ts
1967
+ const assetMigrations = [];
1968
+
1969
+ //#endregion
1970
+ //#region src/service/migrations/collectionMigrations.ts
1971
+ const collectionMigrations = [];
1972
+
1973
+ //#endregion
1974
+ //#region src/service/migrations/entryMigrations.ts
1975
+ const entryMigrations = [];
1976
+
1977
+ //#endregion
1978
+ //#region src/service/migrations/projectMigrations.ts
1979
+ const projectMigrations = [];
1980
+
1752
1981
  //#endregion
1753
1982
  //#region src/util/shared.ts
1754
1983
  /**
@@ -1791,10 +2020,12 @@ function slug(string) {
1791
2020
  * Service that manages CRUD functionality for Asset files on disk
1792
2021
  */
1793
2022
  var AssetService = class extends AbstractCrudService {
2023
+ coreVersion;
1794
2024
  jsonFileService;
1795
2025
  gitService;
1796
- constructor(options, logService, jsonFileService, gitService) {
2026
+ constructor(coreVersion, options, logService, jsonFileService, gitService) {
1797
2027
  super(serviceTypeSchema.enum.Asset, options, logService);
2028
+ this.coreVersion = coreVersion;
1798
2029
  this.jsonFileService = jsonFileService;
1799
2030
  this.gitService = gitService;
1800
2031
  }
@@ -1814,6 +2045,7 @@ var AssetService = class extends AbstractCrudService {
1814
2045
  name: slug(props.name),
1815
2046
  objectType: "asset",
1816
2047
  id,
2048
+ coreVersion: this.coreVersion,
1817
2049
  created: datetime(),
1818
2050
  updated: null,
1819
2051
  extension: fileType.extension,
@@ -1858,6 +2090,13 @@ var AssetService = class extends AbstractCrudService {
1858
2090
  }
1859
2091
  }
1860
2092
  /**
2093
+ * Returns the commit history of an Asset
2094
+ */
2095
+ async history(props) {
2096
+ assetHistorySchema.parse(props);
2097
+ return this.gitService.log(pathTo.project(props.projectId), { filePath: pathTo.assetFile(props.projectId, props.id) });
2098
+ }
2099
+ /**
1861
2100
  * Copies an Asset to given file path on disk
1862
2101
  */
1863
2102
  async save(props) {
@@ -1966,13 +2205,11 @@ var AssetService = class extends AbstractCrudService {
1966
2205
  * @param projectId The project's ID
1967
2206
  * @param assetFile The AssetFile to convert
1968
2207
  */
1969
- async toAsset(projectId, assetFile, commitHash) {
2208
+ toAsset(projectId, assetFile, commitHash) {
1970
2209
  const assetPath = commitHash ? pathTo.tmpAsset(assetFile.id, commitHash, assetFile.extension) : pathTo.asset(projectId, assetFile.id, assetFile.extension);
1971
- const history = await this.gitService.log(pathTo.project(projectId), { filePath: pathTo.assetFile(projectId, assetFile.id) });
1972
2210
  return {
1973
2211
  ...assetFile,
1974
- absolutePath: assetPath,
1975
- history
2212
+ absolutePath: assetPath
1976
2213
  };
1977
2214
  }
1978
2215
  /**
@@ -1995,7 +2232,8 @@ var AssetService = class extends AbstractCrudService {
1995
2232
  * Migrates an potentially outdated Asset file to the current schema
1996
2233
  */
1997
2234
  migrate(potentiallyOutdatedAssetFile) {
1998
- return assetFileSchema.parse(potentiallyOutdatedAssetFile);
2235
+ const migrated = applyMigrations(migrateAssetSchema.parse(potentiallyOutdatedAssetFile), assetMigrations, this.coreVersion);
2236
+ return assetFileSchema.parse(migrated);
1999
2237
  }
2000
2238
  };
2001
2239
 
@@ -2005,29 +2243,59 @@ var AssetService = class extends AbstractCrudService {
2005
2243
  * Service that manages CRUD functionality for Collection files on disk
2006
2244
  */
2007
2245
  var CollectionService = class extends AbstractCrudService {
2246
+ coreVersion;
2008
2247
  jsonFileService;
2009
2248
  gitService;
2010
- constructor(options, logService, jsonFileService, gitService) {
2249
+ /** In-memory cache for collection indices, keyed by projectId */
2250
+ cachedIndex = /* @__PURE__ */ new Map();
2251
+ /** Promise deduplication for concurrent rebuilds, keyed by projectId */
2252
+ rebuildPromise = /* @__PURE__ */ new Map();
2253
+ constructor(coreVersion, options, logService, jsonFileService, gitService) {
2011
2254
  super(serviceTypeSchema.enum.Collection, options, logService);
2255
+ this.coreVersion = coreVersion;
2012
2256
  this.jsonFileService = jsonFileService;
2013
2257
  this.gitService = gitService;
2014
2258
  }
2015
2259
  /**
2260
+ * Resolves a UUID-or-slug string to a collection UUID.
2261
+ *
2262
+ * If the input matches UUID format, verifies the folder exists on disk first.
2263
+ * If the folder doesn't exist, falls back to slug lookup.
2264
+ * Otherwise, looks up via the index.
2265
+ */
2266
+ async resolveCollectionId(props) {
2267
+ if (uuidSchema.safeParse(props.idOrSlug).success) {
2268
+ const collectionPath = pathTo.collection(props.projectId, props.idOrSlug);
2269
+ if (await Fs.pathExists(collectionPath)) return props.idOrSlug;
2270
+ }
2271
+ const index = await this.getIndex(props.projectId);
2272
+ for (const [uuid, slugValue] of Object.entries(index)) if (slugValue === props.idOrSlug) return uuid;
2273
+ this.cachedIndex.delete(props.projectId);
2274
+ const freshIndex = await this.getIndex(props.projectId);
2275
+ for (const [uuid, slugValue] of Object.entries(freshIndex)) if (slugValue === props.idOrSlug) return uuid;
2276
+ throw new Error(`Collection not found: "${props.idOrSlug}" does not match any collection UUID or slug`);
2277
+ }
2278
+ /**
2016
2279
  * Creates a new Collection
2017
2280
  */
2018
2281
  async create(props) {
2019
2282
  createCollectionSchema.parse(props);
2283
+ this.validateFieldDefinitionSlugUniqueness(props.fieldDefinitions);
2020
2284
  const id = uuid();
2021
2285
  const projectPath = pathTo.project(props.projectId);
2022
2286
  const collectionPath = pathTo.collection(props.projectId, id);
2023
2287
  const collectionFilePath = pathTo.collectionFile(props.projectId, id);
2288
+ const slugPlural = slug(props.slug.plural);
2289
+ const index = await this.getIndex(props.projectId);
2290
+ if (Object.values(index).includes(slugPlural)) throw new Error(`Collection slug "${slugPlural}" is already in use by another collection`);
2024
2291
  const collectionFile = {
2025
2292
  ...props,
2026
2293
  objectType: "collection",
2027
2294
  id,
2295
+ coreVersion: this.coreVersion,
2028
2296
  slug: {
2029
2297
  singular: slug(props.slug.singular),
2030
- plural: slug(props.slug.plural)
2298
+ plural: slugPlural
2031
2299
  },
2032
2300
  created: datetime(),
2033
2301
  updated: null
@@ -2042,7 +2310,9 @@ var CollectionService = class extends AbstractCrudService {
2042
2310
  id
2043
2311
  }
2044
2312
  });
2045
- return this.toCollection(props.projectId, collectionFile);
2313
+ index[id] = slugPlural;
2314
+ await this.writeIndex(props.projectId, index);
2315
+ return this.toCollection(collectionFile);
2046
2316
  }
2047
2317
  /**
2048
2318
  * Returns a Collection by ID
@@ -2053,33 +2323,103 @@ var CollectionService = class extends AbstractCrudService {
2053
2323
  readCollectionSchema.parse(props);
2054
2324
  if (!props.commitHash) {
2055
2325
  const collectionFile = await this.jsonFileService.read(pathTo.collectionFile(props.projectId, props.id), collectionFileSchema);
2056
- return this.toCollection(props.projectId, collectionFile);
2326
+ return this.toCollection(collectionFile);
2057
2327
  } else {
2058
2328
  const collectionFile = this.migrate(JSON.parse(await this.gitService.getFileContentAtCommit(pathTo.project(props.projectId), pathTo.collectionFile(props.projectId, props.id), props.commitHash)));
2059
- return this.toCollection(props.projectId, collectionFile);
2329
+ return this.toCollection(collectionFile);
2060
2330
  }
2061
2331
  }
2062
2332
  /**
2333
+ * Reads a Collection by its slug
2334
+ */
2335
+ async readBySlug(props) {
2336
+ const id = await this.resolveCollectionId({
2337
+ projectId: props.projectId,
2338
+ idOrSlug: props.slug
2339
+ });
2340
+ return this.read({
2341
+ projectId: props.projectId,
2342
+ id,
2343
+ commitHash: props.commitHash
2344
+ });
2345
+ }
2346
+ /**
2347
+ * Returns the commit history of a Collection
2348
+ */
2349
+ async history(props) {
2350
+ collectionHistorySchema.parse(props);
2351
+ return this.gitService.log(pathTo.project(props.projectId), { filePath: pathTo.collectionFile(props.projectId, props.id) });
2352
+ }
2353
+ /**
2063
2354
  * Updates given Collection
2064
2355
  *
2065
- * @todo finish implementing checks for FieldDefinitions and extract methods
2066
- *
2067
- * @param projectId Project ID of the collection to update
2068
- * @param collection Collection to write to disk
2069
- * @returns An object containing information about the actions needed to be taken,
2070
- * before given update can be executed or void if the update was executed successfully
2356
+ * Handles fieldDefinition slug rename cascade and collection slug uniqueness.
2071
2357
  */
2072
2358
  async update(props) {
2073
2359
  updateCollectionSchema.parse(props);
2360
+ this.validateFieldDefinitionSlugUniqueness(props.fieldDefinitions);
2074
2361
  const projectPath = pathTo.project(props.projectId);
2075
2362
  const collectionFilePath = pathTo.collectionFile(props.projectId, props.id);
2363
+ const prevCollectionFile = await this.read(props);
2076
2364
  const collectionFile = {
2077
- ...await this.read(props),
2365
+ ...prevCollectionFile,
2078
2366
  ...props,
2079
2367
  updated: datetime()
2080
2368
  };
2369
+ const oldFieldDefs = prevCollectionFile.fieldDefinitions;
2370
+ const newFieldDefs = props.fieldDefinitions;
2371
+ const slugRenames = [];
2372
+ const oldByUuid = new Map(oldFieldDefs.map((fd) => [fd.id, fd]));
2373
+ for (const newFd of newFieldDefs) {
2374
+ const oldFd = oldByUuid.get(newFd.id);
2375
+ if (oldFd && oldFd.slug !== newFd.slug) slugRenames.push({
2376
+ oldSlug: oldFd.slug,
2377
+ newSlug: newFd.slug
2378
+ });
2379
+ }
2380
+ const filesToGitAdd = [collectionFilePath];
2381
+ if (slugRenames.length > 0) {
2382
+ const entriesPath = pathTo.entries(props.projectId, props.id);
2383
+ if (await Fs.pathExists(entriesPath)) {
2384
+ const entryFiles = (await Fs.readdir(entriesPath)).filter((f) => f.endsWith(".json") && f !== "collection.json");
2385
+ for (const entryFileName of entryFiles) {
2386
+ const entryFilePath = pathTo.entryFile(props.projectId, props.id, entryFileName.replace(".json", ""));
2387
+ try {
2388
+ const entryFile = await this.jsonFileService.read(entryFilePath, entryFileSchema);
2389
+ let changed = false;
2390
+ const newValues = { ...entryFile.values };
2391
+ for (const { oldSlug, newSlug } of slugRenames) if (oldSlug in newValues) {
2392
+ newValues[newSlug] = newValues[oldSlug];
2393
+ delete newValues[oldSlug];
2394
+ changed = true;
2395
+ }
2396
+ if (changed) {
2397
+ const updatedEntryFile = {
2398
+ ...entryFile,
2399
+ values: newValues
2400
+ };
2401
+ await this.jsonFileService.update(updatedEntryFile, entryFilePath, entryFileSchema);
2402
+ filesToGitAdd.push(entryFilePath);
2403
+ }
2404
+ } catch (error) {
2405
+ this.logService.warn({
2406
+ source: "core",
2407
+ message: `Failed to update entry "${entryFileName}" during slug rename cascade: ${error instanceof Error ? error.message : String(error)}`
2408
+ });
2409
+ }
2410
+ }
2411
+ }
2412
+ }
2413
+ const newSlugPlural = slug(props.slug.plural);
2414
+ if (prevCollectionFile.slug.plural !== newSlugPlural) {
2415
+ const index = await this.getIndex(props.projectId);
2416
+ const existingUuid = Object.entries(index).find(([, s]) => s === newSlugPlural);
2417
+ if (existingUuid && existingUuid[0] !== props.id) throw new Error(`Collection slug "${newSlugPlural}" is already in use by another collection`);
2418
+ index[props.id] = newSlugPlural;
2419
+ await this.writeIndex(props.projectId, index);
2420
+ }
2081
2421
  await this.jsonFileService.update(collectionFile, collectionFilePath, collectionFileSchema);
2082
- await this.gitService.add(projectPath, [collectionFilePath]);
2422
+ await this.gitService.add(projectPath, filesToGitAdd);
2083
2423
  await this.gitService.commit(projectPath, {
2084
2424
  method: "update",
2085
2425
  reference: {
@@ -2087,10 +2427,10 @@ var CollectionService = class extends AbstractCrudService {
2087
2427
  id: collectionFile.id
2088
2428
  }
2089
2429
  });
2090
- return this.toCollection(props.projectId, collectionFile);
2430
+ return this.toCollection(collectionFile);
2091
2431
  }
2092
2432
  /**
2093
- * Deletes given Collection (folder), including it's items
2433
+ * Deletes given Collection (folder), including it's Entries
2094
2434
  *
2095
2435
  * The Fields that Collection used are not deleted.
2096
2436
  */
@@ -2107,6 +2447,9 @@ var CollectionService = class extends AbstractCrudService {
2107
2447
  id: props.id
2108
2448
  }
2109
2449
  });
2450
+ const index = await this.getIndex(props.projectId);
2451
+ delete index[props.id];
2452
+ await this.writeIndex(props.projectId, index);
2110
2453
  }
2111
2454
  async list(props) {
2112
2455
  listCollectionsSchema.parse(props);
@@ -2141,7 +2484,8 @@ var CollectionService = class extends AbstractCrudService {
2141
2484
  * Migrates an potentially outdated Collection file to the current schema
2142
2485
  */
2143
2486
  migrate(potentiallyOutdatedCollectionFile) {
2144
- return collectionFileSchema.parse(potentiallyOutdatedCollectionFile);
2487
+ const migrated = applyMigrations(migrateCollectionSchema.parse(potentiallyOutdatedCollectionFile), collectionMigrations, this.coreVersion);
2488
+ return collectionFileSchema.parse(migrated);
2145
2489
  }
2146
2490
  /**
2147
2491
  * Creates an Collection from given CollectionFile
@@ -2149,26 +2493,83 @@ var CollectionService = class extends AbstractCrudService {
2149
2493
  * @param projectId The project's ID
2150
2494
  * @param collectionFile The CollectionFile to convert
2151
2495
  */
2152
- async toCollection(projectId, collectionFile) {
2153
- const history = await this.gitService.log(pathTo.project(projectId), { filePath: pathTo.collectionFile(projectId, collectionFile.id) });
2154
- return {
2155
- ...collectionFile,
2156
- history
2157
- };
2496
+ toCollection(collectionFile) {
2497
+ return { ...collectionFile };
2158
2498
  }
2159
- };
2160
-
2499
+ /**
2500
+ * Gets the collection index, rebuilding from disk if not cached
2501
+ */
2502
+ async getIndex(projectId) {
2503
+ const cached = this.cachedIndex.get(projectId);
2504
+ if (cached) return cached;
2505
+ const pending = this.rebuildPromise.get(projectId);
2506
+ if (pending) return pending;
2507
+ const promise = this.rebuildIndex(projectId);
2508
+ this.rebuildPromise.set(projectId, promise);
2509
+ const result = await promise;
2510
+ this.cachedIndex.set(projectId, result);
2511
+ this.rebuildPromise.delete(projectId);
2512
+ return result;
2513
+ }
2514
+ /**
2515
+ * Writes the index file atomically and updates cache
2516
+ */
2517
+ async writeIndex(projectId, index) {
2518
+ const indexPath = pathTo.collectionIndex(projectId);
2519
+ await Fs.writeFile(indexPath, JSON.stringify(index, null, 2), { encoding: "utf8" });
2520
+ this.cachedIndex.set(projectId, index);
2521
+ }
2522
+ /**
2523
+ * Rebuilds the index by scanning all collection folders
2524
+ */
2525
+ async rebuildIndex(projectId) {
2526
+ this.logService.info({
2527
+ source: "core",
2528
+ message: `Rebuilding Collection index for Project "${projectId}"`
2529
+ });
2530
+ const index = {};
2531
+ const collectionFolders = await folders(pathTo.collections(projectId));
2532
+ for (const folder of collectionFolders) {
2533
+ if (!uuidSchema.safeParse(folder.name).success) continue;
2534
+ try {
2535
+ const collectionFilePath = pathTo.collectionFile(projectId, folder.name);
2536
+ const collectionFile = await this.jsonFileService.read(collectionFilePath, collectionFileSchema);
2537
+ index[collectionFile.id] = collectionFile.slug.plural;
2538
+ } catch (error) {
2539
+ this.logService.warn({
2540
+ source: "core",
2541
+ message: `Skipping collection folder "${folder.name}" during index rebuild: ${error instanceof Error ? error.message : String(error)}`
2542
+ });
2543
+ }
2544
+ }
2545
+ await this.writeIndex(projectId, index);
2546
+ return index;
2547
+ }
2548
+ /**
2549
+ * Validates that no two fieldDefinitions share the same slug
2550
+ */
2551
+ validateFieldDefinitionSlugUniqueness(fieldDefinitions) {
2552
+ const seen = /* @__PURE__ */ new Set();
2553
+ for (const fd of fieldDefinitions) {
2554
+ if (seen.has(fd.slug)) throw new Error(`Duplicate fieldDefinition slug "${fd.slug}": each fieldDefinition within a collection must have a unique slug`);
2555
+ seen.add(fd.slug);
2556
+ }
2557
+ }
2558
+ };
2559
+
2161
2560
  //#endregion
2162
2561
  //#region src/service/EntryService.ts
2163
2562
  /**
2164
2563
  * Service that manages CRUD functionality for Entry files on disk
2165
2564
  */
2166
2565
  var EntryService = class extends AbstractCrudService {
2566
+ coreVersion;
2167
2567
  jsonFileService;
2168
2568
  gitService;
2169
2569
  collectionService;
2170
- constructor(options, logService, jsonFileService, gitService, collectionService) {
2570
+ constructor(coreVersion, options, logService, jsonFileService, gitService, collectionService) {
2171
2571
  super(serviceTypeSchema.enum.Entry, options, logService);
2572
+ this.coreVersion = coreVersion;
2172
2573
  this.jsonFileService = jsonFileService;
2173
2574
  this.gitService = gitService;
2174
2575
  this.collectionService = collectionService;
@@ -2188,11 +2589,12 @@ var EntryService = class extends AbstractCrudService {
2188
2589
  const entryFile = {
2189
2590
  objectType: "entry",
2190
2591
  id,
2592
+ coreVersion: this.coreVersion,
2191
2593
  values: props.values,
2192
2594
  created: datetime(),
2193
2595
  updated: null
2194
2596
  };
2195
- const entry = await this.toEntry(props.projectId, props.collectionId, entryFile);
2597
+ const entry = this.toEntry(entryFile);
2196
2598
  getCreateEntrySchemaFromFieldDefinitions(collection.fieldDefinitions).parse(props);
2197
2599
  await this.jsonFileService.create(entryFile, entryFilePath, entryFileSchema);
2198
2600
  await this.gitService.add(projectPath, [entryFilePath]);
@@ -2215,13 +2617,20 @@ var EntryService = class extends AbstractCrudService {
2215
2617
  readEntrySchema.parse(props);
2216
2618
  if (!props.commitHash) {
2217
2619
  const entryFile = await this.jsonFileService.read(pathTo.entryFile(props.projectId, props.collectionId, props.id), entryFileSchema);
2218
- return this.toEntry(props.projectId, props.collectionId, entryFile);
2620
+ return this.toEntry(entryFile);
2219
2621
  } else {
2220
2622
  const entryFile = this.migrate(JSON.parse(await this.gitService.getFileContentAtCommit(pathTo.project(props.projectId), pathTo.entryFile(props.projectId, props.collectionId, props.id), props.commitHash)));
2221
- return this.toEntry(props.projectId, props.collectionId, entryFile);
2623
+ return this.toEntry(entryFile);
2222
2624
  }
2223
2625
  }
2224
2626
  /**
2627
+ * Returns the commit history of an Entry
2628
+ */
2629
+ async history(props) {
2630
+ entryHistorySchema.parse(props);
2631
+ return this.gitService.log(pathTo.project(props.projectId), { filePath: pathTo.entryFile(props.projectId, props.collectionId, props.id) });
2632
+ }
2633
+ /**
2225
2634
  * Updates an Entry of given Collection with new Values and shared Values
2226
2635
  */
2227
2636
  async update(props) {
@@ -2241,7 +2650,7 @@ var EntryService = class extends AbstractCrudService {
2241
2650
  values: props.values,
2242
2651
  updated: datetime()
2243
2652
  };
2244
- const entry = await this.toEntry(props.projectId, props.collectionId, entryFile);
2653
+ const entry = this.toEntry(entryFile);
2245
2654
  getUpdateEntrySchemaFromFieldDefinitions(collection.fieldDefinitions).parse(props);
2246
2655
  await this.jsonFileService.update(entryFile, entryFilePath, entryFileSchema);
2247
2656
  await this.gitService.add(projectPath, [entryFilePath]);
@@ -2307,17 +2716,14 @@ var EntryService = class extends AbstractCrudService {
2307
2716
  * Migrates an potentially outdated Entry file to the current schema
2308
2717
  */
2309
2718
  migrate(potentiallyOutdatedEntryFile) {
2310
- return entryFileSchema.parse(potentiallyOutdatedEntryFile);
2719
+ const migrated = applyMigrations(migrateEntrySchema.parse(potentiallyOutdatedEntryFile), entryMigrations, this.coreVersion);
2720
+ return entryFileSchema.parse(migrated);
2311
2721
  }
2312
2722
  /**
2313
2723
  * Creates an Entry from given EntryFile by resolving it's Values
2314
2724
  */
2315
- async toEntry(projectId, collectionId, entryFile) {
2316
- const history = await this.gitService.log(pathTo.project(projectId), { filePath: pathTo.entryFile(projectId, collectionId, entryFile.id) });
2317
- return {
2318
- ...entryFile,
2319
- history
2320
- };
2725
+ toEntry(entryFile) {
2726
+ return { ...entryFile };
2321
2727
  }
2322
2728
  };
2323
2729
 
@@ -2346,10 +2752,11 @@ var GitTagService = class extends AbstractCrudService {
2346
2752
  id
2347
2753
  ];
2348
2754
  if (props.hash) args = [...args, props.hash];
2755
+ const fullMessage = `${this.serializeTagMessage(props.message)}\n\n${this.tagMessageToTrailers(props.message).join("\n")}`;
2349
2756
  args = [
2350
2757
  ...args,
2351
2758
  "-m",
2352
- props.message
2759
+ fullMessage
2353
2760
  ];
2354
2761
  await this.git(props.path, args);
2355
2762
  return await this.read({
@@ -2408,27 +2815,33 @@ var GitTagService = class extends AbstractCrudService {
2408
2815
  async list(props) {
2409
2816
  listGitTagsSchema.parse(props);
2410
2817
  let args = ["tag", "--list"];
2818
+ const format = [
2819
+ "%(refname:short)",
2820
+ "%(trailers:key=Type,valueonly)",
2821
+ "%(trailers:key=Version,valueonly)",
2822
+ "%(trailers:key=Core-Version,valueonly)",
2823
+ "%(*authorname)",
2824
+ "%(*authoremail)",
2825
+ "%(*authordate:iso-strict)"
2826
+ ].join("|");
2411
2827
  args = [
2412
2828
  ...args,
2413
2829
  "--sort=-*authordate",
2414
- "--format=%(refname:short)|%(subject)|%(*authorname)|%(*authoremail)|%(*authordate:iso-strict)"
2830
+ `--format=${format}`
2415
2831
  ];
2416
- const gitTags = (await this.git(props.path, args)).stdout.split("\n").filter((line) => {
2832
+ const gitTags = (await this.git(props.path, args)).stdout.replace(/\n\|/g, "|").split("\n").filter((line) => {
2417
2833
  return line.trim() !== "";
2418
2834
  }).map((line) => {
2419
2835
  const lineArray = line.split("|");
2420
- if (lineArray[3]?.startsWith("<") && lineArray[3]?.endsWith(">")) {
2421
- lineArray[3] = lineArray[3].slice(1, -1);
2422
- lineArray[3] = lineArray[3].slice(0, -1);
2423
- }
2836
+ if (lineArray[5]?.startsWith("<") && lineArray[5]?.endsWith(">")) lineArray[5] = lineArray[5].slice(1, -1);
2424
2837
  return {
2425
2838
  id: lineArray[0],
2426
- message: lineArray[1],
2839
+ message: this.parseTagTrailers(lineArray[1]?.trim(), lineArray[2]?.trim(), lineArray[3]?.trim()),
2427
2840
  author: {
2428
- name: lineArray[2],
2429
- email: lineArray[3]
2841
+ name: lineArray[4],
2842
+ email: lineArray[5]
2430
2843
  },
2431
- datetime: datetime(lineArray[4])
2844
+ datetime: datetime(lineArray[6])
2432
2845
  };
2433
2846
  }).filter(this.isGitTag.bind(this));
2434
2847
  return {
@@ -2451,6 +2864,43 @@ var GitTagService = class extends AbstractCrudService {
2451
2864
  return (await this.list({ path: props.path })).total;
2452
2865
  }
2453
2866
  /**
2867
+ * Serializes a GitTagMessage into a human-readable subject line
2868
+ */
2869
+ serializeTagMessage(message) {
2870
+ return `${message.type.charAt(0).toUpperCase() + message.type.slice(1)} ${message.type === "upgrade" ? message.coreVersion : message.version}`;
2871
+ }
2872
+ /**
2873
+ * Converts a GitTagMessage into git trailer lines
2874
+ */
2875
+ tagMessageToTrailers(message) {
2876
+ const trailers = [`Type: ${message.type}`];
2877
+ if (message.type === "upgrade") trailers.push(`Core-Version: ${message.coreVersion}`);
2878
+ else trailers.push(`Version: ${message.version}`);
2879
+ return trailers;
2880
+ }
2881
+ /**
2882
+ * Parses git trailer values back into a GitTagMessage
2883
+ */
2884
+ parseTagTrailers(type, version, coreVersion) {
2885
+ switch (type) {
2886
+ case "upgrade": return gitTagMessageSchema.parse({
2887
+ type,
2888
+ coreVersion
2889
+ });
2890
+ case "release":
2891
+ case "preview": return gitTagMessageSchema.parse({
2892
+ type,
2893
+ version
2894
+ });
2895
+ default:
2896
+ this.logService.warn({
2897
+ source: "core",
2898
+ message: `Tag with ID "${type}" has an invalid or missing Type trailer and will be ignored`
2899
+ });
2900
+ return null;
2901
+ }
2902
+ }
2903
+ /**
2454
2904
  * Type guard for GitTag
2455
2905
  *
2456
2906
  * @param obj The object to check
@@ -2740,9 +3190,16 @@ var GitService = class {
2740
3190
  gitMessageSchema.parse(message);
2741
3191
  const user = await this.userService.get();
2742
3192
  if (!user) throw new NoCurrentUserError();
3193
+ const subject = `${message.method.charAt(0).toUpperCase() + message.method.slice(1)} ${message.reference.objectType} ${message.reference.id}`;
3194
+ const trailers = [
3195
+ `Method: ${message.method}`,
3196
+ `Object-Type: ${message.reference.objectType}`,
3197
+ `Object-Id: ${message.reference.id}`
3198
+ ];
3199
+ if (message.reference.collectionId) trailers.push(`Collection-Id: ${message.reference.collectionId}`);
2743
3200
  const args = [
2744
3201
  "commit",
2745
- `--message=${JSON.stringify(message)}`,
3202
+ `--message=${`${subject}\n\n${trailers.join("\n")}`}`,
2746
3203
  `--author=${user.name} <${user.email}>`
2747
3204
  ];
2748
3205
  await this.git(path, args);
@@ -2762,33 +3219,52 @@ var GitService = class {
2762
3219
  let args = ["log"];
2763
3220
  if (options?.between?.from) args = [...args, `${options.between.from}..${options.between.to || "HEAD"}`];
2764
3221
  if (options?.limit) args = [...args, `--max-count=${options.limit}`];
2765
- args = [...args, "--format=%H|%s|%an|%ae|%aI|%D"];
3222
+ const format = [
3223
+ "%H",
3224
+ "%(trailers:key=Method,valueonly)",
3225
+ "%(trailers:key=Object-Type,valueonly)",
3226
+ "%(trailers:key=Object-Id,valueonly)",
3227
+ "%(trailers:key=Collection-Id,valueonly)",
3228
+ "%an",
3229
+ "%ae",
3230
+ "%aI",
3231
+ "%D"
3232
+ ].join("|");
3233
+ args = [...args, `--format=${format}`];
2766
3234
  if (options?.filePath) args = [
2767
3235
  ...args,
2768
3236
  "--",
2769
3237
  options.filePath
2770
3238
  ];
2771
- const noEmptyLinesArr = (await this.git(path, args)).stdout.split("\n").filter((line) => {
3239
+ const noEmptyLinesArr = (await this.git(path, args)).stdout.replace(/\n\|/g, "|").split("\n").filter((line) => {
2772
3240
  return line.trim() !== "";
2773
3241
  });
2774
3242
  return (await Promise.all(noEmptyLinesArr.map(async (line) => {
2775
3243
  const lineArray = line.split("|");
2776
- const tagId = this.refNameToTagName(lineArray[5] || "");
3244
+ const tagId = this.refNameToTagName(lineArray[8]?.trim() || "");
2777
3245
  const tag = tagId ? await this.tags.read({
2778
3246
  path,
2779
3247
  id: tagId
2780
3248
  }) : null;
3249
+ const collectionId = lineArray[4]?.trim();
2781
3250
  return {
2782
3251
  hash: lineArray[0],
2783
- message: JSON.parse(lineArray[1] || ""),
3252
+ message: {
3253
+ method: lineArray[1]?.trim(),
3254
+ reference: {
3255
+ objectType: lineArray[2]?.trim(),
3256
+ id: lineArray[3]?.trim(),
3257
+ ...collectionId ? { collectionId } : {}
3258
+ }
3259
+ },
2784
3260
  author: {
2785
- name: lineArray[2],
2786
- email: lineArray[3]
3261
+ name: lineArray[5],
3262
+ email: lineArray[6]
2787
3263
  },
2788
- datetime: datetime(lineArray[4]),
3264
+ datetime: datetime(lineArray[7]),
2789
3265
  tag
2790
3266
  };
2791
- }))).filter(this.isGitCommit.bind(this));
3267
+ }))).filter((obj) => this.isGitCommit(obj));
2792
3268
  }
2793
3269
  /**
2794
3270
  * Retrieves the content of a file at a specific commit
@@ -2802,6 +3278,35 @@ var GitService = class {
2802
3278
  };
2803
3279
  return (await this.git(path, args, { processCallback: setEncoding })).stdout;
2804
3280
  }
3281
+ /**
3282
+ * Lists directory entries at a specific commit
3283
+ *
3284
+ * Useful for discovering what files/folders existed at a past commit,
3285
+ * e.g. to detect deleted collections when comparing branches.
3286
+ *
3287
+ * @see https://git-scm.com/docs/git-ls-tree
3288
+ *
3289
+ * @param path Path to the repository
3290
+ * @param treePath Relative path within the repository to list
3291
+ * @param commitRef Commit hash, branch name, or other git ref
3292
+ */
3293
+ async listTreeAtCommit(path, treePath, commitRef) {
3294
+ const args = [
3295
+ "ls-tree",
3296
+ "--name-only",
3297
+ commitRef,
3298
+ `${treePath.replace(`${path}${Path.sep}`, "").split("\\").join("/")}/`
3299
+ ];
3300
+ try {
3301
+ return (await this.git(path, args)).stdout.split("\n").map((line) => line.trim()).filter((line) => line !== "").map((entry) => {
3302
+ const parts = entry.split("/");
3303
+ return parts[parts.length - 1] || entry;
3304
+ });
3305
+ } catch (error) {
3306
+ if (error instanceof GitError) return [];
3307
+ throw error;
3308
+ }
3309
+ }
2805
3310
  refNameToTagName(refName) {
2806
3311
  const tagName = refName.replace("tag: ", "").trim();
2807
3312
  if (tagName === "" || uuidSchema.safeParse(tagName).success === false) return null;
@@ -3170,7 +3675,6 @@ var ProjectService = class extends AbstractCrudService {
3170
3675
  created: datetime(),
3171
3676
  updated: null,
3172
3677
  coreVersion: this.coreVersion,
3173
- status: "todo",
3174
3678
  version: "0.0.1"
3175
3679
  };
3176
3680
  const projectPath = pathTo.project(id);
@@ -3224,11 +3728,23 @@ var ProjectService = class extends AbstractCrudService {
3224
3728
  const projectFile = await this.jsonFileService.read(pathTo.projectFile(props.id), projectFileSchema);
3225
3729
  return await this.toProject(projectFile);
3226
3730
  } else {
3227
- const projectFile = this.migrate(migrateProjectSchema.parse(JSON.parse(await this.gitService.getFileContentAtCommit(pathTo.project(props.id), pathTo.projectFile(props.id), props.commitHash))));
3731
+ const projectFile = this.migrate(JSON.parse(await this.gitService.getFileContentAtCommit(pathTo.project(props.id), pathTo.projectFile(props.id), props.commitHash)));
3228
3732
  return await this.toProject(projectFile);
3229
3733
  }
3230
3734
  }
3231
3735
  /**
3736
+ * Returns the commit history of a Project
3737
+ */
3738
+ async history(props) {
3739
+ projectHistorySchema.parse(props);
3740
+ const projectPath = pathTo.project(props.id);
3741
+ const fullHistory = await this.gitService.log(projectPath);
3742
+ return {
3743
+ history: await this.gitService.log(projectPath, { filePath: pathTo.projectFile(props.id) }),
3744
+ fullHistory
3745
+ };
3746
+ }
3747
+ /**
3232
3748
  * Updates given Project
3233
3749
  */
3234
3750
  async update(props) {
@@ -3261,7 +3777,7 @@ var ProjectService = class extends AbstractCrudService {
3261
3777
  const projectPath = pathTo.project(props.id);
3262
3778
  const projectFilePath = pathTo.projectFile(props.id);
3263
3779
  if (await this.gitService.branches.current(projectPath) !== projectBranchSchema.enum.work) await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.work);
3264
- const currentProjectFile = migrateProjectSchema.parse(await this.jsonFileService.unsafeRead(projectFilePath));
3780
+ const currentProjectFile = await this.jsonFileService.unsafeRead(projectFilePath);
3265
3781
  if (Semver.gt(currentProjectFile.coreVersion, this.coreVersion)) throw new ProjectUpgradeError(`The Projects Core version "${currentProjectFile.coreVersion}" is higher than the current Core version "${this.coreVersion}".`);
3266
3782
  if (Semver.eq(currentProjectFile.coreVersion, this.coreVersion) && props.force !== true) throw new ProjectUpgradeError(`The Projects Core version "${currentProjectFile.coreVersion}" is already up to date.`);
3267
3783
  const assetReferences = await this.listReferences("asset", props.id);
@@ -3298,7 +3814,10 @@ var ProjectService = class extends AbstractCrudService {
3298
3814
  });
3299
3815
  await this.gitService.tags.create({
3300
3816
  path: projectPath,
3301
- message: `Upgraded Project to Core version ${migratedProjectFile.coreVersion}`
3817
+ message: {
3818
+ type: "upgrade",
3819
+ coreVersion: migratedProjectFile.coreVersion
3820
+ }
3302
3821
  });
3303
3822
  await this.gitService.branches.delete(projectPath, upgradeBranchName, true);
3304
3823
  this.logService.info({
@@ -3404,7 +3923,7 @@ var ProjectService = class extends AbstractCrudService {
3404
3923
  return (await Promise.all(projectReferences.map(async (reference) => {
3405
3924
  const json = await this.jsonFileService.unsafeRead(pathTo.projectFile(reference.id));
3406
3925
  const projectFile = migrateProjectSchema.parse(json);
3407
- if (projectFile.coreVersion !== this.coreVersion) return projectFile;
3926
+ if (projectFile.coreVersion !== this.coreVersion) return this.migrate(projectFile);
3408
3927
  return null;
3409
3928
  }))).filter(isNotEmpty);
3410
3929
  }
@@ -3436,9 +3955,9 @@ var ProjectService = class extends AbstractCrudService {
3436
3955
  /**
3437
3956
  * Migrates an potentially outdated Project file to the current schema
3438
3957
  */
3439
- migrate(props) {
3440
- props.coreVersion = this.coreVersion;
3441
- return projectFileSchema.parse(props);
3958
+ migrate(potentiallyOutdatedFile) {
3959
+ const migrated = applyMigrations(migrateProjectSchema.parse(potentiallyOutdatedFile), projectMigrations, this.coreVersion);
3960
+ return projectFileSchema.parse(migrated);
3442
3961
  }
3443
3962
  /**
3444
3963
  * Creates a Project from given ProjectFile
@@ -3447,13 +3966,9 @@ var ProjectService = class extends AbstractCrudService {
3447
3966
  const projectPath = pathTo.project(projectFile.id);
3448
3967
  let remoteOriginUrl = null;
3449
3968
  if (await this.gitService.remotes.hasOrigin(projectPath)) remoteOriginUrl = await this.gitService.remotes.getOriginUrl(projectPath);
3450
- const fullHistory = await this.gitService.log(pathTo.project(projectFile.id));
3451
- const history = await this.gitService.log(pathTo.project(projectFile.id), { filePath: pathTo.projectFile(projectFile.id) });
3452
3969
  return {
3453
3970
  ...projectFile,
3454
- remoteOriginUrl,
3455
- history,
3456
- fullHistory
3971
+ remoteOriginUrl
3457
3972
  };
3458
3973
  }
3459
3974
  /**
@@ -3483,7 +3998,8 @@ var ProjectService = class extends AbstractCrudService {
3483
3998
  "!/.gitattributes",
3484
3999
  "!/**/.gitkeep",
3485
4000
  "",
3486
- "# elek.io related ignores"
4001
+ "# elek.io related ignores",
4002
+ "collections/index.json"
3487
4003
  ].join(Os.EOL));
3488
4004
  }
3489
4005
  async upgradeObjectFile(projectId, objectType, reference, collectionId) {
@@ -3549,6 +4065,665 @@ var ProjectService = class extends AbstractCrudService {
3549
4065
  }
3550
4066
  };
3551
4067
 
4068
+ //#endregion
4069
+ //#region src/service/ReleaseService.ts
4070
+ /**
4071
+ * Service that manages Release functionality
4072
+ *
4073
+ * A release diffs the current `work` branch against the `production` branch
4074
+ * to determine what changed, computes a semver bump, and merges work into production.
4075
+ */
4076
+ var ReleaseService = class extends AbstractCrudService {
4077
+ gitService;
4078
+ jsonFileService;
4079
+ projectService;
4080
+ constructor(options, logService, gitService, jsonFileService, projectService) {
4081
+ super(serviceTypeSchema.enum.Release, options, logService);
4082
+ this.gitService = gitService;
4083
+ this.jsonFileService = jsonFileService;
4084
+ this.projectService = projectService;
4085
+ }
4086
+ /**
4087
+ * Prepares a release by diffing the current `work` branch against `production`.
4088
+ *
4089
+ * Returns a read-only summary of all changes and the computed next version.
4090
+ * If there are no changes, the next version and bump will be null.
4091
+ */
4092
+ async prepare(props) {
4093
+ prepareReleaseSchema.parse(props);
4094
+ const projectPath = pathTo.project(props.projectId);
4095
+ const currentBranch = await this.gitService.branches.current(projectPath);
4096
+ if (currentBranch !== projectBranchSchema.enum.work) throw new Error(`Not on work branch (currently on "${currentBranch}")`);
4097
+ const project = await this.projectService.read({ id: props.projectId });
4098
+ const currentVersion = project.version;
4099
+ const productionRef = projectBranchSchema.enum.production;
4100
+ const productionProject = await this.getProjectAtRef(props.projectId, projectPath, productionRef);
4101
+ const projectDiff = this.diffProject(project, productionProject);
4102
+ const currentCollections = await this.getCollectionsAtRef(props.projectId, projectPath, projectBranchSchema.enum.work);
4103
+ const productionCollections = await this.getCollectionsAtRef(props.projectId, projectPath, productionRef);
4104
+ const collectionDiff = this.diffCollections(currentCollections, productionCollections);
4105
+ const currentAssets = await this.getAssetsAtRef(props.projectId, projectPath, projectBranchSchema.enum.work);
4106
+ const productionAssets = await this.getAssetsAtRef(props.projectId, projectPath, productionRef);
4107
+ const assetDiff = this.diffAssets(currentAssets, productionAssets);
4108
+ const allCollectionIds = new Set([...currentCollections.map((c) => c.id), ...productionCollections.map((c) => c.id)]);
4109
+ const entryDiff = await this.diffEntries(props.projectId, projectPath, allCollectionIds, productionRef);
4110
+ let finalBump = null;
4111
+ for (const bump of [
4112
+ projectDiff.bump,
4113
+ collectionDiff.bump,
4114
+ assetDiff.bump,
4115
+ entryDiff.bump
4116
+ ]) if (bump) finalBump = finalBump ? this.higherBump(finalBump, bump) : bump;
4117
+ if (!finalBump) {
4118
+ if (await this.hasCommitsBetween(projectPath, productionRef, projectBranchSchema.enum.work)) finalBump = "patch";
4119
+ }
4120
+ const nextVersion = finalBump ? Semver.inc(currentVersion, finalBump) : null;
4121
+ return {
4122
+ project,
4123
+ bump: finalBump,
4124
+ currentVersion,
4125
+ nextVersion,
4126
+ projectChanges: projectDiff.projectChanges,
4127
+ collectionChanges: collectionDiff.collectionChanges,
4128
+ fieldChanges: collectionDiff.fieldChanges,
4129
+ assetChanges: assetDiff.assetChanges,
4130
+ entryChanges: entryDiff.entryChanges
4131
+ };
4132
+ }
4133
+ /**
4134
+ * Creates a release by:
4135
+ * 1. Recomputing the diff (stateless)
4136
+ * 2. Merging `work` into `production`
4137
+ * 3. Updating the project version on `production`
4138
+ * 4. Tagging on `production`
4139
+ * 5. Merging `production` back into `work` (fast-forward to sync the version commit)
4140
+ * 6. Switching back to `work`
4141
+ */
4142
+ async create(props) {
4143
+ createReleaseSchema.parse(props);
4144
+ const projectPath = pathTo.project(props.projectId);
4145
+ const projectFilePath = pathTo.projectFile(props.projectId);
4146
+ const diff = await this.prepare(props);
4147
+ if (!diff.bump || !diff.nextVersion) throw new Error("Cannot create a release: no changes detected since the last full release");
4148
+ const nextVersion = diff.nextVersion;
4149
+ try {
4150
+ await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.production);
4151
+ await this.gitService.merge(projectPath, projectBranchSchema.enum.work);
4152
+ const updatedProjectFile = {
4153
+ ...diff.project,
4154
+ version: nextVersion,
4155
+ updated: datetime()
4156
+ };
4157
+ await this.jsonFileService.update(updatedProjectFile, projectFilePath, projectFileSchema);
4158
+ await this.gitService.add(projectPath, [projectFilePath]);
4159
+ await this.gitService.commit(projectPath, {
4160
+ method: "release",
4161
+ reference: {
4162
+ objectType: "project",
4163
+ id: props.projectId
4164
+ }
4165
+ });
4166
+ await this.gitService.tags.create({
4167
+ path: projectPath,
4168
+ message: {
4169
+ type: "release",
4170
+ version: nextVersion
4171
+ }
4172
+ });
4173
+ await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.work);
4174
+ await this.gitService.merge(projectPath, projectBranchSchema.enum.production);
4175
+ } catch (error) {
4176
+ await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.work).catch(() => {});
4177
+ throw error;
4178
+ }
4179
+ this.logService.info({
4180
+ source: "core",
4181
+ message: `Released version ${nextVersion} (${diff.bump} bump)`
4182
+ });
4183
+ return {
4184
+ version: nextVersion,
4185
+ diff
4186
+ };
4187
+ }
4188
+ /**
4189
+ * Creates a preview release by:
4190
+ * 1. Recomputing the diff (stateless)
4191
+ * 2. Computing the preview version (e.g. 1.1.0-preview.3)
4192
+ * 3. Updating the project version on `work`
4193
+ * 4. Tagging on `work` (no merge into production)
4194
+ *
4195
+ * Preview releases are snapshots of the current work state.
4196
+ * They don't promote to production — only full releases do.
4197
+ */
4198
+ async createPreview(props) {
4199
+ createPreviewReleaseSchema.parse(props);
4200
+ const projectPath = pathTo.project(props.projectId);
4201
+ const projectFilePath = pathTo.projectFile(props.projectId);
4202
+ const diff = await this.prepare(props);
4203
+ if (!diff.bump || !diff.nextVersion) throw new Error("Cannot create a preview release: no changes detected since the last full release");
4204
+ const previewNumber = await this.countPreviewsSinceLastRelease(projectPath, diff.nextVersion);
4205
+ const previewVersion = `${diff.nextVersion}-preview.${previewNumber + 1}`;
4206
+ try {
4207
+ const updatedProjectFile = {
4208
+ ...diff.project,
4209
+ version: previewVersion,
4210
+ updated: datetime()
4211
+ };
4212
+ await this.jsonFileService.update(updatedProjectFile, projectFilePath, projectFileSchema);
4213
+ await this.gitService.add(projectPath, [projectFilePath]);
4214
+ await this.gitService.commit(projectPath, {
4215
+ method: "release",
4216
+ reference: {
4217
+ objectType: "project",
4218
+ id: props.projectId
4219
+ }
4220
+ });
4221
+ await this.gitService.tags.create({
4222
+ path: projectPath,
4223
+ message: {
4224
+ type: "preview",
4225
+ version: previewVersion
4226
+ }
4227
+ });
4228
+ } catch (error) {
4229
+ await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.work).catch(() => {});
4230
+ throw error;
4231
+ }
4232
+ this.logService.info({
4233
+ source: "core",
4234
+ message: `Preview released version ${previewVersion} (${diff.bump} bump)`
4235
+ });
4236
+ return {
4237
+ version: previewVersion,
4238
+ diff
4239
+ };
4240
+ }
4241
+ /**
4242
+ * Reads the project file as it exists at a given git ref
4243
+ */
4244
+ async getProjectAtRef(projectId, projectPath, ref) {
4245
+ try {
4246
+ const content = await this.gitService.getFileContentAtCommit(projectPath, pathTo.projectFile(projectId), ref);
4247
+ return projectFileSchema.parse(JSON.parse(content));
4248
+ } catch {
4249
+ return null;
4250
+ }
4251
+ }
4252
+ /**
4253
+ * Reads asset metadata files as they exist at a given git ref
4254
+ */
4255
+ async getAssetsAtRef(projectId, projectPath, ref) {
4256
+ const assetsPath = pathTo.assets(projectId);
4257
+ const fileNames = await this.gitService.listTreeAtCommit(projectPath, assetsPath, ref);
4258
+ const assets = [];
4259
+ for (const fileName of fileNames) {
4260
+ if (!fileName.endsWith(".json")) continue;
4261
+ const assetId = fileName.replace(".json", "");
4262
+ const assetFilePath = pathTo.assetFile(projectId, assetId);
4263
+ try {
4264
+ const content = await this.gitService.getFileContentAtCommit(projectPath, assetFilePath, ref);
4265
+ const assetFile = assetFileSchema.parse(JSON.parse(content));
4266
+ assets.push(assetFile);
4267
+ } catch {
4268
+ this.logService.debug({
4269
+ source: "core",
4270
+ message: `Skipping asset "${fileName}" at ref "${ref}" during release diff`
4271
+ });
4272
+ }
4273
+ }
4274
+ return assets;
4275
+ }
4276
+ /**
4277
+ * Reads entry files for a single collection as they exist at a given git ref
4278
+ */
4279
+ async getEntriesAtRef(projectId, projectPath, collectionId, ref) {
4280
+ const entriesPath = pathTo.entries(projectId, collectionId);
4281
+ const fileNames = await this.gitService.listTreeAtCommit(projectPath, entriesPath, ref);
4282
+ const entries = [];
4283
+ for (const fileName of fileNames) {
4284
+ if (!fileName.endsWith(".json") || fileName === "collection.json") continue;
4285
+ const entryId = fileName.replace(".json", "");
4286
+ const entryFilePath = pathTo.entryFile(projectId, collectionId, entryId);
4287
+ try {
4288
+ const content = await this.gitService.getFileContentAtCommit(projectPath, entryFilePath, ref);
4289
+ const entryFile = entryFileSchema.parse(JSON.parse(content));
4290
+ entries.push(entryFile);
4291
+ } catch {
4292
+ this.logService.debug({
4293
+ source: "core",
4294
+ message: `Skipping entry "${fileName}" in collection "${collectionId}" at ref "${ref}" during release diff`
4295
+ });
4296
+ }
4297
+ }
4298
+ return entries;
4299
+ }
4300
+ /**
4301
+ * Reads collections as they exist at a given git ref (branch or commit)
4302
+ */
4303
+ async getCollectionsAtRef(projectId, projectPath, ref) {
4304
+ const collectionsPath = pathTo.collections(projectId);
4305
+ const folderNames = await this.gitService.listTreeAtCommit(projectPath, collectionsPath, ref);
4306
+ const collections = [];
4307
+ for (const folderName of folderNames) {
4308
+ const collectionFilePath = pathTo.collectionFile(projectId, folderName);
4309
+ try {
4310
+ const content = await this.gitService.getFileContentAtCommit(projectPath, collectionFilePath, ref);
4311
+ const collectionFile = collectionFileSchema.parse(JSON.parse(content));
4312
+ collections.push(collectionFile);
4313
+ } catch {
4314
+ this.logService.debug({
4315
+ source: "core",
4316
+ message: `Skipping folder "${folderName}" at ref "${ref}" during release diff`
4317
+ });
4318
+ }
4319
+ }
4320
+ return collections;
4321
+ }
4322
+ /**
4323
+ * Checks if there are any commits between two refs
4324
+ */
4325
+ async hasCommitsBetween(projectPath, from, to) {
4326
+ try {
4327
+ return (await this.gitService.log(projectPath, { between: {
4328
+ from,
4329
+ to
4330
+ } })).length > 0;
4331
+ } catch {
4332
+ return true;
4333
+ }
4334
+ }
4335
+ /**
4336
+ * Diffs two sets of collections and returns all changes with the computed bump level.
4337
+ *
4338
+ * Always collects all changes so they can be displayed to the user.
4339
+ */
4340
+ diffCollections(currentCollections, productionCollections) {
4341
+ const collectionChanges = [];
4342
+ const fieldChanges = [];
4343
+ let highestBump = null;
4344
+ const currentById = new Map(currentCollections.map((c) => [c.id, c]));
4345
+ const productionById = new Map(productionCollections.map((c) => [c.id, c]));
4346
+ for (const [id] of productionById) if (!currentById.has(id)) {
4347
+ collectionChanges.push({
4348
+ collectionId: id,
4349
+ changeType: "deleted",
4350
+ bump: "major"
4351
+ });
4352
+ highestBump = "major";
4353
+ }
4354
+ for (const [id] of currentById) if (!productionById.has(id)) {
4355
+ collectionChanges.push({
4356
+ collectionId: id,
4357
+ changeType: "added",
4358
+ bump: "minor"
4359
+ });
4360
+ highestBump = this.higherBump(highestBump, "minor");
4361
+ }
4362
+ for (const [id, currentCollection] of currentById) {
4363
+ const productionCollection = productionById.get(id);
4364
+ if (!productionCollection) continue;
4365
+ const changes = this.diffFieldDefinitions(id, currentCollection.fieldDefinitions, productionCollection.fieldDefinitions);
4366
+ fieldChanges.push(...changes);
4367
+ for (const change of changes) highestBump = this.higherBump(highestBump, change.bump);
4368
+ }
4369
+ return {
4370
+ bump: highestBump,
4371
+ collectionChanges,
4372
+ fieldChanges
4373
+ };
4374
+ }
4375
+ /**
4376
+ * Diffs the project file between current and production.
4377
+ *
4378
+ * Skips immutable/system-managed fields (id, objectType, created, updated, version, coreVersion).
4379
+ */
4380
+ diffProject(current, production) {
4381
+ const projectChanges = [];
4382
+ if (!production) return {
4383
+ bump: null,
4384
+ projectChanges
4385
+ };
4386
+ let highestBump = null;
4387
+ if (current.settings.language.default !== production.settings.language.default) {
4388
+ projectChanges.push({
4389
+ changeType: "defaultLanguageChanged",
4390
+ bump: "major"
4391
+ });
4392
+ highestBump = "major";
4393
+ }
4394
+ const currentSupported = new Set(current.settings.language.supported);
4395
+ const productionSupported = new Set(production.settings.language.supported);
4396
+ for (const lang of productionSupported) if (!currentSupported.has(lang)) {
4397
+ projectChanges.push({
4398
+ changeType: "supportedLanguageRemoved",
4399
+ bump: "major"
4400
+ });
4401
+ highestBump = "major";
4402
+ break;
4403
+ }
4404
+ for (const lang of currentSupported) if (!productionSupported.has(lang)) {
4405
+ projectChanges.push({
4406
+ changeType: "supportedLanguageAdded",
4407
+ bump: "minor"
4408
+ });
4409
+ highestBump = this.higherBump(highestBump, "minor");
4410
+ break;
4411
+ }
4412
+ if (current.name !== production.name) {
4413
+ projectChanges.push({
4414
+ changeType: "nameChanged",
4415
+ bump: "patch"
4416
+ });
4417
+ highestBump = this.higherBump(highestBump, "patch");
4418
+ }
4419
+ if (current.description !== production.description) {
4420
+ projectChanges.push({
4421
+ changeType: "descriptionChanged",
4422
+ bump: "patch"
4423
+ });
4424
+ highestBump = this.higherBump(highestBump, "patch");
4425
+ }
4426
+ return {
4427
+ bump: highestBump,
4428
+ projectChanges
4429
+ };
4430
+ }
4431
+ /**
4432
+ * Diffs two sets of assets and returns all changes with the computed bump level.
4433
+ */
4434
+ diffAssets(currentAssets, productionAssets) {
4435
+ const assetChanges = [];
4436
+ let highestBump = null;
4437
+ const currentById = new Map(currentAssets.map((a) => [a.id, a]));
4438
+ const productionById = new Map(productionAssets.map((a) => [a.id, a]));
4439
+ for (const [id] of productionById) if (!currentById.has(id)) {
4440
+ assetChanges.push({
4441
+ assetId: id,
4442
+ changeType: "deleted",
4443
+ bump: "major"
4444
+ });
4445
+ highestBump = "major";
4446
+ }
4447
+ for (const [id] of currentById) if (!productionById.has(id)) {
4448
+ assetChanges.push({
4449
+ assetId: id,
4450
+ changeType: "added",
4451
+ bump: "minor"
4452
+ });
4453
+ highestBump = this.higherBump(highestBump, "minor");
4454
+ }
4455
+ for (const [id, current] of currentById) {
4456
+ const production = productionById.get(id);
4457
+ if (!production) continue;
4458
+ if (current.extension !== production.extension || current.mimeType !== production.mimeType || current.size !== production.size) {
4459
+ assetChanges.push({
4460
+ assetId: id,
4461
+ changeType: "binaryChanged",
4462
+ bump: "patch"
4463
+ });
4464
+ highestBump = this.higherBump(highestBump, "patch");
4465
+ }
4466
+ if (current.name !== production.name || current.description !== production.description) {
4467
+ assetChanges.push({
4468
+ assetId: id,
4469
+ changeType: "metadataChanged",
4470
+ bump: "patch"
4471
+ });
4472
+ highestBump = this.higherBump(highestBump, "patch");
4473
+ }
4474
+ }
4475
+ return {
4476
+ bump: highestBump,
4477
+ assetChanges
4478
+ };
4479
+ }
4480
+ /**
4481
+ * Diffs entries across all collections between current and production.
4482
+ */
4483
+ async diffEntries(projectId, projectPath, allCollectionIds, productionRef) {
4484
+ const entryChanges = [];
4485
+ let highestBump = null;
4486
+ for (const collectionId of allCollectionIds) {
4487
+ const currentEntries = await this.getEntriesAtRef(projectId, projectPath, collectionId, projectBranchSchema.enum.work);
4488
+ const productionEntries = await this.getEntriesAtRef(projectId, projectPath, collectionId, productionRef);
4489
+ const currentById = new Map(currentEntries.map((e) => [e.id, e]));
4490
+ const productionById = new Map(productionEntries.map((e) => [e.id, e]));
4491
+ for (const [id] of productionById) if (!currentById.has(id)) {
4492
+ entryChanges.push({
4493
+ collectionId,
4494
+ entryId: id,
4495
+ changeType: "deleted",
4496
+ bump: "major"
4497
+ });
4498
+ highestBump = "major";
4499
+ }
4500
+ for (const [id] of currentById) if (!productionById.has(id)) {
4501
+ entryChanges.push({
4502
+ collectionId,
4503
+ entryId: id,
4504
+ changeType: "added",
4505
+ bump: "minor"
4506
+ });
4507
+ highestBump = this.higherBump(highestBump, "minor");
4508
+ }
4509
+ for (const [id, current] of currentById) {
4510
+ const production = productionById.get(id);
4511
+ if (!production) continue;
4512
+ if (!isDeepStrictEqual(current.values, production.values)) {
4513
+ entryChanges.push({
4514
+ collectionId,
4515
+ entryId: id,
4516
+ changeType: "modified",
4517
+ bump: "patch"
4518
+ });
4519
+ highestBump = this.higherBump(highestBump, "patch");
4520
+ }
4521
+ }
4522
+ }
4523
+ return {
4524
+ bump: highestBump,
4525
+ entryChanges
4526
+ };
4527
+ }
4528
+ /**
4529
+ * Diffs field definitions of a single collection.
4530
+ *
4531
+ * Matches fields by UUID and classifies each change.
4532
+ * Always collects all changes so they can be displayed to the user.
4533
+ */
4534
+ diffFieldDefinitions(collectionId, currentFields, productionFields) {
4535
+ const changes = [];
4536
+ const currentById = new Map(currentFields.map((f) => [f.id, f]));
4537
+ const productionById = new Map(productionFields.map((f) => [f.id, f]));
4538
+ for (const [id, field] of productionById) if (!currentById.has(id)) changes.push({
4539
+ collectionId,
4540
+ fieldId: id,
4541
+ fieldSlug: field.slug,
4542
+ changeType: "deleted",
4543
+ bump: "major"
4544
+ });
4545
+ for (const [id, field] of currentById) if (!productionById.has(id)) changes.push({
4546
+ collectionId,
4547
+ fieldId: id,
4548
+ fieldSlug: field.slug,
4549
+ changeType: "added",
4550
+ bump: "minor"
4551
+ });
4552
+ for (const [id, currentField] of currentById) {
4553
+ const productionField = productionById.get(id);
4554
+ if (!productionField) continue;
4555
+ const fieldChanges = this.diffSingleField(collectionId, currentField, productionField);
4556
+ changes.push(...fieldChanges);
4557
+ }
4558
+ return changes;
4559
+ }
4560
+ /**
4561
+ * Compares two versions of the same field definition and returns all detected changes.
4562
+ *
4563
+ * Collects every change on the field so the full diff can be shown to the user.
4564
+ */
4565
+ diffSingleField(collectionId, current, production) {
4566
+ const changes = [];
4567
+ const base = {
4568
+ collectionId,
4569
+ fieldId: current.id,
4570
+ fieldSlug: current.slug
4571
+ };
4572
+ if (current.valueType !== production.valueType) changes.push({
4573
+ ...base,
4574
+ changeType: "valueTypeChanged",
4575
+ bump: "major"
4576
+ });
4577
+ if (current.fieldType !== production.fieldType) changes.push({
4578
+ ...base,
4579
+ changeType: "fieldTypeChanged",
4580
+ bump: "major"
4581
+ });
4582
+ if (current.slug !== production.slug) changes.push({
4583
+ ...base,
4584
+ changeType: "slugChanged",
4585
+ bump: "major"
4586
+ });
4587
+ if (this.isMinMaxTightened(current, production)) changes.push({
4588
+ ...base,
4589
+ changeType: "minMaxTightened",
4590
+ bump: "major"
4591
+ });
4592
+ if (production.isRequired === true && current.isRequired === false) changes.push({
4593
+ ...base,
4594
+ changeType: "isRequiredToNotRequired",
4595
+ bump: "major"
4596
+ });
4597
+ if (production.isUnique === true && current.isUnique === false) changes.push({
4598
+ ...base,
4599
+ changeType: "isUniqueToNotUnique",
4600
+ bump: "major"
4601
+ });
4602
+ if (current.fieldType === "entry" && production.fieldType === "entry") {
4603
+ if (!isDeepStrictEqual([...current.ofCollections].sort(), [...production.ofCollections].sort())) changes.push({
4604
+ ...base,
4605
+ changeType: "ofCollectionsChanged",
4606
+ bump: "major"
4607
+ });
4608
+ }
4609
+ if (production.isRequired === false && current.isRequired === true) changes.push({
4610
+ ...base,
4611
+ changeType: "isNotRequiredToRequired",
4612
+ bump: "minor"
4613
+ });
4614
+ if (production.isUnique === false && current.isUnique === true) changes.push({
4615
+ ...base,
4616
+ changeType: "isNotUniqueToUnique",
4617
+ bump: "minor"
4618
+ });
4619
+ if (this.isMinMaxLoosened(current, production)) changes.push({
4620
+ ...base,
4621
+ changeType: "minMaxLoosened",
4622
+ bump: "patch"
4623
+ });
4624
+ if (!isDeepStrictEqual(current.label, production.label)) changes.push({
4625
+ ...base,
4626
+ changeType: "labelChanged",
4627
+ bump: "patch"
4628
+ });
4629
+ if (!isDeepStrictEqual(current.description, production.description)) changes.push({
4630
+ ...base,
4631
+ changeType: "descriptionChanged",
4632
+ bump: "patch"
4633
+ });
4634
+ if ("defaultValue" in current && "defaultValue" in production && !isDeepStrictEqual(current.defaultValue, production.defaultValue)) changes.push({
4635
+ ...base,
4636
+ changeType: "defaultValueChanged",
4637
+ bump: "patch"
4638
+ });
4639
+ if (current.inputWidth !== production.inputWidth) changes.push({
4640
+ ...base,
4641
+ changeType: "inputWidthChanged",
4642
+ bump: "patch"
4643
+ });
4644
+ if (current.isDisabled !== production.isDisabled) changes.push({
4645
+ ...base,
4646
+ changeType: "isDisabledChanged",
4647
+ bump: "patch"
4648
+ });
4649
+ return changes;
4650
+ }
4651
+ /**
4652
+ * Checks if min/max constraints have been tightened.
4653
+ *
4654
+ * Tightening means: new min > old min, or new max < old max.
4655
+ * A null value means no constraint (unbounded).
4656
+ */
4657
+ isMinMaxTightened(current, production) {
4658
+ const currentMin = this.getMinMax(current, "min");
4659
+ const productionMin = this.getMinMax(production, "min");
4660
+ const currentMax = this.getMinMax(current, "max");
4661
+ const productionMax = this.getMinMax(production, "max");
4662
+ if (currentMin !== null && productionMin === null) return true;
4663
+ if (currentMin !== null && productionMin !== null && currentMin > productionMin) return true;
4664
+ if (currentMax !== null && productionMax === null) return true;
4665
+ if (currentMax !== null && productionMax !== null && currentMax < productionMax) return true;
4666
+ return false;
4667
+ }
4668
+ /**
4669
+ * Checks if min/max constraints have been loosened.
4670
+ *
4671
+ * Loosening means: new min < old min, or new max > old max.
4672
+ */
4673
+ isMinMaxLoosened(current, production) {
4674
+ const currentMin = this.getMinMax(current, "min");
4675
+ const productionMin = this.getMinMax(production, "min");
4676
+ const currentMax = this.getMinMax(current, "max");
4677
+ const productionMax = this.getMinMax(production, "max");
4678
+ if (currentMin === null && productionMin !== null) return true;
4679
+ if (currentMin !== null && productionMin !== null && currentMin < productionMin) return true;
4680
+ if (currentMax === null && productionMax !== null) return true;
4681
+ if (currentMax !== null && productionMax !== null && currentMax > productionMax) return true;
4682
+ return false;
4683
+ }
4684
+ /**
4685
+ * Safely extracts min or max from a field definition (not all types have it)
4686
+ */
4687
+ getMinMax(field, prop) {
4688
+ switch (field.fieldType) {
4689
+ case "text":
4690
+ case "textarea":
4691
+ case "number":
4692
+ case "range":
4693
+ case "asset":
4694
+ case "entry": return field[prop];
4695
+ default: return null;
4696
+ }
4697
+ }
4698
+ /**
4699
+ * Counts existing preview tags for a given base version since the last full release.
4700
+ */
4701
+ async countPreviewsSinceLastRelease(projectPath, baseVersion) {
4702
+ const tags = await this.gitService.tags.list({ path: projectPath });
4703
+ let count = 0;
4704
+ for (const tag of tags.list) {
4705
+ if (tag.message.type === "upgrade") continue;
4706
+ if (tag.message.type === "release") break;
4707
+ if (tag.message.type === "preview") {
4708
+ if (tag.message.version.split("-")[0] === baseVersion) count++;
4709
+ }
4710
+ }
4711
+ return count;
4712
+ }
4713
+ /**
4714
+ * Returns the higher of two bumps (major > minor > patch)
4715
+ */
4716
+ higherBump(a, b) {
4717
+ const order = {
4718
+ patch: 0,
4719
+ minor: 1,
4720
+ major: 2
4721
+ };
4722
+ if (a === null) return b;
4723
+ return order[a] >= order[b] ? a : b;
4724
+ }
4725
+ };
4726
+
3552
4727
  //#endregion
3553
4728
  //#region src/service/UserService.ts
3554
4729
  /**
@@ -3584,7 +4759,7 @@ var UserService = class {
3584
4759
  setUserSchema.parse(props);
3585
4760
  const userFilePath = pathTo.userFile;
3586
4761
  const userFile = { ...props };
3587
- if (userFile.userType === UserTypeSchema.enum.cloud) {}
4762
+ if (userFile.userType === userTypeSchema.enum.cloud) {}
3588
4763
  await this.jsonFileService.update(userFile, userFilePath, userFileSchema);
3589
4764
  this.logService.debug({
3590
4765
  source: "core",
@@ -3612,6 +4787,7 @@ var ElekIoCore = class {
3612
4787
  projectService;
3613
4788
  collectionService;
3614
4789
  entryService;
4790
+ releaseService;
3615
4791
  localApi;
3616
4792
  constructor(props) {
3617
4793
  this.coreVersion = package_default.version;
@@ -3624,10 +4800,11 @@ var ElekIoCore = class {
3624
4800
  this.jsonFileService = new JsonFileService(this.options, this.logService);
3625
4801
  this.userService = new UserService(this.logService, this.jsonFileService);
3626
4802
  this.gitService = new GitService(this.options, this.logService, this.userService);
3627
- this.assetService = new AssetService(this.options, this.logService, this.jsonFileService, this.gitService);
3628
- this.collectionService = new CollectionService(this.options, this.logService, this.jsonFileService, this.gitService);
3629
- this.entryService = new EntryService(this.options, this.logService, this.jsonFileService, this.gitService, this.collectionService);
4803
+ this.assetService = new AssetService(this.coreVersion, this.options, this.logService, this.jsonFileService, this.gitService);
4804
+ this.collectionService = new CollectionService(this.coreVersion, this.options, this.logService, this.jsonFileService, this.gitService);
4805
+ this.entryService = new EntryService(this.coreVersion, this.options, this.logService, this.jsonFileService, this.gitService, this.collectionService);
3630
4806
  this.projectService = new ProjectService(this.coreVersion, this.options, this.logService, this.jsonFileService, this.gitService, this.assetService, this.collectionService, this.entryService);
4807
+ this.releaseService = new ReleaseService(this.options, this.logService, this.gitService, this.jsonFileService, this.projectService);
3631
4808
  this.localApi = new LocalApi(this.logService, this.projectService, this.collectionService, this.entryService, this.assetService);
3632
4809
  this.logService.info({
3633
4810
  source: "core",
@@ -3687,6 +4864,12 @@ var ElekIoCore = class {
3687
4864
  return this.entryService;
3688
4865
  }
3689
4866
  /**
4867
+ * Prepare and create releases
4868
+ */
4869
+ get releases() {
4870
+ return this.releaseService;
4871
+ }
4872
+ /**
3690
4873
  * Allows starting and stopping a REST API
3691
4874
  * to allow developers to read local Project data
3692
4875
  */
@@ -3696,5 +4879,5 @@ var ElekIoCore = class {
3696
4879
  };
3697
4880
 
3698
4881
  //#endregion
3699
- export { BooleanFieldDefinitionBaseSchema, FieldDefinitionBaseSchema, FieldTypeSchema, FieldWidthSchema, NumberFieldDefinitionBaseSchema, ReferenceFieldDefinitionBaseSchema, StringFieldDefinitionBaseSchema, UserTypeSchema, ValueTypeSchema, apiStartSchema, assetExportSchema, assetFieldDefinitionSchema, assetFileSchema, assetSchema, baseFileSchema, baseUserSchema, cloneProjectSchema, cloudUserSchema, collectionExportSchema, collectionFileSchema, collectionSchema, constructorElekIoCoreSchema, countAssetsSchema, countCollectionsSchema, countEntriesSchema, countGitTagsSchema, createAssetSchema, createCollectionSchema, createEntrySchema, createGitTagSchema, createProjectSchema, currentBranchProjectSchema, dateFieldDefinitionSchema, datetime, datetimeFieldDefinitionSchema, ElekIoCore as default, deleteAssetSchema, deleteCollectionSchema, deleteEntrySchema, deleteGitTagSchema, deleteProjectSchema, directBooleanValueSchema, directNumberValueSchema, directStringValueSchema, directValueBaseSchema, directValueSchema, elekIoCoreOptionsSchema, emailFieldDefinitionSchema, entryExportSchema, entryFieldDefinitionSchema, entryFileSchema, entrySchema, exportSchema, fieldDefinitionSchema, fileReferenceSchema, generateApiClientSchema, getChangesProjectSchema, getCreateEntrySchemaFromFieldDefinitions, getEntrySchemaFromFieldDefinitions, getRemoteOriginUrlProjectSchema, getTranslatableBooleanValueContentSchemaFromFieldDefinition, getTranslatableNumberValueContentSchemaFromFieldDefinition, getTranslatableReferenceValueContentSchemaFromFieldDefinition, getTranslatableStringValueContentSchemaFromFieldDefinition, getUpdateEntrySchemaFromFieldDefinitions, getValueSchemaFromFieldDefinition, gitCloneOptionsSchema, gitCommitSchema, gitInitOptionsSchema, gitLogOptionsSchema, gitMergeOptionsSchema, gitMessageSchema, gitSignatureSchema, gitSwitchOptionsSchema, gitTagSchema, ipv4FieldDefinitionSchema, listAssetsSchema, listBranchesProjectSchema, listCollectionsSchema, listEntriesSchema, listGitTagsSchema, listProjectsSchema, localUserSchema, logConsoleTransportSchema, logLevelSchema, logSchema, logSourceSchema, migrateProjectSchema, numberFieldDefinitionSchema, objectTypeSchema, paginatedListOf, projectBranchSchema, projectExportSchema, projectFileSchema, projectFolderSchema, projectSchema, projectSettingsSchema, projectStatusSchema, projectUpgradeSchema, rangeFieldDefinitionSchema, readAssetSchema, readCollectionSchema, readEntrySchema, readGitTagSchema, readProjectSchema, referencedValueSchema, saveAssetSchema, searchProjectSchema, serviceTypeSchema, setRemoteOriginUrlProjectSchema, setUserSchema, slug, stringFieldDefinitionSchema, supportedIconSchema, supportedLanguageSchema, switchBranchProjectSchema, synchronizeProjectSchema, telephoneFieldDefinitionSchema, textFieldDefinitionSchema, textareaFieldDefinitionSchema, timeFieldDefinitionSchema, toggleFieldDefinitionSchema, translatableArrayOf, translatableBooleanSchema, translatableNumberSchema, translatableStringSchema, updateAssetSchema, updateCollectionSchema, updateEntrySchema, updateProjectSchema, upgradeProjectSchema, urlFieldDefinitionSchema, userFileSchema, userSchema, uuid, uuidSchema, valueContentReferenceBase, valueContentReferenceSchema, valueContentReferenceToAssetSchema, valueContentReferenceToCollectionSchema, valueContentReferenceToEntrySchema, valueSchema, versionSchema };
4882
+ export { apiStartSchema, assetChangeSchema, assetChangeTypeSchema, assetExportSchema, assetFieldDefinitionSchema, assetFileSchema, assetHistorySchema, assetSchema, baseFileSchema, baseUserSchema, booleanFieldDefinitionBaseSchema, cloneProjectSchema, cloudUserSchema, collectionChangeSchema, collectionChangeTypeSchema, collectionExportSchema, collectionFileSchema, collectionHistorySchema, collectionIndexSchema, collectionSchema, constructorElekIoCoreSchema, countAssetsSchema, countCollectionsSchema, countEntriesSchema, countGitTagsSchema, createAssetSchema, createCollectionSchema, createEntrySchema, createGitTagSchema, createPreviewReleaseSchema, createProjectSchema, createReleaseSchema, currentBranchProjectSchema, dateFieldDefinitionSchema, datetime, datetimeFieldDefinitionSchema, ElekIoCore as default, deleteAssetSchema, deleteCollectionSchema, deleteEntrySchema, deleteGitTagSchema, deleteProjectSchema, directBooleanValueSchema, directNumberValueSchema, directStringValueSchema, directValueBaseSchema, directValueSchema, elekIoCoreOptionsSchema, emailFieldDefinitionSchema, entryChangeSchema, entryChangeTypeSchema, entryExportSchema, entryFieldDefinitionSchema, entryFileSchema, entryHistorySchema, entrySchema, exportSchema, fieldChangeSchema, fieldChangeTypeSchema, fieldDefinitionBaseSchema, fieldDefinitionSchema, fieldTypeSchema, fieldWidthSchema, fileReferenceSchema, generateApiClientSchema, getChangesProjectSchema, getCreateEntrySchemaFromFieldDefinitions, getEntrySchemaFromFieldDefinitions, getRemoteOriginUrlProjectSchema, getTranslatableBooleanValueContentSchemaFromFieldDefinition, getTranslatableNumberValueContentSchemaFromFieldDefinition, getTranslatableReferenceValueContentSchemaFromFieldDefinition, getTranslatableStringValueContentSchemaFromFieldDefinition, getUpdateEntrySchemaFromFieldDefinitions, getValueSchemaFromFieldDefinition, gitCloneOptionsSchema, gitCommitSchema, gitInitOptionsSchema, gitLogOptionsSchema, gitMergeOptionsSchema, gitMessageSchema, gitSignatureSchema, gitSwitchOptionsSchema, gitTagMessageSchema, gitTagSchema, ipv4FieldDefinitionSchema, listAssetsSchema, listBranchesProjectSchema, listCollectionsSchema, listEntriesSchema, listGitTagsSchema, listProjectsSchema, localUserSchema, logConsoleTransportSchema, logLevelSchema, logSchema, logSourceSchema, migrateAssetSchema, migrateCollectionSchema, migrateEntrySchema, migrateProjectSchema, numberFieldDefinitionBaseSchema, numberFieldDefinitionSchema, objectTypeSchema, paginatedListOf, prepareReleaseSchema, projectBranchSchema, projectChangeSchema, projectChangeTypeSchema, projectExportSchema, projectFileSchema, projectFolderSchema, projectHistoryResultSchema, projectHistorySchema, projectSchema, projectSettingsSchema, rangeFieldDefinitionSchema, readAssetSchema, readBySlugCollectionSchema, readCollectionSchema, readEntrySchema, readGitTagSchema, readProjectSchema, referenceFieldDefinitionBaseSchema, referencedValueSchema, releaseDiffSchema, releaseResultSchema, reservedSlugs, resolveCollectionIdSchema, saveAssetSchema, searchProjectSchema, semverBumpSchema, serviceTypeSchema, setRemoteOriginUrlProjectSchema, setUserSchema, slug, slugSchema, stringFieldDefinitionBaseSchema, stringFieldDefinitionSchema, supportedIconSchema, supportedLanguageSchema, switchBranchProjectSchema, synchronizeProjectSchema, telephoneFieldDefinitionSchema, textFieldDefinitionSchema, textareaFieldDefinitionSchema, timeFieldDefinitionSchema, toggleFieldDefinitionSchema, translatableArrayOf, translatableBooleanSchema, translatableNumberSchema, translatableStringSchema, updateAssetSchema, updateCollectionSchema, updateEntrySchema, updateProjectSchema, upgradeProjectSchema, urlFieldDefinitionSchema, userFileSchema, userSchema, userTypeSchema, uuid, uuidSchema, valueContentReferenceBase, valueContentReferenceSchema, valueContentReferenceToAssetSchema, valueContentReferenceToCollectionSchema, valueContentReferenceToEntrySchema, valueSchema, valueTypeSchema, versionSchema };
3700
4883
  //# sourceMappingURL=index.node.mjs.map