@aurios/mizzle 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +37 -0
- package/LICENSE +21 -0
- package/README.md +166 -57
- package/dist/chunk-AQVECMXP.js +1 -0
- package/dist/chunk-DU7UPWBW.js +1 -0
- package/dist/chunk-GPYZK4WY.js +1 -0
- package/dist/chunk-NPPZW6VT.js +1 -0
- package/dist/chunk-TOYV2M4M.js +1 -0
- package/dist/chunk-UM3YF5EC.js +1 -0
- package/dist/columns.d.ts +1 -0
- package/dist/columns.js +1 -0
- package/dist/db-zHIHBm1E.d.ts +815 -0
- package/dist/db.d.ts +3 -0
- package/dist/db.js +1 -0
- package/dist/diff.d.ts +18 -0
- package/dist/diff.js +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +1 -0
- package/dist/introspection.d.ts +7 -0
- package/dist/introspection.js +1 -0
- package/dist/operators-BVreW0ky.d.ts +719 -0
- package/dist/snapshot.d.ts +24 -0
- package/dist/snapshot.js +1 -0
- package/dist/table.d.ts +1 -0
- package/dist/table.js +1 -0
- package/dist/transaction-RE7LXTGV.js +1 -0
- package/package.json +82 -66
- package/src/builders/base.ts +53 -56
- package/src/builders/batch-get.ts +63 -58
- package/src/builders/batch-write.ts +81 -78
- package/src/builders/delete.ts +46 -53
- package/src/builders/insert.ts +158 -150
- package/src/builders/query-promise.ts +26 -35
- package/src/builders/relational-builder.ts +214 -191
- package/src/builders/select.ts +250 -237
- package/src/builders/transaction.ts +170 -152
- package/src/builders/update.ts +197 -192
- package/src/columns/binary-set.ts +29 -38
- package/src/columns/binary.ts +25 -35
- package/src/columns/boolean.ts +25 -30
- package/src/columns/date.ts +57 -64
- package/src/columns/index.ts +15 -15
- package/src/columns/json.ts +39 -48
- package/src/columns/list.ts +26 -36
- package/src/columns/map.ts +26 -34
- package/src/columns/number-set.ts +29 -38
- package/src/columns/number.ts +33 -40
- package/src/columns/string-set.ts +38 -47
- package/src/columns/string.ts +37 -49
- package/src/columns/uuid.ts +26 -33
- package/src/core/client.ts +9 -9
- package/src/core/column-builder.ts +194 -220
- package/src/core/column.ts +127 -135
- package/src/core/diff.ts +40 -34
- package/src/core/errors.ts +20 -17
- package/src/core/introspection.ts +62 -58
- package/src/core/operations.ts +17 -23
- package/src/core/parser.ts +82 -89
- package/src/core/relations.ts +164 -154
- package/src/core/retry.ts +52 -52
- package/src/core/snapshot.ts +131 -130
- package/src/core/strategies.ts +222 -218
- package/src/core/table.ts +189 -202
- package/src/core/validation.ts +52 -52
- package/src/db.ts +211 -209
- package/src/expressions/actions.ts +26 -26
- package/src/expressions/builder.ts +62 -54
- package/src/expressions/operators.ts +48 -48
- package/src/expressions/update-builder.ts +78 -76
- package/src/index.ts +1 -1
- package/src/indexes.ts +8 -8
- package/test/batch-resilience.test.ts +138 -0
- package/test/builders/delete.test.ts +100 -0
- package/test/builders/insert.test.ts +216 -0
- package/test/builders/relational-types.test.ts +55 -0
- package/test/builders/relational.integration.test.ts +291 -0
- package/test/builders/relational.test.ts +66 -0
- package/test/builders/select.test.ts +411 -0
- package/test/builders/transaction-errors.test.ts +46 -0
- package/test/builders/transaction-execution.test.ts +99 -0
- package/test/builders/transaction-proxy.test.ts +41 -0
- package/test/builders/update-expression.test.ts +106 -0
- package/test/builders/update.test.ts +179 -0
- package/test/core/diff.test.ts +152 -0
- package/test/core/expressions.test.ts +64 -0
- package/test/core/introspection.test.ts +47 -0
- package/test/core/parser.test.ts +69 -0
- package/test/core/snapshot-gen.test.ts +155 -0
- package/test/core/snapshot.test.ts +52 -0
- package/test/date-column.test.ts +159 -0
- package/test/fluent-writes.integration.test.ts +148 -0
- package/test/integration-retry.test.ts +77 -0
- package/test/integration.test.ts +105 -0
- package/test/item-size-error.test.ts +16 -0
- package/test/item-size-validation.test.ts +82 -0
- package/test/item-size.test.ts +47 -0
- package/test/iterator-pagination.integration.test.ts +132 -0
- package/test/jsdoc-builders.test.ts +55 -0
- package/test/jsdoc-schema.test.ts +107 -0
- package/test/json-column.test.ts +51 -0
- package/test/metadata.test.ts +54 -0
- package/test/mizzle-package.test.ts +20 -0
- package/test/relational-centralized.test.ts +83 -0
- package/test/relational-definition.test.ts +75 -0
- package/test/relational-init.test.ts +30 -0
- package/test/relational-proxy.test.ts +52 -0
- package/test/relations.test.ts +45 -0
- package/test/resilience-config.test.ts +34 -0
- package/test/retry-handler.test.ts +63 -0
- package/test/transaction.integration.test.ts +153 -0
- package/test/unified-select.integration.test.ts +153 -0
- package/test/unified-update.integration.test.ts +139 -0
- package/test/update.integration.test.ts +132 -0
- package/tsconfig.json +12 -8
- package/tsup.config.ts +11 -11
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { ItemSizeExceededError } from "@aurios/mizzle";
|
|
3
|
+
|
|
4
|
+
describe("ItemSizeExceededError", () => {
|
|
5
|
+
it("should be an instance of Error", () => {
|
|
6
|
+
const error = new ItemSizeExceededError("Item too large");
|
|
7
|
+
expect(error).toBeInstanceOf(Error);
|
|
8
|
+
expect(error).toBeInstanceOf(ItemSizeExceededError);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("should have correct name and message", () => {
|
|
12
|
+
const error = new ItemSizeExceededError("Size 500KB exceeds 400KB limit");
|
|
13
|
+
expect(error.name).toBe("ItemSizeExceededError");
|
|
14
|
+
expect(error.message).toBe("Size 500KB exceeds 400KB limit");
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mizzle, ItemSizeExceededError } from "@aurios/mizzle";
|
|
3
|
+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
4
|
+
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
|
|
5
|
+
import { ENTITY_SYMBOLS, TABLE_SYMBOLS } from "@repo/shared";
|
|
6
|
+
|
|
7
|
+
// Mock send method
|
|
8
|
+
const mockSend = vi.fn();
|
|
9
|
+
const mockDocClient = {
|
|
10
|
+
send: mockSend,
|
|
11
|
+
middlewareStack: {
|
|
12
|
+
add: vi.fn(),
|
|
13
|
+
remove: vi.fn(),
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe("Item Size Validation Integration", () => {
|
|
18
|
+
const mockTable = {
|
|
19
|
+
[ENTITY_SYMBOLS.PHYSICAL_TABLE]: {
|
|
20
|
+
[TABLE_SYMBOLS.TABLE_NAME]: "test-table",
|
|
21
|
+
[TABLE_SYMBOLS.PARTITION_KEY]: { name: "pk" },
|
|
22
|
+
[TABLE_SYMBOLS.SORT_KEY]: undefined,
|
|
23
|
+
[TABLE_SYMBOLS.INDEXES]: {},
|
|
24
|
+
},
|
|
25
|
+
[ENTITY_SYMBOLS.ENTITY_STRATEGY]: { pk: { type: "static", segments: ["test"] } },
|
|
26
|
+
[ENTITY_SYMBOLS.COLUMNS]: {
|
|
27
|
+
pk: { name: "pk" },
|
|
28
|
+
data: { name: "data" },
|
|
29
|
+
},
|
|
30
|
+
} as any;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
vi.spyOn(DynamoDBDocumentClient, "from").mockReturnValue(mockDocClient as any);
|
|
34
|
+
mockSend.mockReset();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
vi.restoreAllMocks();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should throw ItemSizeExceededError on insert if item is too large", async () => {
|
|
42
|
+
const client = new DynamoDBClient({});
|
|
43
|
+
const db = mizzle(client);
|
|
44
|
+
|
|
45
|
+
const largeItem = {
|
|
46
|
+
pk: "test",
|
|
47
|
+
data: "x".repeat(401 * 1024),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
await expect(db.insert(mockTable).values(largeItem).execute()).rejects.toThrow(
|
|
51
|
+
ItemSizeExceededError,
|
|
52
|
+
);
|
|
53
|
+
expect(mockSend).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should throw ItemSizeExceededError on update if update payload is too large", async () => {
|
|
57
|
+
const client = new DynamoDBClient({});
|
|
58
|
+
const db = mizzle(client);
|
|
59
|
+
|
|
60
|
+
const largeData = "x".repeat(401 * 1024);
|
|
61
|
+
|
|
62
|
+
await expect(
|
|
63
|
+
db.update(mockTable).key({ pk: "test" }).set({ data: largeData }).execute(),
|
|
64
|
+
).rejects.toThrow(ItemSizeExceededError);
|
|
65
|
+
expect(mockSend).not.toHaveBeenCalled();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should throw ItemSizeExceededError on batchWrite if any item is too large", async () => {
|
|
69
|
+
const client = new DynamoDBClient({});
|
|
70
|
+
const db = mizzle(client);
|
|
71
|
+
|
|
72
|
+
const largeItem = {
|
|
73
|
+
pk: "test",
|
|
74
|
+
data: "x".repeat(401 * 1024),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
await expect(
|
|
78
|
+
db.batchWrite(mockTable, [{ type: "put", item: largeItem }]).execute(),
|
|
79
|
+
).rejects.toThrow(ItemSizeExceededError);
|
|
80
|
+
expect(mockSend).not.toHaveBeenCalled();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { calculateItemSize } from "@aurios/mizzle/core/validation";
|
|
3
|
+
|
|
4
|
+
describe("calculateItemSize", () => {
|
|
5
|
+
it("should calculate size of a simple item", () => {
|
|
6
|
+
const item = {
|
|
7
|
+
pk: "USER#1",
|
|
8
|
+
name: "John Doe",
|
|
9
|
+
};
|
|
10
|
+
// pk (2) + USER#1 (6) + name (4) + John Doe (8) = 20
|
|
11
|
+
expect(calculateItemSize(item)).toBe(20);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should handle numbers and booleans", () => {
|
|
15
|
+
const item = {
|
|
16
|
+
age: 30,
|
|
17
|
+
active: true,
|
|
18
|
+
};
|
|
19
|
+
// age (3) + 21 (number max) + active (6) + 1 (bool) = 31
|
|
20
|
+
expect(calculateItemSize(item)).toBe(31);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should handle lists", () => {
|
|
24
|
+
const item = {
|
|
25
|
+
tags: ["a", "b"],
|
|
26
|
+
};
|
|
27
|
+
// tags (4) + [3 overhead + a(1) + b(1)] = 4 + 5 = 9
|
|
28
|
+
expect(calculateItemSize(item)).toBe(9);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should handle maps", () => {
|
|
32
|
+
const item = {
|
|
33
|
+
meta: { key: "val" },
|
|
34
|
+
};
|
|
35
|
+
// meta (4) + [3 overhead + key(3) + val(3)] = 4 + 9 = 13
|
|
36
|
+
expect(calculateItemSize(item)).toBe(13);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should accurately estimate large items", () => {
|
|
40
|
+
const largeString = "x".repeat(100 * 1024); // 100KB
|
|
41
|
+
const item = {
|
|
42
|
+
data: largeString,
|
|
43
|
+
};
|
|
44
|
+
// data (4) + 102400 = 102404
|
|
45
|
+
expect(calculateItemSize(item)).toBe(102404);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
+
import { dynamoTable, dynamoEntity } from "@aurios/mizzle/table";
|
|
3
|
+
import { string, uuid } from "@aurios/mizzle/columns";
|
|
4
|
+
import { prefixKey, staticKey } from "@aurios/mizzle";
|
|
5
|
+
import { DynamoDBClient, CreateTableCommand, DeleteTableCommand, DescribeTableCommand, DescribeTableCommand } from "@aws-sdk/client-dynamodb";
|
|
6
|
+
import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb";
|
|
7
|
+
import { mizzle } from "@aurios/mizzle/db";
|
|
8
|
+
|
|
9
|
+
const client = new DynamoDBClient({
|
|
10
|
+
endpoint: "http://localhost:8000",
|
|
11
|
+
region: "us-east-1",
|
|
12
|
+
credentials: {
|
|
13
|
+
accessKeyId: "local",
|
|
14
|
+
secretAccessKey: "local",
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const docClient = DynamoDBDocumentClient.from(client);
|
|
19
|
+
|
|
20
|
+
describe("Iterator Pagination Integration", () => {
|
|
21
|
+
const tableName = "TestTable_Pagination";
|
|
22
|
+
const table = dynamoTable(tableName, {
|
|
23
|
+
pk: string("pk"),
|
|
24
|
+
sk: string("sk"),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const user = dynamoEntity(
|
|
28
|
+
table,
|
|
29
|
+
"User",
|
|
30
|
+
{
|
|
31
|
+
id: uuid(),
|
|
32
|
+
data: string(),
|
|
33
|
+
},
|
|
34
|
+
(cols) => ({
|
|
35
|
+
pk: prefixKey("USER#", cols.id),
|
|
36
|
+
sk: staticKey("METADATA"),
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// 1MB is the limit. Each item is ~10KB. 120 items = 1.2MB
|
|
41
|
+
const itemCount = 120;
|
|
42
|
+
const largeData = "x".repeat(1024 * 10); // 10KB
|
|
43
|
+
|
|
44
|
+
beforeAll(async () => {
|
|
45
|
+
try {
|
|
46
|
+
await client.send(
|
|
47
|
+
new CreateTableCommand({
|
|
48
|
+
TableName: tableName,
|
|
49
|
+
KeySchema: [
|
|
50
|
+
{ AttributeName: "pk", KeyType: "HASH" },
|
|
51
|
+
{ AttributeName: "sk", KeyType: "RANGE" },
|
|
52
|
+
],
|
|
53
|
+
AttributeDefinitions: [
|
|
54
|
+
{ AttributeName: "pk", AttributeType: "S" },
|
|
55
|
+
{ AttributeName: "sk", AttributeType: "S" },
|
|
56
|
+
],
|
|
57
|
+
ProvisionedThroughput: { ReadCapacityUnits: 25, WriteCapacityUnits: 25 },
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Wait for table to be active
|
|
62
|
+
let active = false;
|
|
63
|
+
while (!active) {
|
|
64
|
+
const { Table } = await client.send(new DescribeTableCommand({ TableName: tableName }));
|
|
65
|
+
if (Table?.TableStatus === "ACTIVE") active = true;
|
|
66
|
+
else await new Promise((r) => setTimeout(r, 500));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Parallel insert to speed up
|
|
70
|
+
const puts = [];
|
|
71
|
+
for (let i = 0; i < itemCount; i++) {
|
|
72
|
+
puts.push(
|
|
73
|
+
docClient.send(
|
|
74
|
+
new PutCommand({
|
|
75
|
+
TableName: tableName,
|
|
76
|
+
Item: {
|
|
77
|
+
pk: `USER#${i}`,
|
|
78
|
+
sk: "METADATA",
|
|
79
|
+
id: `${i}`,
|
|
80
|
+
data: largeData,
|
|
81
|
+
},
|
|
82
|
+
}),
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
const results = await Promise.allSettled(puts);
|
|
87
|
+
const failed = results.filter((r) => r.status === "rejected");
|
|
88
|
+
if (failed.length > 0) {
|
|
89
|
+
console.error(`${failed.length} puts failed:`, failed[0]);
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {
|
|
92
|
+
console.error("Setup failed:", e);
|
|
93
|
+
}
|
|
94
|
+
}, 60000);
|
|
95
|
+
|
|
96
|
+
afterAll(async () => {
|
|
97
|
+
try {
|
|
98
|
+
await client.send(new DeleteTableCommand({ TableName: tableName }));
|
|
99
|
+
} catch {
|
|
100
|
+
/* ignore */
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should iterate through all items across multiple pages", async () => {
|
|
105
|
+
const iterator = mizzle(client).select().from(user).iterator();
|
|
106
|
+
let count = 0;
|
|
107
|
+
for await (const _ of iterator) {
|
|
108
|
+
count++;
|
|
109
|
+
}
|
|
110
|
+
expect(count).toBe(itemCount);
|
|
111
|
+
}, 20000);
|
|
112
|
+
|
|
113
|
+
it("should respect limit across multiple pages", async () => {
|
|
114
|
+
const limit = 15;
|
|
115
|
+
const iterator = mizzle(client).select().from(user).limit(limit).iterator();
|
|
116
|
+
let count = 0;
|
|
117
|
+
for await (const _ of iterator) {
|
|
118
|
+
count++;
|
|
119
|
+
}
|
|
120
|
+
expect(count).toBe(limit);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should respect pageSize hint and still fetch all", async () => {
|
|
124
|
+
// Small page size to force many requests
|
|
125
|
+
const iterator = mizzle(client).select().from(user).pageSize(10).iterator();
|
|
126
|
+
let count = 0;
|
|
127
|
+
for await (const _ of iterator) {
|
|
128
|
+
count++;
|
|
129
|
+
}
|
|
130
|
+
expect(count).toBe(itemCount);
|
|
131
|
+
}, 20000);
|
|
132
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
function checkJSDoc(filePath: string, methods: string[]) {
|
|
6
|
+
const content = readFileSync(filePath, "utf-8");
|
|
7
|
+
methods.forEach((method) => {
|
|
8
|
+
it(`should have JSDoc for method: ${method} in ${path.basename(filePath)}`, () => {
|
|
9
|
+
// Simpler escaping for common method names
|
|
10
|
+
const escapedMethod = method.replace(/[.*+?^${}()|[\\]/g, "\\$&");
|
|
11
|
+
// Match JSDoc optionally followed by modifiers and the method name
|
|
12
|
+
const regex = new RegExp(
|
|
13
|
+
`\\/\\*[\\s\\S]*?\\*\\/\\s*(?:override\\s+|async\\s+|public\\s+|private\\s+|static\\s+)*${escapedMethod}\\b`,
|
|
14
|
+
);
|
|
15
|
+
const hasMatch = regex.test(content);
|
|
16
|
+
expect(hasMatch, `Method ${method} is missing JSDoc in ${filePath}`).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("JSDoc Presence for Builders", () => {
|
|
22
|
+
describe("SelectBuilder", () => {
|
|
23
|
+
const filePath = path.resolve(__dirname, "../src/builders/select.ts");
|
|
24
|
+
const methods = [
|
|
25
|
+
"from",
|
|
26
|
+
"where",
|
|
27
|
+
"limit",
|
|
28
|
+
"pageSize",
|
|
29
|
+
"consistentRead",
|
|
30
|
+
"sort",
|
|
31
|
+
"index",
|
|
32
|
+
"iterator",
|
|
33
|
+
"execute",
|
|
34
|
+
];
|
|
35
|
+
checkJSDoc(filePath, methods);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("InsertBuilder", () => {
|
|
39
|
+
const filePath = path.resolve(__dirname, "../src/builders/insert.ts");
|
|
40
|
+
const methods = ["values", "returning", "execute"];
|
|
41
|
+
checkJSDoc(filePath, methods);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("UpdateBuilder", () => {
|
|
45
|
+
const filePath = path.resolve(__dirname, "../src/builders/update.ts");
|
|
46
|
+
const methods = ["key", "set", "add", "remove", "delete", "where", "returning", "execute"];
|
|
47
|
+
checkJSDoc(filePath, methods);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("DeleteBuilder", () => {
|
|
51
|
+
const filePath = path.resolve(__dirname, "../src/builders/delete.ts");
|
|
52
|
+
const methods = ["returning", "execute"];
|
|
53
|
+
checkJSDoc(filePath, methods);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
describe("JSDoc Presence for Schema Definition", () => {
|
|
6
|
+
describe("Core Schema Functions", () => {
|
|
7
|
+
const filePath = path.resolve(__dirname, "../src/core/table.ts");
|
|
8
|
+
const content = readFileSync(filePath, "utf-8");
|
|
9
|
+
const methods = ["dynamoTable", "dynamoEntity"];
|
|
10
|
+
|
|
11
|
+
methods.forEach((method) => {
|
|
12
|
+
it(`should have JSDoc for export: ${method}`, () => {
|
|
13
|
+
const escapedMethod = method.replace(/[.*+?^${}()|[\\]/g, "\\$&");
|
|
14
|
+
const regex = new RegExp(
|
|
15
|
+
`\\/\\*\\*[\\s\\S]*?\\*\\/\\s*(?:export\\s+)?(?:function\\s+)?${escapedMethod}\\b`,
|
|
16
|
+
);
|
|
17
|
+
const hasMatch = regex.test(content);
|
|
18
|
+
expect(hasMatch, `Export ${method} is missing JSDoc in ${filePath}`).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("Initialization", () => {
|
|
24
|
+
const filePath = path.resolve(__dirname, "../src/db.ts");
|
|
25
|
+
const content = readFileSync(filePath, "utf-8");
|
|
26
|
+
const methods = ["mizzle"];
|
|
27
|
+
|
|
28
|
+
methods.forEach((method) => {
|
|
29
|
+
it(`should have JSDoc for export: ${method}`, () => {
|
|
30
|
+
const escapedMethod = method.replace(/[.*+?^${}()|[\\]/g, "\\$&");
|
|
31
|
+
const regex = new RegExp(
|
|
32
|
+
`\\/\\*\\*[\\s\\S]*?\\*\\/\\s*(?:export\\s+)?(?:function\\s+)?${escapedMethod}\\b`,
|
|
33
|
+
);
|
|
34
|
+
const hasMatch = regex.test(content);
|
|
35
|
+
expect(hasMatch, `Export ${method} is missing JSDoc in ${filePath}`).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("CLI Config", () => {
|
|
41
|
+
const filePath = path.resolve(__dirname, "../../mizzling/src/config.ts");
|
|
42
|
+
const content = readFileSync(filePath, "utf-8");
|
|
43
|
+
const methods = ["defineConfig"];
|
|
44
|
+
|
|
45
|
+
methods.forEach((method) => {
|
|
46
|
+
it(`should have JSDoc for export: ${method}`, () => {
|
|
47
|
+
const escapedMethod = method.replace(/[.*+?^${}()|[\\]/g, "\\$&");
|
|
48
|
+
const regex = new RegExp(
|
|
49
|
+
`\\/\\*\\*[\\s\\S]*?\\*\\/\\s*(?:export\\s+)?(?:function\\s+)?${escapedMethod}\\b`,
|
|
50
|
+
);
|
|
51
|
+
const hasMatch = regex.test(content);
|
|
52
|
+
expect(hasMatch, `Export ${method} is missing JSDoc in ${filePath}`).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("Relations", () => {
|
|
58
|
+
const filePath = path.resolve(__dirname, "../src/core/relations.ts");
|
|
59
|
+
const content = readFileSync(filePath, "utf-8");
|
|
60
|
+
const methods = ["defineRelations"];
|
|
61
|
+
|
|
62
|
+
methods.forEach((method) => {
|
|
63
|
+
it(`should have JSDoc for export: ${method}`, () => {
|
|
64
|
+
const escapedMethod = method.replace(/[.*+?^${}()|[\\]/g, "\\$&");
|
|
65
|
+
const regex = new RegExp(
|
|
66
|
+
`\\/\\*\\*[\\s\\S]*?\\*\\/\\s*(?:export\\s+)?(?:function\\s+)?${escapedMethod}\\b`,
|
|
67
|
+
);
|
|
68
|
+
const hasMatch = regex.test(content);
|
|
69
|
+
expect(hasMatch, `Export ${method} is missing JSDoc in ${filePath}`).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("Column Builders", () => {
|
|
75
|
+
const columnFiles = [
|
|
76
|
+
"string.ts",
|
|
77
|
+
"number.ts",
|
|
78
|
+
"boolean.ts",
|
|
79
|
+
"date.ts",
|
|
80
|
+
"uuid.ts",
|
|
81
|
+
"json.ts",
|
|
82
|
+
"binary.ts",
|
|
83
|
+
"list.ts",
|
|
84
|
+
"map.ts",
|
|
85
|
+
"string-set.ts",
|
|
86
|
+
"number-set.ts",
|
|
87
|
+
"binary-set.ts",
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
columnFiles.forEach((file) => {
|
|
91
|
+
const builderName = file
|
|
92
|
+
.replace(".ts", "")
|
|
93
|
+
.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()); // camelCase
|
|
94
|
+
|
|
95
|
+
it(`should have JSDoc for column builder: ${builderName} in ${file}`, () => {
|
|
96
|
+
const filePath = path.resolve(__dirname, `../src/columns/${file}`);
|
|
97
|
+
const content = readFileSync(filePath, "utf-8");
|
|
98
|
+
const escapedMethod = builderName.replace(/[.*+?^${}()|[\\]/g, "\\$&");
|
|
99
|
+
const regex = new RegExp(
|
|
100
|
+
`\\/\\*\\*[\\s\\S]*?\\*\\/\\s*(?:export\\s+)?(?:function\\s+)?${escapedMethod}\\b`,
|
|
101
|
+
);
|
|
102
|
+
const hasMatch = regex.test(content);
|
|
103
|
+
expect(hasMatch, `Export ${builderName} is missing JSDoc in ${filePath}`).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { json } from "@aurios/mizzle/columns";
|
|
3
|
+
import { dynamoTable, dynamoEntity } from "@aurios/mizzle/table";
|
|
4
|
+
|
|
5
|
+
describe("json column", () => {
|
|
6
|
+
it("should define a json column", () => {
|
|
7
|
+
const col = json("data");
|
|
8
|
+
expect(col.config.name).toBe("data");
|
|
9
|
+
expect(col.config.dataType).toBe("json");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should serialize objects to strings for DynamoDB", () => {
|
|
13
|
+
const table = dynamoTable("test", {
|
|
14
|
+
pk: json("pk").partitionKey(),
|
|
15
|
+
});
|
|
16
|
+
const entity = dynamoEntity(table, "test", {
|
|
17
|
+
pk: json("pk"),
|
|
18
|
+
});
|
|
19
|
+
const col = entity.pk;
|
|
20
|
+
const data = { hello: "world", numbers: [1, 2, 3] };
|
|
21
|
+
|
|
22
|
+
expect(col.mapToDynamoValue(data)).toBe(JSON.stringify(data));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should deserialize strings from DynamoDB back to objects", () => {
|
|
26
|
+
const table = dynamoTable("test", {
|
|
27
|
+
pk: json("pk").partitionKey(),
|
|
28
|
+
});
|
|
29
|
+
const entity = dynamoEntity(table, "test", {
|
|
30
|
+
pk: json("pk"),
|
|
31
|
+
});
|
|
32
|
+
const col = entity.pk;
|
|
33
|
+
const data = { hello: "world" };
|
|
34
|
+
const serialized = JSON.stringify(data);
|
|
35
|
+
|
|
36
|
+
expect(col.mapFromDynamoValue(serialized)).toEqual(data);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should return raw value if parsing fails", () => {
|
|
40
|
+
const table = dynamoTable("test", {
|
|
41
|
+
pk: json("pk").partitionKey(),
|
|
42
|
+
});
|
|
43
|
+
const entity = dynamoEntity(table, "test", {
|
|
44
|
+
pk: json("pk"),
|
|
45
|
+
});
|
|
46
|
+
const col = entity.pk;
|
|
47
|
+
const invalidJson = "{ invalid }";
|
|
48
|
+
|
|
49
|
+
expect(col.mapFromDynamoValue(invalidJson as unknown)).toBe(invalidJson);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { dynamoTable, dynamoEntity } from "@aurios/mizzle/table";
|
|
3
|
+
import { defineRelations, extractMetadata } from "@aurios/mizzle";
|
|
4
|
+
import { string, uuid } from "@aurios/mizzle/columns";
|
|
5
|
+
|
|
6
|
+
const table = dynamoTable("mizzle-test", {
|
|
7
|
+
pk: string("pk"),
|
|
8
|
+
sk: string("sk"),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const users = dynamoEntity(table, "users", {
|
|
12
|
+
id: uuid("id"),
|
|
13
|
+
name: string("name"),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const posts = dynamoEntity(table, "posts", {
|
|
17
|
+
id: uuid("id"),
|
|
18
|
+
userId: uuid("userId"),
|
|
19
|
+
content: string("content"),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const usersRelations = defineRelations(users, ({ many }) => ({
|
|
23
|
+
posts: many(posts),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
const postsRelations = defineRelations(posts, ({ one }) => ({
|
|
27
|
+
author: one(users, {
|
|
28
|
+
fields: [posts.userId],
|
|
29
|
+
references: [users.id],
|
|
30
|
+
}),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
describe("extractMetadata", () => {
|
|
34
|
+
it("should correctly extract metadata from relations definition", () => {
|
|
35
|
+
const schema = {
|
|
36
|
+
users,
|
|
37
|
+
posts,
|
|
38
|
+
usersRelations,
|
|
39
|
+
postsRelations,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const metadata = extractMetadata(schema);
|
|
43
|
+
|
|
44
|
+
expect(metadata.entities.users).toBeDefined();
|
|
45
|
+
expect(metadata.entities.users!.entity).toBe(users);
|
|
46
|
+
expect(metadata.entities.users!.relations.posts).toBeDefined();
|
|
47
|
+
expect(metadata.entities.users!.relations.posts!.type).toBe("many");
|
|
48
|
+
|
|
49
|
+
expect(metadata.entities.posts).toBeDefined();
|
|
50
|
+
expect(metadata.entities.posts!.entity).toBe(posts);
|
|
51
|
+
expect(metadata.entities.posts!.relations.author).toBeDefined();
|
|
52
|
+
expect(metadata.entities.posts!.relations.author!.type).toBe("one");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
describe("Mizzle Core Package", () => {
|
|
6
|
+
const mizzlePath = process.cwd().includes("packages/mizzle")
|
|
7
|
+
? process.cwd()
|
|
8
|
+
: join(process.cwd(), "packages", "mizzle");
|
|
9
|
+
|
|
10
|
+
it("should have a package.json", () => {
|
|
11
|
+
expect(existsSync(join(mizzlePath, "package.json"))).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should have migrated core directories", () => {
|
|
15
|
+
expect(existsSync(join(mizzlePath, "src", "builders"))).toBe(true);
|
|
16
|
+
expect(existsSync(join(mizzlePath, "src", "columns"))).toBe(true);
|
|
17
|
+
expect(existsSync(join(mizzlePath, "src", "expressions"))).toBe(true);
|
|
18
|
+
expect(existsSync(join(mizzlePath, "src", "core"))).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { dynamoTable, dynamoEntity } from "@aurios/mizzle/core/table";
|
|
3
|
+
import { defineRelations, extractMetadata } from "@aurios/mizzle/core/relations";
|
|
4
|
+
import { string, uuid } from "@aurios/mizzle/columns";
|
|
5
|
+
|
|
6
|
+
describe("Centralized defineRelations", () => {
|
|
7
|
+
const table = dynamoTable("mizzle-test", {
|
|
8
|
+
pk: string("pk"),
|
|
9
|
+
sk: string("sk"),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const users = dynamoEntity(table, "users", {
|
|
13
|
+
id: uuid("id"),
|
|
14
|
+
name: string("name"),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const posts = dynamoEntity(table, "posts", {
|
|
18
|
+
id: uuid("id"),
|
|
19
|
+
authorId: uuid("authorId"),
|
|
20
|
+
content: string("content"),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should allow defining relations for multiple entities at once", () => {
|
|
24
|
+
const relations = defineRelations({ users, posts }, (r) => ({
|
|
25
|
+
users: {
|
|
26
|
+
posts: r.many.posts(),
|
|
27
|
+
},
|
|
28
|
+
posts: {
|
|
29
|
+
author: r.one.users({
|
|
30
|
+
fields: [r.posts.authorId],
|
|
31
|
+
references: [r.users.id],
|
|
32
|
+
}),
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
expect(relations).toBeDefined();
|
|
37
|
+
const defs = (relations as any).definitions;
|
|
38
|
+
expect(defs.users.posts.type).toBe("many");
|
|
39
|
+
expect(defs.posts.author.type).toBe("one");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should correctly extract metadata from centralized relations", () => {
|
|
43
|
+
const relations = defineRelations({ users, posts }, (r) => ({
|
|
44
|
+
users: {
|
|
45
|
+
posts: r.many.posts(),
|
|
46
|
+
},
|
|
47
|
+
posts: {
|
|
48
|
+
author: r.one.users({
|
|
49
|
+
fields: [r.posts.authorId],
|
|
50
|
+
references: [r.users.id],
|
|
51
|
+
}),
|
|
52
|
+
},
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
const schema = { users, posts, relations };
|
|
56
|
+
const metadata = extractMetadata(schema);
|
|
57
|
+
|
|
58
|
+
expect(metadata.entities.users).toBeDefined();
|
|
59
|
+
expect(metadata.entities.users!.relations.posts).toBeDefined();
|
|
60
|
+
expect(metadata.entities.users!.relations.posts!.type).toBe("many");
|
|
61
|
+
expect(metadata.entities.posts).toBeDefined();
|
|
62
|
+
expect(metadata.entities.posts!.relations.author).toBeDefined();
|
|
63
|
+
expect(metadata.entities.posts!.relations.author!.type).toBe("one");
|
|
64
|
+
|
|
65
|
+
// Check column references
|
|
66
|
+
const authorRelation = metadata.entities.posts!.relations.author;
|
|
67
|
+
expect(authorRelation!.config.fields?.[0]).toBe(posts.authorId);
|
|
68
|
+
expect(authorRelation!.config.references?.[0]).toBe(users.id);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should maintain backward compatibility with single-entity defineRelations", () => {
|
|
72
|
+
const usersRelations = defineRelations(users, ({ many }) => ({
|
|
73
|
+
posts: many(posts),
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
const schema = { users, posts, usersRelations };
|
|
77
|
+
const metadata = extractMetadata(schema);
|
|
78
|
+
|
|
79
|
+
expect(metadata.entities.users).toBeDefined();
|
|
80
|
+
expect(metadata.entities.users!.relations.posts).toBeDefined();
|
|
81
|
+
expect(metadata.entities.users!.relations.posts!.type).toBe("many");
|
|
82
|
+
});
|
|
83
|
+
});
|