@crazygiscool/cap 1.1.8 → 1.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,114 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { generateDynamicSchema, } from "../index.js";
3
+ const baseFields = {
4
+ name: "Optimus Prime",
5
+ description: "Leader of the Autobots",
6
+ continuityId: "g1",
7
+ factions: ["Autobots"],
8
+ };
9
+ describe("generateDynamicSchema", () => {
10
+ it("returns base schema when customFields is empty", () => {
11
+ const schema = generateDynamicSchema([]);
12
+ const result = schema.safeParse(baseFields);
13
+ expect(result.success).toBe(true);
14
+ });
15
+ it("adds a string field", () => {
16
+ const fields = [
17
+ { key: "sparkId", type: "string", label: "Spark Signature" },
18
+ ];
19
+ const schema = generateDynamicSchema(fields);
20
+ const result = schema.safeParse({
21
+ ...baseFields,
22
+ sparkId: "SP-2187",
23
+ });
24
+ expect(result.success).toBe(true);
25
+ });
26
+ it("rejects a number for a string field", () => {
27
+ const fields = [
28
+ { key: "sparkId", type: "string", label: "Spark Signature" },
29
+ ];
30
+ const schema = generateDynamicSchema(fields);
31
+ const result = schema.safeParse({
32
+ ...baseFields,
33
+ sparkId: 42,
34
+ });
35
+ expect(result.success).toBe(false);
36
+ });
37
+ it("adds a number field", () => {
38
+ const fields = [
39
+ { key: "firepower", type: "number", label: "Firepower Rating" },
40
+ ];
41
+ const schema = generateDynamicSchema(fields);
42
+ const ok = schema.safeParse({
43
+ ...baseFields,
44
+ firepower: 9000,
45
+ });
46
+ expect(ok.success).toBe(true);
47
+ const fail = schema.safeParse({
48
+ ...baseFields,
49
+ firepower: "over 9000",
50
+ });
51
+ expect(fail.success).toBe(false);
52
+ });
53
+ it("adds a boolean field", () => {
54
+ const fields = [
55
+ { key: "isOnline", type: "boolean", label: "Online Status" },
56
+ ];
57
+ const schema = generateDynamicSchema(fields);
58
+ const ok = schema.safeParse({
59
+ ...baseFields,
60
+ isOnline: true,
61
+ });
62
+ expect(ok.success).toBe(true);
63
+ const fail = schema.safeParse({
64
+ ...baseFields,
65
+ isOnline: "yes",
66
+ });
67
+ expect(fail.success).toBe(false);
68
+ });
69
+ it("adds a date field (ISO datetime string)", () => {
70
+ const fields = [
71
+ { key: "activated", type: "date", label: "Activation Date" },
72
+ ];
73
+ const schema = generateDynamicSchema(fields);
74
+ const ok = schema.safeParse({
75
+ ...baseFields,
76
+ activated: "2024-01-15T08:00:00.000Z",
77
+ });
78
+ expect(ok.success).toBe(true);
79
+ const fail = schema.safeParse({
80
+ ...baseFields,
81
+ activated: "not-a-date",
82
+ });
83
+ expect(fail.success).toBe(false);
84
+ });
85
+ it("handles multiple custom fields of different types", () => {
86
+ const fields = [
87
+ { key: "sparkId", type: "string", label: "Spark Signature" },
88
+ { key: "firepower", type: "number", label: "Firepower Rating" },
89
+ { key: "isOnline", type: "boolean", label: "Online Status" },
90
+ ];
91
+ const schema = generateDynamicSchema(fields);
92
+ const result = schema.safeParse({
93
+ ...baseFields,
94
+ sparkId: "SP-2187",
95
+ firepower: 9000,
96
+ isOnline: true,
97
+ });
98
+ expect(result.success).toBe(true);
99
+ });
100
+ it("rejects missing custom fields", () => {
101
+ const fields = [
102
+ { key: "sparkId", type: "string", label: "Spark Signature" },
103
+ ];
104
+ const schema = generateDynamicSchema(fields);
105
+ const result = schema.safeParse(baseFields);
106
+ expect(result.success).toBe(false);
107
+ });
108
+ it("still enforces base schema requirements", () => {
109
+ const schema = generateDynamicSchema([]);
110
+ const result = schema.safeParse({ name: "Megatron" });
111
+ expect(result.success).toBe(false);
112
+ });
113
+ });
114
+ //# sourceMappingURL=dynamic-schema.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamic-schema.test.js","sourceRoot":"","sources":["../../src/__tests__/dynamic-schema.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,160 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { CAPRepository } from "../index.js";
3
+ class TestRepository extends CAPRepository {
4
+ constructor(db) {
5
+ super(db, "test_entries");
6
+ }
7
+ }
8
+ function mockDb() {
9
+ const mockCollection = {
10
+ insertOne: vi.fn(),
11
+ findOne: vi.fn(),
12
+ find: vi.fn(() => ({
13
+ toArray: vi.fn(),
14
+ })),
15
+ findOneAndUpdate: vi.fn(),
16
+ deleteOne: vi.fn(),
17
+ };
18
+ const db = {
19
+ collection: vi.fn(() => mockCollection),
20
+ };
21
+ return { db, collection: mockCollection };
22
+ }
23
+ describe("CAPRepository", () => {
24
+ let repo;
25
+ let collection;
26
+ beforeEach(() => {
27
+ const { db, collection: c } = mockDb();
28
+ repo = new TestRepository(db);
29
+ collection = c;
30
+ });
31
+ describe("create", () => {
32
+ it("inserts a document with generated id and timestamps", async () => {
33
+ collection.insertOne.mockResolvedValue({ acknowledged: true });
34
+ const result = await repo.create({
35
+ name: "Optimus Prime",
36
+ description: "Leader of the Autobots",
37
+ continuityId: "g1",
38
+ factions: ["Autobots"],
39
+ sparkId: "SP-2187",
40
+ });
41
+ expect(result.id).toBeDefined();
42
+ expect(typeof result.id).toBe("string");
43
+ expect(result.createdAt).toBeInstanceOf(Date);
44
+ expect(result.updatedAt).toBeInstanceOf(Date);
45
+ expect(result.name).toBe("Optimus Prime");
46
+ expect(result.sparkId).toBe("SP-2187");
47
+ expect(collection.insertOne).toHaveBeenCalledOnce();
48
+ const inserted = collection.insertOne.mock.calls[0][0];
49
+ expect(inserted.id).toBe(result.id);
50
+ expect(inserted.createdAt).toBeInstanceOf(Date);
51
+ expect(inserted.updatedAt).toBeInstanceOf(Date);
52
+ });
53
+ });
54
+ describe("findById", () => {
55
+ it("returns a document by id", async () => {
56
+ const doc = {
57
+ _id: "abc",
58
+ id: "uuid-123",
59
+ name: "Bumblebee",
60
+ description: "Scout",
61
+ continuityId: "g1",
62
+ factions: ["Autobots"],
63
+ sparkId: "SP-0001",
64
+ createdAt: new Date(),
65
+ updatedAt: new Date(),
66
+ };
67
+ collection.findOne.mockResolvedValue(doc);
68
+ const result = await repo.findById("uuid-123");
69
+ expect(result).toEqual(doc);
70
+ expect(collection.findOne).toHaveBeenCalledWith({ id: "uuid-123" });
71
+ });
72
+ it("returns null when not found", async () => {
73
+ collection.findOne.mockResolvedValue(null);
74
+ const result = await repo.findById("nonexistent");
75
+ expect(result).toBeNull();
76
+ });
77
+ });
78
+ describe("findByName", () => {
79
+ it("returns a document by name (case-insensitive)", async () => {
80
+ const doc = {
81
+ _id: "abc",
82
+ id: "uuid-1",
83
+ name: "Megatron",
84
+ description: "Decepticon leader",
85
+ continuityId: "g1",
86
+ factions: ["Decepticons"],
87
+ createdAt: new Date(),
88
+ updatedAt: new Date(),
89
+ };
90
+ collection.findOne.mockResolvedValue(doc);
91
+ const result = await repo.findByName("megatron");
92
+ expect(result).toEqual(doc);
93
+ });
94
+ it("returns null when not found", async () => {
95
+ collection.findOne.mockResolvedValue(null);
96
+ const result = await repo.findByName("Starscream");
97
+ expect(result).toBeNull();
98
+ });
99
+ });
100
+ describe("listAll", () => {
101
+ it("returns all documents", async () => {
102
+ const docs = [
103
+ { id: "1", name: "A", description: "", continuityId: "g1", factions: [], createdAt: new Date(), updatedAt: new Date() },
104
+ { id: "2", name: "B", description: "", continuityId: "g1", factions: [], createdAt: new Date(), updatedAt: new Date() },
105
+ ];
106
+ collection.find.mockReturnValue({ toArray: vi.fn().mockResolvedValue(docs) });
107
+ const result = await repo.listAll();
108
+ expect(result).toEqual(docs);
109
+ expect(result).toHaveLength(2);
110
+ });
111
+ });
112
+ describe("update", () => {
113
+ it("updates fields and bumps updatedAt", async () => {
114
+ const original = {
115
+ id: "uuid-1",
116
+ name: "Optimus Prime",
117
+ description: "Old description",
118
+ continuityId: "g1",
119
+ factions: ["Autobots"],
120
+ createdAt: new Date("2020-01-01"),
121
+ updatedAt: new Date("2020-01-01"),
122
+ };
123
+ const updated = {
124
+ ...original,
125
+ description: "New description",
126
+ updatedAt: new Date(),
127
+ };
128
+ collection.findOneAndUpdate.mockResolvedValue(updated);
129
+ const result = await repo.update("uuid-1", {
130
+ description: "New description",
131
+ });
132
+ expect(result).toEqual(updated);
133
+ expect(collection.findOneAndUpdate).toHaveBeenCalled();
134
+ const [, updateDoc] = collection.findOneAndUpdate.mock.calls[0];
135
+ expect(updateDoc.$set.description).toBe("New description");
136
+ expect(updateDoc.$set.updatedAt).toBeInstanceOf(Date);
137
+ });
138
+ it("returns null when document does not exist", async () => {
139
+ collection.findOneAndUpdate.mockResolvedValue(null);
140
+ const result = await repo.update("nonexistent", {
141
+ description: "Nope",
142
+ });
143
+ expect(result).toBeNull();
144
+ });
145
+ });
146
+ describe("delete", () => {
147
+ it("returns true when a document is deleted", async () => {
148
+ collection.deleteOne.mockResolvedValue({ deletedCount: 1 });
149
+ const result = await repo.delete("uuid-1");
150
+ expect(result).toBe(true);
151
+ expect(collection.deleteOne).toHaveBeenCalledWith({ id: "uuid-1" });
152
+ });
153
+ it("returns false when no document matched", async () => {
154
+ collection.deleteOne.mockResolvedValue({ deletedCount: 0 });
155
+ const result = await repo.delete("nonexistent");
156
+ expect(result).toBe(false);
157
+ });
158
+ });
159
+ });
160
+ //# sourceMappingURL=repository.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repository.test.js","sourceRoot":"","sources":["../../src/__tests__/repository.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mkdtempSync, writeFileSync, unlinkSync, rmdirSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { loadSettings } from "../index.js";
6
+ function withTempFile(name, content, fn) {
7
+ const dir = mkdtempSync(join(tmpdir(), "cap-test-"));
8
+ const filePath = join(dir, name);
9
+ writeFileSync(filePath, content, "utf-8");
10
+ try {
11
+ fn(filePath);
12
+ }
13
+ finally {
14
+ try {
15
+ unlinkSync(filePath);
16
+ }
17
+ catch { }
18
+ try {
19
+ rmdirSync(dir);
20
+ }
21
+ catch { }
22
+ }
23
+ }
24
+ const validSettings = JSON.stringify({
25
+ databaseName: "Nova Cronum",
26
+ entryFormat: {
27
+ requiredFields: ["name", "description", "continuityId", "factions"],
28
+ customFields: [
29
+ { key: "sparkId", type: "string", label: "Spark Signature" },
30
+ { key: "firepower", type: "number", label: "Firepower Rating" },
31
+ ],
32
+ },
33
+ });
34
+ describe("loadSettings", () => {
35
+ it("parses a valid settings file", () => {
36
+ withTempFile("settings.json", validSettings, (path) => {
37
+ const config = loadSettings(path);
38
+ expect(config.databaseName).toBe("Nova Cronum");
39
+ expect(config.entryFormat.customFields).toHaveLength(2);
40
+ expect(config.entryFormat.customFields[0].key).toBe("sparkId");
41
+ });
42
+ });
43
+ it("accepts settings without customFields", () => {
44
+ const minimal = JSON.stringify({
45
+ databaseName: "Test Universe",
46
+ entryFormat: {},
47
+ });
48
+ withTempFile("settings.json", minimal, (path) => {
49
+ const config = loadSettings(path);
50
+ expect(config.databaseName).toBe("Test Universe");
51
+ expect(config.entryFormat.customFields).toBeUndefined();
52
+ });
53
+ });
54
+ it("throws on missing file", () => {
55
+ expect(() => loadSettings("/nonexistent/path.json")).toThrow();
56
+ });
57
+ it("throws on invalid JSON", () => {
58
+ withTempFile("settings.json", "{bad json}", (path) => {
59
+ expect(() => loadSettings(path)).toThrow();
60
+ });
61
+ });
62
+ it("throws on invalid field type", () => {
63
+ const bad = JSON.stringify({
64
+ databaseName: "Test",
65
+ entryFormat: {
66
+ customFields: [{ key: "x", type: "binary", label: "X" }],
67
+ },
68
+ });
69
+ withTempFile("settings.json", bad, (path) => {
70
+ expect(() => loadSettings(path)).toThrow();
71
+ });
72
+ });
73
+ it("throws when databaseName is missing", () => {
74
+ const bad = JSON.stringify({
75
+ entryFormat: {},
76
+ });
77
+ withTempFile("settings.json", bad, (path) => {
78
+ expect(() => loadSettings(path)).toThrow();
79
+ });
80
+ });
81
+ });
82
+ //# sourceMappingURL=settings-loader.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-loader.test.js","sourceRoot":"","sources":["../../src/__tests__/settings-loader.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { CAPBaseSchema, CAPRepository } from "../index.js";
3
+ describe("CAPBaseSchema", () => {
4
+ it("validates a correct lore entry", () => {
5
+ const result = CAPBaseSchema.safeParse({
6
+ name: "Optimus Prime",
7
+ description: "Leader of the Autobots",
8
+ continuityId: "g1",
9
+ factions: ["Autobots"],
10
+ });
11
+ expect(result.success).toBe(true);
12
+ });
13
+ it("rejects an entry missing required fields", () => {
14
+ const result = CAPBaseSchema.safeParse({
15
+ name: "Megatron",
16
+ });
17
+ expect(result.success).toBe(false);
18
+ });
19
+ });
20
+ describe("CAPRepository", () => {
21
+ it("is an abstract class that cannot be instantiated directly", () => {
22
+ expect(CAPRepository).toBeDefined();
23
+ });
24
+ });
25
+ //# sourceMappingURL=smoke.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smoke.test.js","sourceRoot":"","sources":["../../src/__tests__/smoke.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE3D,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC;YACrC,IAAI,EAAE,eAAe;YACrB,WAAW,EAAE,wBAAwB;YACrC,YAAY,EAAE,IAAI;YAClB,QAAQ,EAAE,CAAC,UAAU,CAAC;SACvB,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC;YACrC,IAAI,EAAE,UAAU;SACjB,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,CAAC,aAAa,CAAC,CAAC,WAAW,EAAE,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,113 @@
1
+ import { z } from "zod";
2
+ import { Collection, Db } from "mongodb";
3
+ export interface ICAPBaseDocument {
4
+ id: string;
5
+ name: string;
6
+ description: string;
7
+ continuityId: string;
8
+ factions: string[];
9
+ createdAt: Date;
10
+ updatedAt: Date;
11
+ }
12
+ export interface ILoreVariant<T> {
13
+ variantName: string;
14
+ appearanceMediaId: string;
15
+ specifications: T;
16
+ }
17
+ export declare const CAPBaseSchema: z.ZodObject<{
18
+ name: z.ZodString;
19
+ description: z.ZodString;
20
+ continuityId: z.ZodString;
21
+ factions: z.ZodArray<z.ZodString, "many">;
22
+ }, "strip", z.ZodTypeAny, {
23
+ name: string;
24
+ description: string;
25
+ continuityId: string;
26
+ factions: string[];
27
+ }, {
28
+ name: string;
29
+ description: string;
30
+ continuityId: string;
31
+ factions: string[];
32
+ }>;
33
+ export declare abstract class CAPRepository<T extends ICAPBaseDocument> {
34
+ protected collection: Collection<T>;
35
+ constructor(db: Db, collectionName: string);
36
+ create(data: Omit<T, "id" | "createdAt" | "updatedAt">): Promise<T & {
37
+ id: string;
38
+ }>;
39
+ findById(id: string): Promise<T | null>;
40
+ findByName(name: string): Promise<T | null>;
41
+ listAll(): Promise<T[]>;
42
+ update(id: string, data: Partial<Omit<T, "id" | "createdAt" | "updatedAt">>): Promise<T | null>;
43
+ delete(id: string): Promise<boolean>;
44
+ }
45
+ export type FieldType = "string" | "number" | "boolean" | "date";
46
+ export interface CustomFieldConfig {
47
+ key: string;
48
+ type: FieldType;
49
+ label: string;
50
+ }
51
+ export interface EntryFormatConfig {
52
+ requiredFields?: string[];
53
+ customFields?: CustomFieldConfig[];
54
+ }
55
+ export interface SettingsConfig {
56
+ databaseName: string;
57
+ entryFormat: EntryFormatConfig;
58
+ }
59
+ export declare const CAPSettingsSchema: z.ZodObject<{
60
+ databaseName: z.ZodString;
61
+ entryFormat: z.ZodObject<{
62
+ requiredFields: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
63
+ customFields: z.ZodOptional<z.ZodArray<z.ZodObject<{
64
+ key: z.ZodString;
65
+ type: z.ZodEnum<["string", "number", "boolean", "date"]>;
66
+ label: z.ZodString;
67
+ }, "strip", z.ZodTypeAny, {
68
+ type: "string" | "number" | "boolean" | "date";
69
+ key: string;
70
+ label: string;
71
+ }, {
72
+ type: "string" | "number" | "boolean" | "date";
73
+ key: string;
74
+ label: string;
75
+ }>, "many">>;
76
+ }, "strip", z.ZodTypeAny, {
77
+ requiredFields?: string[] | undefined;
78
+ customFields?: {
79
+ type: "string" | "number" | "boolean" | "date";
80
+ key: string;
81
+ label: string;
82
+ }[] | undefined;
83
+ }, {
84
+ requiredFields?: string[] | undefined;
85
+ customFields?: {
86
+ type: "string" | "number" | "boolean" | "date";
87
+ key: string;
88
+ label: string;
89
+ }[] | undefined;
90
+ }>;
91
+ }, "strip", z.ZodTypeAny, {
92
+ databaseName: string;
93
+ entryFormat: {
94
+ requiredFields?: string[] | undefined;
95
+ customFields?: {
96
+ type: "string" | "number" | "boolean" | "date";
97
+ key: string;
98
+ label: string;
99
+ }[] | undefined;
100
+ };
101
+ }, {
102
+ databaseName: string;
103
+ entryFormat: {
104
+ requiredFields?: string[] | undefined;
105
+ customFields?: {
106
+ type: "string" | "number" | "boolean" | "date";
107
+ key: string;
108
+ label: string;
109
+ }[] | undefined;
110
+ };
111
+ }>;
112
+ export declare function generateDynamicSchema(customFields: CustomFieldConfig[]): z.ZodObject<z.ZodRawShape>;
113
+ export declare function loadSettings(path: string): SettingsConfig;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAqBxB,8BAA8B;AAC9B,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IACxB,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;CAC9B,CAAC,CAAC;AAEH,kCAAkC;AAClC,MAAM,OAAgB,aAAa;IACvB,UAAU,CAAgB;IAEpC,YAAY,EAAM,EAAE,cAAsB;QACxC,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,CAAI,cAAc,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,CAAC,MAAM,CACV,IAA+C;QAE/C,MAAM,GAAG,GAAG;YACV,GAAG,IAAI;YACP,EAAE,EAAE,UAAU,EAAE;YAChB,SAAS,EAAE,IAAI,IAAI,EAAE;YACrB,SAAS,EAAE,IAAI,IAAI,EAAE;SACf,CAAC;QACT,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACrC,OAAO,GAAyB,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,EAAE,EAAS,CAAsB,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,IAAY;QAC3B,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAC7B,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE;SAC/B,CAAsB,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,OAAO;QACX,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,OAAO,EAAkB,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,MAAM,CACV,EAAU,EACV,IAAwD;QAExD,OAAO,IAAI,CAAC,UAAU,CAAC,gBAAgB,CACrC,EAAE,EAAE,EAAS,EACb,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,EAAS,EACnD,EAAE,cAAc,EAAE,OAAO,EAAE,CACP,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAU;QACrB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,EAAE,EAAS,CAAC,CAAC;QAC9D,OAAO,MAAM,CAAC,YAAY,GAAG,CAAC,CAAC;IACjC,CAAC;CACF;AAsBD,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACjC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACtB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IACrD,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACzB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/B,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,cAAc,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;QAC9C,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,QAAQ,EAAE;KACpD,CAAC;CACH,CAAC,CAAC;AAEH,SAAS,mBAAmB,CAAC,SAAoB;IAC/C,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,QAAQ;YACX,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;QACpB,KAAK,QAAQ;YACX,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;QACpB,KAAK,SAAS;YACZ,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC;QACrB,KAAK,MAAM;YACT,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC;QAC/B;YACE,MAAM,IAAI,KAAK,CAAC,uBAAuB,SAAS,EAAE,CAAC,CAAC;IACxD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,YAAiC;IAEjC,MAAM,KAAK,GAA8B,EAAE,CAAC;IAE5C,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;QACjC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,iBAAiB,CAAC,KAAK,CAAC,MAAM,CAAmB,CAAC;AAC3D,CAAC"}
package/package.json CHANGED
@@ -1,9 +1,21 @@
1
1
  {
2
2
  "name": "@crazygiscool/cap",
3
- "version": "1.1.8",
3
+ "version": "1.1.10",
4
4
  "description": "The Central Archive Protocol core library for lore documentation",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "README.md",
16
+ "docs/PLAN.md",
17
+ "docs/QUICKSTART.md"
18
+ ],
7
19
  "type": "module",
8
20
  "scripts": {
9
21
  "build": "tsc",
@@ -1,53 +0,0 @@
1
- name: Publish
2
-
3
- on:
4
- push:
5
- tags:
6
- - "v*"
7
- release:
8
- types: [published]
9
-
10
- jobs:
11
- test:
12
- runs-on: ubuntu-latest
13
- steps:
14
- - uses: actions/checkout@v4
15
- - uses: actions/setup-node@v4
16
- with:
17
- node-version: 20
18
- - run: npm ci
19
- - run: npm run build
20
- - run: npm test
21
-
22
- publish-npm:
23
- needs: test
24
- runs-on: ubuntu-latest
25
- steps:
26
- - uses: actions/checkout@v4
27
- - uses: actions/setup-node@v4
28
- with:
29
- node-version: 20
30
- registry-url: "https://registry.npmjs.org"
31
- - run: npm ci
32
- - run: npm run build
33
- - run: npm publish --access public
34
- env:
35
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
36
-
37
- publish-github:
38
- needs: test
39
- runs-on: ubuntu-latest
40
- permissions:
41
- packages: write
42
- steps:
43
- - uses: actions/checkout@v4
44
- - uses: actions/setup-node@v4
45
- with:
46
- node-version: 20
47
- registry-url: "https://npm.pkg.github.com"
48
- scope: "@crazygiscool"
49
- - run: npm ci
50
- - run: npm run build
51
- - run: npm publish
52
- env:
53
- NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -1,130 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import {
3
- CAPBaseSchema,
4
- generateDynamicSchema,
5
- type CustomFieldConfig,
6
- } from "../index.js";
7
-
8
- const baseFields = {
9
- name: "Optimus Prime",
10
- description: "Leader of the Autobots",
11
- continuityId: "g1",
12
- factions: ["Autobots"],
13
- };
14
-
15
- describe("generateDynamicSchema", () => {
16
- it("returns base schema when customFields is empty", () => {
17
- const schema = generateDynamicSchema([]);
18
- const result = schema.safeParse(baseFields);
19
- expect(result.success).toBe(true);
20
- });
21
-
22
- it("adds a string field", () => {
23
- const fields: CustomFieldConfig[] = [
24
- { key: "sparkId", type: "string", label: "Spark Signature" },
25
- ];
26
- const schema = generateDynamicSchema(fields);
27
- const result = schema.safeParse({
28
- ...baseFields,
29
- sparkId: "SP-2187",
30
- });
31
- expect(result.success).toBe(true);
32
- });
33
-
34
- it("rejects a number for a string field", () => {
35
- const fields: CustomFieldConfig[] = [
36
- { key: "sparkId", type: "string", label: "Spark Signature" },
37
- ];
38
- const schema = generateDynamicSchema(fields);
39
- const result = schema.safeParse({
40
- ...baseFields,
41
- sparkId: 42,
42
- });
43
- expect(result.success).toBe(false);
44
- });
45
-
46
- it("adds a number field", () => {
47
- const fields: CustomFieldConfig[] = [
48
- { key: "firepower", type: "number", label: "Firepower Rating" },
49
- ];
50
- const schema = generateDynamicSchema(fields);
51
- const ok = schema.safeParse({
52
- ...baseFields,
53
- firepower: 9000,
54
- });
55
- expect(ok.success).toBe(true);
56
-
57
- const fail = schema.safeParse({
58
- ...baseFields,
59
- firepower: "over 9000",
60
- });
61
- expect(fail.success).toBe(false);
62
- });
63
-
64
- it("adds a boolean field", () => {
65
- const fields: CustomFieldConfig[] = [
66
- { key: "isOnline", type: "boolean", label: "Online Status" },
67
- ];
68
- const schema = generateDynamicSchema(fields);
69
- const ok = schema.safeParse({
70
- ...baseFields,
71
- isOnline: true,
72
- });
73
- expect(ok.success).toBe(true);
74
-
75
- const fail = schema.safeParse({
76
- ...baseFields,
77
- isOnline: "yes",
78
- });
79
- expect(fail.success).toBe(false);
80
- });
81
-
82
- it("adds a date field (ISO datetime string)", () => {
83
- const fields: CustomFieldConfig[] = [
84
- { key: "activated", type: "date", label: "Activation Date" },
85
- ];
86
- const schema = generateDynamicSchema(fields);
87
- const ok = schema.safeParse({
88
- ...baseFields,
89
- activated: "2024-01-15T08:00:00.000Z",
90
- });
91
- expect(ok.success).toBe(true);
92
-
93
- const fail = schema.safeParse({
94
- ...baseFields,
95
- activated: "not-a-date",
96
- });
97
- expect(fail.success).toBe(false);
98
- });
99
-
100
- it("handles multiple custom fields of different types", () => {
101
- const fields: CustomFieldConfig[] = [
102
- { key: "sparkId", type: "string", label: "Spark Signature" },
103
- { key: "firepower", type: "number", label: "Firepower Rating" },
104
- { key: "isOnline", type: "boolean", label: "Online Status" },
105
- ];
106
- const schema = generateDynamicSchema(fields);
107
- const result = schema.safeParse({
108
- ...baseFields,
109
- sparkId: "SP-2187",
110
- firepower: 9000,
111
- isOnline: true,
112
- });
113
- expect(result.success).toBe(true);
114
- });
115
-
116
- it("rejects missing custom fields", () => {
117
- const fields: CustomFieldConfig[] = [
118
- { key: "sparkId", type: "string", label: "Spark Signature" },
119
- ];
120
- const schema = generateDynamicSchema(fields);
121
- const result = schema.safeParse(baseFields);
122
- expect(result.success).toBe(false);
123
- });
124
-
125
- it("still enforces base schema requirements", () => {
126
- const schema = generateDynamicSchema([]);
127
- const result = schema.safeParse({ name: "Megatron" });
128
- expect(result.success).toBe(false);
129
- });
130
- });
@@ -1,195 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import { CAPRepository, type ICAPBaseDocument } from "../index.js";
3
-
4
- interface TestEntry extends ICAPBaseDocument {
5
- sparkId: string;
6
- }
7
-
8
- class TestRepository extends CAPRepository<TestEntry> {
9
- constructor(db: any) {
10
- super(db, "test_entries");
11
- }
12
- }
13
-
14
- function mockDb() {
15
- const mockCollection = {
16
- insertOne: vi.fn(),
17
- findOne: vi.fn(),
18
- find: vi.fn(() => ({
19
- toArray: vi.fn(),
20
- })),
21
- findOneAndUpdate: vi.fn(),
22
- deleteOne: vi.fn(),
23
- };
24
-
25
- const db = {
26
- collection: vi.fn(() => mockCollection),
27
- } as any;
28
-
29
- return { db, collection: mockCollection };
30
- }
31
-
32
- describe("CAPRepository", () => {
33
- let repo: TestRepository;
34
- let collection: ReturnType<typeof mockDb>["collection"];
35
-
36
- beforeEach(() => {
37
- const { db, collection: c } = mockDb();
38
- repo = new TestRepository(db);
39
- collection = c;
40
- });
41
-
42
- describe("create", () => {
43
- it("inserts a document with generated id and timestamps", async () => {
44
- collection.insertOne.mockResolvedValue({ acknowledged: true });
45
-
46
- const result = await repo.create({
47
- name: "Optimus Prime",
48
- description: "Leader of the Autobots",
49
- continuityId: "g1",
50
- factions: ["Autobots"],
51
- sparkId: "SP-2187",
52
- });
53
-
54
- expect(result.id).toBeDefined();
55
- expect(typeof result.id).toBe("string");
56
- expect(result.createdAt).toBeInstanceOf(Date);
57
- expect(result.updatedAt).toBeInstanceOf(Date);
58
- expect(result.name).toBe("Optimus Prime");
59
- expect(result.sparkId).toBe("SP-2187");
60
-
61
- expect(collection.insertOne).toHaveBeenCalledOnce();
62
- const inserted = collection.insertOne.mock.calls[0][0];
63
- expect(inserted.id).toBe(result.id);
64
- expect(inserted.createdAt).toBeInstanceOf(Date);
65
- expect(inserted.updatedAt).toBeInstanceOf(Date);
66
- });
67
- });
68
-
69
- describe("findById", () => {
70
- it("returns a document by id", async () => {
71
- const doc = {
72
- _id: "abc",
73
- id: "uuid-123",
74
- name: "Bumblebee",
75
- description: "Scout",
76
- continuityId: "g1",
77
- factions: ["Autobots"],
78
- sparkId: "SP-0001",
79
- createdAt: new Date(),
80
- updatedAt: new Date(),
81
- };
82
- collection.findOne.mockResolvedValue(doc);
83
-
84
- const result = await repo.findById("uuid-123");
85
- expect(result).toEqual(doc);
86
- expect(collection.findOne).toHaveBeenCalledWith({ id: "uuid-123" });
87
- });
88
-
89
- it("returns null when not found", async () => {
90
- collection.findOne.mockResolvedValue(null);
91
-
92
- const result = await repo.findById("nonexistent");
93
- expect(result).toBeNull();
94
- });
95
- });
96
-
97
- describe("findByName", () => {
98
- it("returns a document by name (case-insensitive)", async () => {
99
- const doc = {
100
- _id: "abc",
101
- id: "uuid-1",
102
- name: "Megatron",
103
- description: "Decepticon leader",
104
- continuityId: "g1",
105
- factions: ["Decepticons"],
106
- createdAt: new Date(),
107
- updatedAt: new Date(),
108
- };
109
- collection.findOne.mockResolvedValue(doc);
110
-
111
- const result = await repo.findByName("megatron");
112
- expect(result).toEqual(doc);
113
- });
114
-
115
- it("returns null when not found", async () => {
116
- collection.findOne.mockResolvedValue(null);
117
-
118
- const result = await repo.findByName("Starscream");
119
- expect(result).toBeNull();
120
- });
121
- });
122
-
123
- describe("listAll", () => {
124
- it("returns all documents", async () => {
125
- const docs = [
126
- { id: "1", name: "A", description: "", continuityId: "g1", factions: [], createdAt: new Date(), updatedAt: new Date() },
127
- { id: "2", name: "B", description: "", continuityId: "g1", factions: [], createdAt: new Date(), updatedAt: new Date() },
128
- ];
129
- collection.find.mockReturnValue({ toArray: vi.fn().mockResolvedValue(docs) });
130
-
131
- const result = await repo.listAll();
132
- expect(result).toEqual(docs);
133
- expect(result).toHaveLength(2);
134
- });
135
- });
136
-
137
- describe("update", () => {
138
- it("updates fields and bumps updatedAt", async () => {
139
- const original = {
140
- id: "uuid-1",
141
- name: "Optimus Prime",
142
- description: "Old description",
143
- continuityId: "g1",
144
- factions: ["Autobots"],
145
- createdAt: new Date("2020-01-01"),
146
- updatedAt: new Date("2020-01-01"),
147
- };
148
-
149
- const updated = {
150
- ...original,
151
- description: "New description",
152
- updatedAt: new Date(),
153
- };
154
-
155
- collection.findOneAndUpdate.mockResolvedValue(updated);
156
-
157
- const result = await repo.update("uuid-1", {
158
- description: "New description",
159
- });
160
-
161
- expect(result).toEqual(updated);
162
- expect(collection.findOneAndUpdate).toHaveBeenCalled();
163
-
164
- const [, updateDoc] = collection.findOneAndUpdate.mock.calls[0];
165
- expect(updateDoc.$set.description).toBe("New description");
166
- expect(updateDoc.$set.updatedAt).toBeInstanceOf(Date);
167
- });
168
-
169
- it("returns null when document does not exist", async () => {
170
- collection.findOneAndUpdate.mockResolvedValue(null);
171
-
172
- const result = await repo.update("nonexistent", {
173
- description: "Nope",
174
- });
175
- expect(result).toBeNull();
176
- });
177
- });
178
-
179
- describe("delete", () => {
180
- it("returns true when a document is deleted", async () => {
181
- collection.deleteOne.mockResolvedValue({ deletedCount: 1 });
182
-
183
- const result = await repo.delete("uuid-1");
184
- expect(result).toBe(true);
185
- expect(collection.deleteOne).toHaveBeenCalledWith({ id: "uuid-1" });
186
- });
187
-
188
- it("returns false when no document matched", async () => {
189
- collection.deleteOne.mockResolvedValue({ deletedCount: 0 });
190
-
191
- const result = await repo.delete("nonexistent");
192
- expect(result).toBe(false);
193
- });
194
- });
195
- });
@@ -1,90 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { mkdtempSync, writeFileSync, unlinkSync, rmdirSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import { loadSettings } from "../index.js";
6
-
7
- function withTempFile(
8
- name: string,
9
- content: string,
10
- fn: (path: string) => void,
11
- ) {
12
- const dir = mkdtempSync(join(tmpdir(), "cap-test-"));
13
- const filePath = join(dir, name);
14
- writeFileSync(filePath, content, "utf-8");
15
- try {
16
- fn(filePath);
17
- } finally {
18
- try {
19
- unlinkSync(filePath);
20
- } catch {}
21
- try {
22
- rmdirSync(dir);
23
- } catch {}
24
- }
25
- }
26
-
27
- const validSettings = JSON.stringify({
28
- databaseName: "Nova Cronum",
29
- entryFormat: {
30
- requiredFields: ["name", "description", "continuityId", "factions"],
31
- customFields: [
32
- { key: "sparkId", type: "string", label: "Spark Signature" },
33
- { key: "firepower", type: "number", label: "Firepower Rating" },
34
- ],
35
- },
36
- });
37
-
38
- describe("loadSettings", () => {
39
- it("parses a valid settings file", () => {
40
- withTempFile("settings.json", validSettings, (path) => {
41
- const config = loadSettings(path);
42
- expect(config.databaseName).toBe("Nova Cronum");
43
- expect(config.entryFormat.customFields).toHaveLength(2);
44
- expect(config.entryFormat.customFields![0].key).toBe("sparkId");
45
- });
46
- });
47
-
48
- it("accepts settings without customFields", () => {
49
- const minimal = JSON.stringify({
50
- databaseName: "Test Universe",
51
- entryFormat: {},
52
- });
53
- withTempFile("settings.json", minimal, (path) => {
54
- const config = loadSettings(path);
55
- expect(config.databaseName).toBe("Test Universe");
56
- expect(config.entryFormat.customFields).toBeUndefined();
57
- });
58
- });
59
-
60
- it("throws on missing file", () => {
61
- expect(() => loadSettings("/nonexistent/path.json")).toThrow();
62
- });
63
-
64
- it("throws on invalid JSON", () => {
65
- withTempFile("settings.json", "{bad json}", (path) => {
66
- expect(() => loadSettings(path)).toThrow();
67
- });
68
- });
69
-
70
- it("throws on invalid field type", () => {
71
- const bad = JSON.stringify({
72
- databaseName: "Test",
73
- entryFormat: {
74
- customFields: [{ key: "x", type: "binary", label: "X" }],
75
- },
76
- });
77
- withTempFile("settings.json", bad, (path) => {
78
- expect(() => loadSettings(path)).toThrow();
79
- });
80
- });
81
-
82
- it("throws when databaseName is missing", () => {
83
- const bad = JSON.stringify({
84
- entryFormat: {},
85
- });
86
- withTempFile("settings.json", bad, (path) => {
87
- expect(() => loadSettings(path)).toThrow();
88
- });
89
- });
90
- });
@@ -1,27 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { CAPBaseSchema, CAPRepository } from "../index.js";
3
-
4
- describe("CAPBaseSchema", () => {
5
- it("validates a correct lore entry", () => {
6
- const result = CAPBaseSchema.safeParse({
7
- name: "Optimus Prime",
8
- description: "Leader of the Autobots",
9
- continuityId: "g1",
10
- factions: ["Autobots"],
11
- });
12
- expect(result.success).toBe(true);
13
- });
14
-
15
- it("rejects an entry missing required fields", () => {
16
- const result = CAPBaseSchema.safeParse({
17
- name: "Megatron",
18
- });
19
- expect(result.success).toBe(false);
20
- });
21
- });
22
-
23
- describe("CAPRepository", () => {
24
- it("is an abstract class that cannot be instantiated directly", () => {
25
- expect(CAPRepository).toBeDefined();
26
- });
27
- });
package/src/index.ts DELETED
@@ -1,149 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { readFileSync } from "node:fs";
3
- import { z } from "zod";
4
- import { Collection, Db } from "mongodb";
5
-
6
- // Universal Document Rules
7
- export interface ICAPBaseDocument {
8
- id: string;
9
- name: string;
10
- description: string;
11
- continuityId: string;
12
- factions: string[];
13
- createdAt: Date;
14
- updatedAt: Date;
15
- }
16
-
17
- // Flexible variant template (Alt-modes, Multiverse variants, Eras)
18
- export interface ILoreVariant<T> {
19
- variantName: string;
20
- appearanceMediaId: string;
21
- specifications: T;
22
- }
23
-
24
- // Runtime validation baseline
25
- export const CAPBaseSchema = z.object({
26
- name: z.string().min(1),
27
- description: z.string(),
28
- continuityId: z.string(),
29
- factions: z.array(z.string()),
30
- });
31
-
32
- // Standardized MongoDB Data Layer
33
- export abstract class CAPRepository<T extends ICAPBaseDocument> {
34
- protected collection: Collection<T>;
35
-
36
- constructor(db: Db, collectionName: string) {
37
- this.collection = db.collection<T>(collectionName);
38
- }
39
-
40
- async create(
41
- data: Omit<T, "id" | "createdAt" | "updatedAt">,
42
- ): Promise<T & { id: string }> {
43
- const doc = {
44
- ...data,
45
- id: randomUUID(),
46
- createdAt: new Date(),
47
- updatedAt: new Date(),
48
- } as any;
49
- await this.collection.insertOne(doc);
50
- return doc as T & { id: string };
51
- }
52
-
53
- async findById(id: string): Promise<T | null> {
54
- return this.collection.findOne({ id } as any) as Promise<T | null>;
55
- }
56
-
57
- async findByName(name: string): Promise<T | null> {
58
- return this.collection.findOne({
59
- name: { $regex: name, $options: "i" },
60
- } as any) as Promise<T | null>;
61
- }
62
-
63
- async listAll(): Promise<T[]> {
64
- return this.collection.find({}).toArray() as Promise<T[]>;
65
- }
66
-
67
- async update(
68
- id: string,
69
- data: Partial<Omit<T, "id" | "createdAt" | "updatedAt">>,
70
- ): Promise<T | null> {
71
- return this.collection.findOneAndUpdate(
72
- { id } as any,
73
- { $set: { ...data, updatedAt: new Date() } } as any,
74
- { returnDocument: "after" },
75
- ) as Promise<T | null>;
76
- }
77
-
78
- async delete(id: string): Promise<boolean> {
79
- const result = await this.collection.deleteOne({ id } as any);
80
- return result.deletedCount > 0;
81
- }
82
- }
83
-
84
- // --- Dynamic Schema Generator ---
85
-
86
- export type FieldType = "string" | "number" | "boolean" | "date";
87
-
88
- export interface CustomFieldConfig {
89
- key: string;
90
- type: FieldType;
91
- label: string;
92
- }
93
-
94
- export interface EntryFormatConfig {
95
- requiredFields?: string[];
96
- customFields?: CustomFieldConfig[];
97
- }
98
-
99
- export interface SettingsConfig {
100
- databaseName: string;
101
- entryFormat: EntryFormatConfig;
102
- }
103
-
104
- const CustomFieldSchema = z.object({
105
- key: z.string().min(1),
106
- type: z.enum(["string", "number", "boolean", "date"]),
107
- label: z.string().min(1),
108
- });
109
-
110
- export const CAPSettingsSchema = z.object({
111
- databaseName: z.string().min(1),
112
- entryFormat: z.object({
113
- requiredFields: z.array(z.string()).optional(),
114
- customFields: z.array(CustomFieldSchema).optional(),
115
- }),
116
- });
117
-
118
- function zodTypeForFieldType(fieldType: FieldType): z.ZodType {
119
- switch (fieldType) {
120
- case "string":
121
- return z.string();
122
- case "number":
123
- return z.number();
124
- case "boolean":
125
- return z.boolean();
126
- case "date":
127
- return z.string().datetime();
128
- default:
129
- throw new Error(`Unknown field type: ${fieldType}`);
130
- }
131
- }
132
-
133
- export function generateDynamicSchema(
134
- customFields: CustomFieldConfig[],
135
- ): z.ZodObject<z.ZodRawShape> {
136
- const shape: Record<string, z.ZodType> = {};
137
-
138
- for (const field of customFields) {
139
- shape[field.key] = zodTypeForFieldType(field.type);
140
- }
141
-
142
- return CAPBaseSchema.extend(shape);
143
- }
144
-
145
- export function loadSettings(path: string): SettingsConfig {
146
- const raw = readFileSync(path, "utf-8");
147
- const parsed = JSON.parse(raw);
148
- return CAPSettingsSchema.parse(parsed) as SettingsConfig;
149
- }
package/tsconfig.json DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "declaration": true,
7
- "sourceMap": true,
8
- "outDir": "./dist",
9
- "rootDir": "./src",
10
- "strict": true,
11
- "esModuleInterop": true,
12
- "skipLibCheck": true,
13
- "forceConsistentCasingInFileNames": true,
14
- },
15
- "include": ["src/**/*"],
16
- }
package/vitest.config.ts DELETED
@@ -1,9 +0,0 @@
1
- import { defineConfig } from "vitest/config";
2
-
3
- export default defineConfig({
4
- test: {
5
- globals: true,
6
- environment: "node",
7
- include: ["src/**/*.test.ts"],
8
- },
9
- });