jotdb 0.1.2 → 0.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/bun.lock +1 -3
- package/dist/index.d.ts +6 -5
- package/dist/index.js +60 -57
- package/package.json +8 -8
- package/src/index.ts +120 -70
- package/jotdb.tests.ts +0 -63
package/bun.lock
CHANGED
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
"workspaces": {
|
|
4
4
|
"": {
|
|
5
5
|
"name": "jotdb",
|
|
6
|
-
"dependencies": {
|
|
7
|
-
"typescript": "^5.8.3",
|
|
8
|
-
},
|
|
9
6
|
"devDependencies": {
|
|
10
7
|
"@cloudflare/workers-types": "^4.20250517.0",
|
|
11
8
|
"hono": "^4.7.10",
|
|
9
|
+
"typescript": "^5.8.3",
|
|
12
10
|
"wrangler": "^4.15.2",
|
|
13
11
|
"zod": "^3.24.4",
|
|
14
12
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { DurableObjectState } from "@cloudflare/workers-types";
|
|
2
1
|
import { Hono } from 'hono';
|
|
3
2
|
import { DurableObject } from "cloudflare:workers";
|
|
4
3
|
type SchemaType = "string" | "number" | "boolean" | "email" | "array" | "object" | "any";
|
|
@@ -18,14 +17,15 @@ export declare class JotDB extends DurableObject {
|
|
|
18
17
|
private zodSchema;
|
|
19
18
|
private options;
|
|
20
19
|
private auditLog;
|
|
21
|
-
constructor(state:
|
|
20
|
+
constructor(state: any, env: Env);
|
|
22
21
|
load(): Promise<void>;
|
|
23
22
|
save(): Promise<void>;
|
|
23
|
+
isArrayMode(): boolean;
|
|
24
|
+
push(item: unknown): Promise<void>;
|
|
25
|
+
setAll(objOrArr: Record<string, unknown> | unknown[]): Promise<void>;
|
|
26
|
+
getAll(): Promise<unknown>;
|
|
24
27
|
logAudit(action: string, keys: string[] | string): Promise<void>;
|
|
25
28
|
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
26
|
-
getAll(): Promise<Record<string, unknown>>;
|
|
27
|
-
set<T>(key: string, value: T): Promise<void>;
|
|
28
|
-
setAll(obj: Record<string, unknown>): Promise<void>;
|
|
29
29
|
delete(key: string): Promise<void>;
|
|
30
30
|
clear(): Promise<void>;
|
|
31
31
|
keys(): Promise<string[]>;
|
|
@@ -39,6 +39,7 @@ export declare class JotDB extends DurableObject {
|
|
|
39
39
|
clearAuditLog(): Promise<void>;
|
|
40
40
|
private buildZodSchema;
|
|
41
41
|
fetch(request: Request): Promise<Response>;
|
|
42
|
+
set(key: string, value: unknown): Promise<void>;
|
|
42
43
|
}
|
|
43
44
|
export interface Env {
|
|
44
45
|
JOTDB: DurableObjectNamespace;
|
package/dist/index.js
CHANGED
|
@@ -16,8 +16,8 @@ export class JotDB extends DurableObject {
|
|
|
16
16
|
this.auditLog = [];
|
|
17
17
|
}
|
|
18
18
|
async load() {
|
|
19
|
-
if (Object.keys(this.data).length === 0) {
|
|
20
|
-
this.data = (await this.ctx.storage.get("data"))
|
|
19
|
+
if (this.data == null || (typeof this.data === 'object' && Object.keys(this.data).length === 0)) {
|
|
20
|
+
this.data = (await this.ctx.storage.get("data")) ?? {};
|
|
21
21
|
}
|
|
22
22
|
if (Object.keys(this.rawSchema).length === 0) {
|
|
23
23
|
this.rawSchema = (await this.ctx.storage.get("__schema__")) || {};
|
|
@@ -33,6 +33,31 @@ export class JotDB extends DurableObject {
|
|
|
33
33
|
async save() {
|
|
34
34
|
await this.ctx.storage.put("data", this.data);
|
|
35
35
|
}
|
|
36
|
+
isArrayMode() {
|
|
37
|
+
return Array.isArray(this.data);
|
|
38
|
+
}
|
|
39
|
+
async push(item) {
|
|
40
|
+
await this.load();
|
|
41
|
+
if (!Array.isArray(this.data)) {
|
|
42
|
+
this.data = [];
|
|
43
|
+
}
|
|
44
|
+
this.data.push(item);
|
|
45
|
+
await this.save();
|
|
46
|
+
await this.logAudit("push", []);
|
|
47
|
+
}
|
|
48
|
+
async setAll(objOrArr) {
|
|
49
|
+
await this.load();
|
|
50
|
+
if (!Array.isArray(objOrArr) && this.zodSchema) {
|
|
51
|
+
objOrArr = this.zodSchema.parse(objOrArr);
|
|
52
|
+
}
|
|
53
|
+
this.data = objOrArr;
|
|
54
|
+
await this.save();
|
|
55
|
+
await this.logAudit("setAll", Array.isArray(objOrArr) ? [] : Object.keys(objOrArr));
|
|
56
|
+
}
|
|
57
|
+
async getAll() {
|
|
58
|
+
await this.load();
|
|
59
|
+
return this.data;
|
|
60
|
+
}
|
|
36
61
|
async logAudit(action, keys) {
|
|
37
62
|
const entry = {
|
|
38
63
|
timestamp: Date.now(),
|
|
@@ -44,63 +69,18 @@ export class JotDB extends DurableObject {
|
|
|
44
69
|
}
|
|
45
70
|
async get(key) {
|
|
46
71
|
await this.load();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
async getAll() {
|
|
50
|
-
await this.load();
|
|
51
|
-
return this.data;
|
|
52
|
-
}
|
|
53
|
-
async set(key, value) {
|
|
54
|
-
await this.load();
|
|
55
|
-
if (this.options.readOnly)
|
|
56
|
-
throw new Error("JotDB is in read-only mode");
|
|
57
|
-
if (this.zodSchema) {
|
|
58
|
-
const partialSchema = this.zodSchema.pick({ [key]: true });
|
|
59
|
-
partialSchema.parse({ [key]: value });
|
|
60
|
-
}
|
|
61
|
-
this.data[key] = value;
|
|
62
|
-
await this.save();
|
|
63
|
-
await this.logAudit("set", key);
|
|
64
|
-
}
|
|
65
|
-
async setAll(obj) {
|
|
66
|
-
await this.load();
|
|
67
|
-
if (this.options.readOnly)
|
|
68
|
-
throw new Error("JotDB is in read-only mode");
|
|
69
|
-
const typeOfValue = (v) => {
|
|
70
|
-
if (Array.isArray(v))
|
|
71
|
-
return "array";
|
|
72
|
-
switch (typeof v) {
|
|
73
|
-
case "string": return "string";
|
|
74
|
-
case "number": return "number";
|
|
75
|
-
case "boolean": return "boolean";
|
|
76
|
-
case "object": return "object";
|
|
77
|
-
default: return "any";
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
if (!this.zodSchema) {
|
|
81
|
-
const inferred = {};
|
|
82
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
83
|
-
inferred[k] = typeOfValue(v);
|
|
84
|
-
}
|
|
85
|
-
await this.setSchema(inferred);
|
|
72
|
+
if (!Array.isArray(this.data)) {
|
|
73
|
+
return this.data[key];
|
|
86
74
|
}
|
|
87
|
-
|
|
88
|
-
if (this.options.autoStrip) {
|
|
89
|
-
obj = this.zodSchema.parse(obj); // returns stripped
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
this.zodSchema.parse(obj); // strict match
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
Object.assign(this.data, obj);
|
|
96
|
-
await this.save();
|
|
97
|
-
await this.logAudit("setAll", Object.keys(obj));
|
|
75
|
+
return undefined;
|
|
98
76
|
}
|
|
99
77
|
async delete(key) {
|
|
100
78
|
await this.load();
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
79
|
+
if (!Array.isArray(this.data)) {
|
|
80
|
+
delete this.data[key];
|
|
81
|
+
await this.save();
|
|
82
|
+
await this.logAudit("delete", key);
|
|
83
|
+
}
|
|
104
84
|
}
|
|
105
85
|
async clear() {
|
|
106
86
|
this.data = {};
|
|
@@ -109,11 +89,17 @@ export class JotDB extends DurableObject {
|
|
|
109
89
|
}
|
|
110
90
|
async keys() {
|
|
111
91
|
await this.load();
|
|
112
|
-
|
|
92
|
+
if (!Array.isArray(this.data)) {
|
|
93
|
+
return Object.keys(this.data);
|
|
94
|
+
}
|
|
95
|
+
return [];
|
|
113
96
|
}
|
|
114
97
|
async has(key) {
|
|
115
98
|
await this.load();
|
|
116
|
-
|
|
99
|
+
if (!Array.isArray(this.data)) {
|
|
100
|
+
return key in this.data;
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
117
103
|
}
|
|
118
104
|
async getSchema() {
|
|
119
105
|
await this.load();
|
|
@@ -191,6 +177,23 @@ export class JotDB extends DurableObject {
|
|
|
191
177
|
async fetch(request) {
|
|
192
178
|
return new Response("Hello, World!");
|
|
193
179
|
}
|
|
180
|
+
async set(key, value) {
|
|
181
|
+
await this.load();
|
|
182
|
+
if (this.options.readOnly) {
|
|
183
|
+
throw new Error("Database is in read-only mode");
|
|
184
|
+
}
|
|
185
|
+
if (!Array.isArray(this.data)) {
|
|
186
|
+
this.data[key] = value;
|
|
187
|
+
if (this.zodSchema) {
|
|
188
|
+
this.zodSchema.parse(this.data);
|
|
189
|
+
}
|
|
190
|
+
await this.save();
|
|
191
|
+
await this.logAudit("set", key);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
throw new Error("Cannot use set() in array mode");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
194
197
|
}
|
|
195
198
|
const app = new Hono();
|
|
196
199
|
// Middleware
|
package/package.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jotdb",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"exports": {
|
|
7
|
-
".":
|
|
7
|
+
".": {
|
|
8
|
+
"import": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts"
|
|
10
|
+
}
|
|
8
11
|
},
|
|
9
12
|
"types": "dist/index.d.ts",
|
|
10
13
|
"scripts": {
|
|
11
|
-
"dev": "wrangler dev --port 5173
|
|
12
|
-
"test": "bun test jotdb.tests.ts",
|
|
14
|
+
"dev": "wrangler dev --port 5173",
|
|
13
15
|
"deploy": "wrangler deploy",
|
|
14
16
|
"patch": "npm version patch && npm publish --access public"
|
|
15
17
|
},
|
|
16
|
-
"dependencies": {
|
|
17
|
-
"typescript": "^5.8.3"
|
|
18
|
-
},
|
|
19
18
|
"devDependencies": {
|
|
19
|
+
"typescript": "^5.8.3",
|
|
20
20
|
"@cloudflare/workers-types": "^4.20250517.0",
|
|
21
21
|
"hono": "^4.7.10",
|
|
22
22
|
"wrangler": "^4.15.2",
|
|
@@ -26,4 +26,4 @@
|
|
|
26
26
|
"type": "git",
|
|
27
27
|
"url": "https://github.com/acoyfellow/jotdb.git"
|
|
28
28
|
}
|
|
29
|
-
}
|
|
29
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -20,7 +20,7 @@ interface AuditLogEntry {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export class JotDB extends DurableObject {
|
|
23
|
-
private data: Record<string, unknown> = {};
|
|
23
|
+
private data: Record<string, unknown> | unknown[] = {};
|
|
24
24
|
private rawSchema: SchemaDefinition = {};
|
|
25
25
|
private zodSchema: ZodObject<any> | null = null;
|
|
26
26
|
private options: JotDBOptions = {
|
|
@@ -34,8 +34,8 @@ export class JotDB extends DurableObject {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
async load(): Promise<void> {
|
|
37
|
-
if (Object.keys(this.data).length === 0) {
|
|
38
|
-
this.data = (await this.ctx.storage.get("data"))
|
|
37
|
+
if (this.data == null || (typeof this.data === 'object' && Object.keys(this.data).length === 0)) {
|
|
38
|
+
this.data = (await this.ctx.storage.get("data")) ?? {};
|
|
39
39
|
}
|
|
40
40
|
if (Object.keys(this.rawSchema).length === 0) {
|
|
41
41
|
this.rawSchema = (await this.ctx.storage.get("__schema__")) || {};
|
|
@@ -45,7 +45,6 @@ export class JotDB extends DurableObject {
|
|
|
45
45
|
}
|
|
46
46
|
const storedOptions = await this.ctx.storage.get("__options__") as JotDBOptions | null;
|
|
47
47
|
if (storedOptions) this.options = storedOptions;
|
|
48
|
-
|
|
49
48
|
this.auditLog = (await this.ctx.storage.get("__audit__")) || [];
|
|
50
49
|
}
|
|
51
50
|
|
|
@@ -53,80 +52,60 @@ export class JotDB extends DurableObject {
|
|
|
53
52
|
await this.ctx.storage.put("data", this.data);
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
timestamp: Date.now(),
|
|
59
|
-
action,
|
|
60
|
-
keys: Array.isArray(keys) ? keys : [keys],
|
|
61
|
-
};
|
|
62
|
-
this.auditLog.unshift(entry);
|
|
63
|
-
await this.ctx.storage.put("__audit__", this.auditLog.slice(0, 100)); // keep max 100 entries
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async get<T = unknown>(key: string): Promise<T | undefined> {
|
|
67
|
-
await this.load();
|
|
68
|
-
return this.data[key] as T;
|
|
55
|
+
isArrayMode(): boolean {
|
|
56
|
+
return Array.isArray(this.data);
|
|
69
57
|
}
|
|
70
58
|
|
|
71
|
-
async
|
|
59
|
+
async push(item: unknown): Promise<void> {
|
|
72
60
|
await this.load();
|
|
73
|
-
|
|
61
|
+
if (!Array.isArray(this.data)) {
|
|
62
|
+
this.data = [];
|
|
63
|
+
}
|
|
64
|
+
(this.data as unknown[]).push(item);
|
|
65
|
+
await this.save();
|
|
66
|
+
await this.logAudit("push", []);
|
|
74
67
|
}
|
|
75
68
|
|
|
76
|
-
async
|
|
69
|
+
async setAll(objOrArr: Record<string, unknown> | unknown[]): Promise<void> {
|
|
77
70
|
await this.load();
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
if (this.zodSchema) {
|
|
81
|
-
const partialSchema = this.zodSchema.pick({ [key]: true });
|
|
82
|
-
partialSchema.parse({ [key]: value });
|
|
71
|
+
if (!Array.isArray(objOrArr) && this.zodSchema) {
|
|
72
|
+
objOrArr = this.zodSchema.parse(objOrArr);
|
|
83
73
|
}
|
|
84
|
-
this.data
|
|
74
|
+
this.data = objOrArr;
|
|
85
75
|
await this.save();
|
|
86
|
-
await this.logAudit("
|
|
76
|
+
await this.logAudit("setAll", Array.isArray(objOrArr) ? [] : Object.keys(objOrArr));
|
|
87
77
|
}
|
|
88
78
|
|
|
89
|
-
async
|
|
79
|
+
async getAll(): Promise<unknown> {
|
|
90
80
|
await this.load();
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const typeOfValue = (v: any): string => {
|
|
94
|
-
if (Array.isArray(v)) return "array";
|
|
95
|
-
switch (typeof v) {
|
|
96
|
-
case "string": return "string";
|
|
97
|
-
case "number": return "number";
|
|
98
|
-
case "boolean": return "boolean";
|
|
99
|
-
case "object": return "object";
|
|
100
|
-
default: return "any";
|
|
101
|
-
}
|
|
102
|
-
};
|
|
81
|
+
return this.data;
|
|
82
|
+
}
|
|
103
83
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
84
|
+
async logAudit(action: string, keys: string[] | string): Promise<void> {
|
|
85
|
+
const entry: AuditLogEntry = {
|
|
86
|
+
timestamp: Date.now(),
|
|
87
|
+
action,
|
|
88
|
+
keys: Array.isArray(keys) ? keys : [keys],
|
|
89
|
+
};
|
|
90
|
+
this.auditLog.unshift(entry);
|
|
91
|
+
await this.ctx.storage.put("__audit__", this.auditLog.slice(0, 100)); // keep max 100 entries
|
|
92
|
+
}
|
|
111
93
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
this.zodSchema.parse(obj); // strict match
|
|
117
|
-
}
|
|
94
|
+
async get<T = unknown>(key: string): Promise<T | undefined> {
|
|
95
|
+
await this.load();
|
|
96
|
+
if (!Array.isArray(this.data)) {
|
|
97
|
+
return this.data[key] as T;
|
|
118
98
|
}
|
|
119
|
-
|
|
120
|
-
Object.assign(this.data, obj);
|
|
121
|
-
await this.save();
|
|
122
|
-
await this.logAudit("setAll", Object.keys(obj));
|
|
99
|
+
return undefined;
|
|
123
100
|
}
|
|
124
101
|
|
|
125
102
|
async delete(key: string): Promise<void> {
|
|
126
103
|
await this.load();
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
104
|
+
if (!Array.isArray(this.data)) {
|
|
105
|
+
delete this.data[key];
|
|
106
|
+
await this.save();
|
|
107
|
+
await this.logAudit("delete", key);
|
|
108
|
+
}
|
|
130
109
|
}
|
|
131
110
|
|
|
132
111
|
async clear(): Promise<void> {
|
|
@@ -137,12 +116,18 @@ export class JotDB extends DurableObject {
|
|
|
137
116
|
|
|
138
117
|
async keys(): Promise<string[]> {
|
|
139
118
|
await this.load();
|
|
140
|
-
|
|
119
|
+
if (!Array.isArray(this.data)) {
|
|
120
|
+
return Object.keys(this.data);
|
|
121
|
+
}
|
|
122
|
+
return [];
|
|
141
123
|
}
|
|
142
124
|
|
|
143
125
|
async has(key: string): Promise<boolean> {
|
|
144
126
|
await this.load();
|
|
145
|
-
|
|
127
|
+
if (!Array.isArray(this.data)) {
|
|
128
|
+
return key in this.data;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
146
131
|
}
|
|
147
132
|
|
|
148
133
|
async getSchema(): Promise<SchemaDefinition> {
|
|
@@ -230,6 +215,23 @@ export class JotDB extends DurableObject {
|
|
|
230
215
|
async fetch(request: Request) {
|
|
231
216
|
return new Response("Hello, World!");
|
|
232
217
|
}
|
|
218
|
+
|
|
219
|
+
async set(key: string, value: unknown): Promise<void> {
|
|
220
|
+
await this.load();
|
|
221
|
+
if (this.options.readOnly) {
|
|
222
|
+
throw new Error("Database is in read-only mode");
|
|
223
|
+
}
|
|
224
|
+
if (!Array.isArray(this.data)) {
|
|
225
|
+
this.data[key] = value;
|
|
226
|
+
if (this.zodSchema) {
|
|
227
|
+
this.zodSchema.parse(this.data);
|
|
228
|
+
}
|
|
229
|
+
await this.save();
|
|
230
|
+
await this.logAudit("set", key);
|
|
231
|
+
} else {
|
|
232
|
+
throw new Error("Cannot use set() in array mode");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
233
235
|
}
|
|
234
236
|
|
|
235
237
|
export interface Env {
|
|
@@ -274,7 +276,7 @@ app.get('/test', async (c) => {
|
|
|
274
276
|
age: 30,
|
|
275
277
|
email: "john@example.com"
|
|
276
278
|
});
|
|
277
|
-
const all = await db.getAll();
|
|
279
|
+
const all = await db.getAll() as { name: string, age: number, email: string };
|
|
278
280
|
results.tests.push({
|
|
279
281
|
name: "Schema validation",
|
|
280
282
|
passed: all.name === "John" && all.age === 30,
|
|
@@ -306,23 +308,71 @@ app.get('/test', async (c) => {
|
|
|
306
308
|
email: "jane@example.com",
|
|
307
309
|
extra: "should be stripped"
|
|
308
310
|
});
|
|
309
|
-
const stripped = await db.getAll();
|
|
311
|
+
const stripped = await db.getAll() as { name: string, age: number, email: string };
|
|
310
312
|
results.tests.push({
|
|
311
313
|
name: "Auto-strip mode",
|
|
312
314
|
passed: !("extra" in stripped),
|
|
313
315
|
value: stripped
|
|
314
316
|
});
|
|
315
317
|
|
|
318
|
+
// Test 5: Array mode - setAll and getAll
|
|
319
|
+
const arrayId = c.env.JOTDB.idFromName("test-array");
|
|
320
|
+
const arrayDb = c.env.JOTDB.get(arrayId) as unknown as JotDB;
|
|
321
|
+
await arrayDb.setAll([1, 2, 3]);
|
|
322
|
+
const arr = await arrayDb.getAll();
|
|
323
|
+
results.tests.push({
|
|
324
|
+
name: "Array mode setAll/getAll",
|
|
325
|
+
passed: Array.isArray(arr) && arr.length === 3 && arr[0] === 1 && arr[2] === 3,
|
|
326
|
+
value: arr
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Test 6: Array mode - push
|
|
330
|
+
await arrayDb.push(4);
|
|
331
|
+
const arr2 = await arrayDb.getAll();
|
|
332
|
+
results.tests.push({
|
|
333
|
+
name: "Array mode push",
|
|
334
|
+
passed: Array.isArray(arr2) && arr2.length === 4 && arr2[3] === 4,
|
|
335
|
+
value: arr2
|
|
336
|
+
});
|
|
337
|
+
|
|
316
338
|
// Get audit log
|
|
317
339
|
results.auditLog = await db.getAuditLog();
|
|
318
340
|
|
|
319
|
-
|
|
341
|
+
// HTML output
|
|
342
|
+
let html = `<!DOCTYPE html><html><head><title>JotDB Test Results</title>
|
|
343
|
+
<style>
|
|
344
|
+
body { font-family: sans-serif; margin: 2em; }
|
|
345
|
+
.pass { color: green; }
|
|
346
|
+
.fail { color: red; }
|
|
347
|
+
.test { margin-bottom: 1em; }
|
|
348
|
+
pre { background: #f4f4f4; padding: 0.5em; }
|
|
349
|
+
</style>
|
|
350
|
+
</head><body>
|
|
351
|
+
<h1>JotDB Test Results</h1>
|
|
352
|
+
<p><b>Timestamp:</b> ${new Date(results.timestamp).toLocaleString()}</p>
|
|
353
|
+
<div>
|
|
354
|
+
${results.tests.map(test => `
|
|
355
|
+
<div class="test">
|
|
356
|
+
<b>${test.name}:</b> <span class="${test.passed ? 'pass' : 'fail'}">${test.passed ? 'PASS' : 'FAIL'}</span><br/>
|
|
357
|
+
<pre>${JSON.stringify(test.value ?? test.error, null, 2)}</pre>
|
|
358
|
+
</div>
|
|
359
|
+
`).join('')}
|
|
360
|
+
</div>
|
|
361
|
+
<h2>Audit Log</h2>
|
|
362
|
+
<pre>${JSON.stringify(results.auditLog, null, 2)}</pre>
|
|
363
|
+
</body></html>`;
|
|
364
|
+
|
|
365
|
+
return new Response(html, { headers: { 'Content-Type': 'text/html' } });
|
|
320
366
|
} catch (error) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
367
|
+
let html = `<!DOCTYPE html><html><head><title>JotDB Test Error</title></head><body>` +
|
|
368
|
+
`<h1 style="color:red">Error</h1>` +
|
|
369
|
+
`<pre>${error instanceof Error ? error.message : String(error)}</pre>` +
|
|
370
|
+
`<h2>Partial Results</h2>` +
|
|
371
|
+
`<pre>${JSON.stringify(results.tests, null, 2)}</pre>` +
|
|
372
|
+
`<h2>Audit Log</h2>` +
|
|
373
|
+
`<pre>${JSON.stringify(results.auditLog, null, 2)}</pre>` +
|
|
374
|
+
`</body></html>`;
|
|
375
|
+
return new Response(html, { headers: { 'Content-Type': 'text/html' } });
|
|
326
376
|
}
|
|
327
377
|
});
|
|
328
378
|
|
package/jotdb.tests.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { assert, describe, it, beforeEach } from "bun:test";
|
|
2
|
-
import { JotDB } from "./src/index.ts";
|
|
3
|
-
|
|
4
|
-
// Fake DurableObjectState stub for unit testing
|
|
5
|
-
function createFakeState(): any {
|
|
6
|
-
let store: Record<string, any> = {};
|
|
7
|
-
return {
|
|
8
|
-
storage: {
|
|
9
|
-
get: async (k: string) => store[k],
|
|
10
|
-
put: async (k: string, v: any) => { store[k] = v },
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
describe("JotDB", () => {
|
|
16
|
-
let jot: JotDB;
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
jot = new JotDB(createFakeState());
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("should set and get a value", async () => {
|
|
23
|
-
await jot.set("foo", "bar");
|
|
24
|
-
const val = await jot.get("foo");
|
|
25
|
-
assert(val === "bar");
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("should support setAll and getAll", async () => {
|
|
29
|
-
await jot.setAll({ a: 1, b: true });
|
|
30
|
-
const all = await jot.getAll();
|
|
31
|
-
assert(all.a === 1);
|
|
32
|
-
assert(all.b === true);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("should enforce schema after inference", async () => {
|
|
36
|
-
await jot.setAll({ x: "yes", y: 2 });
|
|
37
|
-
await assert.rejects(() => jot.set("y", "not a number"));
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("should strip unknowns if autoStrip is on", async () => {
|
|
41
|
-
await jot.setAll({ known: "yes" });
|
|
42
|
-
await jot.setOptions({ autoStrip: true });
|
|
43
|
-
await jot.setAll({ known: "ok", extra: "skip" });
|
|
44
|
-
const data = await jot.getAll();
|
|
45
|
-
assert(data.known === "ok");
|
|
46
|
-
assert(!("extra" in data));
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("should block writes if readOnly is on", async () => {
|
|
50
|
-
await jot.setAll({ z: 9 });
|
|
51
|
-
await jot.setOptions({ readOnly: true });
|
|
52
|
-
await assert.rejects(() => jot.set("z", 10));
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("should track audit logs", async () => {
|
|
56
|
-
await jot.set("a", 1);
|
|
57
|
-
await jot.setAll({ b: 2, c: 3 });
|
|
58
|
-
const log = await jot.getAuditLog();
|
|
59
|
-
assert(log.length >= 2);
|
|
60
|
-
assert(log[0].action === "setAll");
|
|
61
|
-
assert(log[1].action === "set");
|
|
62
|
-
});
|
|
63
|
-
});
|