jotdb 0.1.8 → 0.1.9
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/README.md +296 -6
- package/dist/index.d.ts +58 -1
- package/dist/index.js +552 -31
- package/dist/site-worker.d.ts +7 -0
- package/dist/site-worker.js +6 -0
- package/dist/src/index.d.ts +107 -0
- package/dist/src/index.js +802 -0
- package/dist/src/site-worker.d.ts +7 -0
- package/dist/src/site-worker.js +6 -0
- package/dist/src/store.d.ts +60 -0
- package/dist/src/store.js +63 -0
- package/dist/store.d.ts +60 -0
- package/dist/store.js +64 -0
- package/package.json +18 -7
- package/bun.lock +0 -220
- package/src/index.ts +0 -538
- package/tsconfig.json +0 -33
- package/wrangler.jsonc +0 -29
package/src/index.ts
DELETED
|
@@ -1,538 +0,0 @@
|
|
|
1
|
-
import { z, ZodTypeAny, ZodObject, ZodError } from "zod";
|
|
2
|
-
import { Hono } from 'hono';
|
|
3
|
-
import { cors } from 'hono/cors';
|
|
4
|
-
import { prettyJSON } from 'hono/pretty-json';
|
|
5
|
-
import { DurableObject } from "cloudflare:workers";
|
|
6
|
-
|
|
7
|
-
// Type definitions
|
|
8
|
-
type SchemaType = "string" | "number" | "boolean" | "email" | "array" | "object" | "any";
|
|
9
|
-
type ObjectSchema = Record<string, SchemaType>;
|
|
10
|
-
type ArraySchema = { __arrayType: SchemaType | ObjectSchema };
|
|
11
|
-
type SchemaDefinition = ObjectSchema | ArraySchema;
|
|
12
|
-
|
|
13
|
-
interface JotDBOptions {
|
|
14
|
-
autoStrip: boolean;
|
|
15
|
-
readOnly: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface AuditLogEntry {
|
|
19
|
-
timestamp: number;
|
|
20
|
-
action: string;
|
|
21
|
-
keys: string[];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function isZodError(e: any): e is ZodError {
|
|
25
|
-
return e && typeof e === 'object' && Array.isArray(e.issues);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function handleZod<T>(fn: () => T): T {
|
|
29
|
-
try {
|
|
30
|
-
return fn();
|
|
31
|
-
} catch (e) {
|
|
32
|
-
if (isZodError(e)) {
|
|
33
|
-
throw new Error('Validation failed: ' + e.issues.map(issue => issue.message).join('; '));
|
|
34
|
-
}
|
|
35
|
-
throw e;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function isArraySchema(schema: SchemaDefinition): schema is ArraySchema {
|
|
40
|
-
return '__arrayType' in schema;
|
|
41
|
-
}
|
|
42
|
-
function isObjectSchema(schema: SchemaDefinition): schema is ObjectSchema {
|
|
43
|
-
return !('__arrayType' in schema);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function inferPrimitiveType(v: any): SchemaType {
|
|
47
|
-
if (typeof v === "string") return v.includes("@") ? "email" : "string";
|
|
48
|
-
if (typeof v === "number") return "number";
|
|
49
|
-
if (typeof v === "boolean") return "boolean";
|
|
50
|
-
return "any";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export class JotDB extends DurableObject {
|
|
54
|
-
private data: Record<string, unknown> | unknown[] = {};
|
|
55
|
-
private rawSchema: SchemaDefinition = {};
|
|
56
|
-
private zodSchema: ZodTypeAny | null = null;
|
|
57
|
-
private options: JotDBOptions = {
|
|
58
|
-
autoStrip: false,
|
|
59
|
-
readOnly: false,
|
|
60
|
-
};
|
|
61
|
-
private auditLog: AuditLogEntry[] = [];
|
|
62
|
-
|
|
63
|
-
constructor(state: any, env: Env) {
|
|
64
|
-
super(state, env);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async load(): Promise<void> {
|
|
68
|
-
if (this.data == null || (typeof this.data === 'object' && Object.keys(this.data).length === 0)) {
|
|
69
|
-
this.data = (await this.ctx.storage.get("data")) ?? {};
|
|
70
|
-
}
|
|
71
|
-
if (Object.keys(this.rawSchema).length === 0) {
|
|
72
|
-
this.rawSchema = (await this.ctx.storage.get("__schema__")) || {};
|
|
73
|
-
if (Object.keys(this.rawSchema).length > 0) {
|
|
74
|
-
this.zodSchema = this.buildZodSchema(this.rawSchema);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
const storedOptions = await this.ctx.storage.get("__options__") as JotDBOptions | null;
|
|
78
|
-
if (storedOptions) this.options = storedOptions;
|
|
79
|
-
this.auditLog = (await this.ctx.storage.get("__audit__")) || [];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async save(): Promise<void> {
|
|
83
|
-
await this.ctx.storage.put("data", this.data);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
isArrayMode(): boolean {
|
|
87
|
-
return Array.isArray(this.data);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
private inferSchemaFromValue(value: any): SchemaDefinition {
|
|
91
|
-
if (Array.isArray(value)) {
|
|
92
|
-
if (value.length === 0) return { __arrayType: "any" };
|
|
93
|
-
const first = value[0];
|
|
94
|
-
if (typeof first === "object" && first !== null && !Array.isArray(first)) {
|
|
95
|
-
// Array of objects
|
|
96
|
-
return { __arrayType: this.inferSchemaFromValue(first) };
|
|
97
|
-
}
|
|
98
|
-
// Array of primitives
|
|
99
|
-
return { __arrayType: inferPrimitiveType(first) };
|
|
100
|
-
}
|
|
101
|
-
if (typeof value === "object" && value !== null) {
|
|
102
|
-
const schema: ObjectSchema = {};
|
|
103
|
-
for (const [k, v] of Object.entries(value)) {
|
|
104
|
-
if (Array.isArray(v)) {
|
|
105
|
-
schema[k] = "array";
|
|
106
|
-
} else if (typeof v === "object" && v !== null) {
|
|
107
|
-
schema[k] = "object";
|
|
108
|
-
} else {
|
|
109
|
-
schema[k] = inferPrimitiveType(v);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return schema;
|
|
113
|
-
}
|
|
114
|
-
// Top-level primitive (shouldn't happen for objects, but fallback)
|
|
115
|
-
return {};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async push(item: unknown): Promise<void> {
|
|
119
|
-
await this.load();
|
|
120
|
-
if (!Array.isArray(this.data)) {
|
|
121
|
-
this.data = [];
|
|
122
|
-
}
|
|
123
|
-
if (!this.zodSchema) {
|
|
124
|
-
const schema = this.inferSchemaFromValue([item]);
|
|
125
|
-
await this.setSchema(schema);
|
|
126
|
-
console.info('[JotDB] Auto-inferred and set schema from first push:', schema);
|
|
127
|
-
}
|
|
128
|
-
if (isArraySchema(this.rawSchema)) {
|
|
129
|
-
handleZod(() => (this.zodSchema as any).parse([item]));
|
|
130
|
-
} else if (this.zodSchema && isObjectSchema(this.rawSchema)) {
|
|
131
|
-
handleZod(() => this.zodSchema!.parse(item));
|
|
132
|
-
}
|
|
133
|
-
(this.data as unknown[]).push(item);
|
|
134
|
-
await this.save();
|
|
135
|
-
await this.logAudit("push", []);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async setAll(objOrArr: Record<string, unknown> | unknown[]): Promise<void> {
|
|
139
|
-
await this.load();
|
|
140
|
-
if (!this.zodSchema) {
|
|
141
|
-
const schema = this.inferSchemaFromValue(objOrArr);
|
|
142
|
-
await this.setSchema(schema);
|
|
143
|
-
console.info('[JotDB] Auto-inferred and set schema from first setAll:', schema);
|
|
144
|
-
}
|
|
145
|
-
if (Array.isArray(objOrArr) && isArraySchema(this.rawSchema)) {
|
|
146
|
-
handleZod(() => (this.zodSchema as any).parse(objOrArr));
|
|
147
|
-
} else if (!Array.isArray(objOrArr) && this.zodSchema && isObjectSchema(this.rawSchema)) {
|
|
148
|
-
objOrArr = handleZod(() => this.zodSchema!.parse(objOrArr));
|
|
149
|
-
} else if (Array.isArray(objOrArr) && this.zodSchema && isObjectSchema(this.rawSchema)) {
|
|
150
|
-
objOrArr.forEach(item => handleZod(() => this.zodSchema!.parse(item)));
|
|
151
|
-
}
|
|
152
|
-
this.data = objOrArr;
|
|
153
|
-
await this.save();
|
|
154
|
-
await this.logAudit("setAll", Array.isArray(objOrArr) ? [] : Object.keys(objOrArr));
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async getAll(): Promise<unknown> {
|
|
158
|
-
await this.load();
|
|
159
|
-
return this.data;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async logAudit(action: string, keys: string[] | string): Promise<void> {
|
|
163
|
-
const entry: AuditLogEntry = {
|
|
164
|
-
timestamp: Date.now(),
|
|
165
|
-
action,
|
|
166
|
-
keys: Array.isArray(keys) ? keys : [keys],
|
|
167
|
-
};
|
|
168
|
-
this.auditLog.unshift(entry);
|
|
169
|
-
await this.ctx.storage.put("__audit__", this.auditLog.slice(0, 100)); // keep max 100 entries
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async get<T = unknown>(key: string): Promise<T | undefined> {
|
|
173
|
-
await this.load();
|
|
174
|
-
if (!Array.isArray(this.data)) {
|
|
175
|
-
return this.data[key] as T;
|
|
176
|
-
}
|
|
177
|
-
return undefined;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async delete(key: string): Promise<void> {
|
|
181
|
-
await this.load();
|
|
182
|
-
if (!Array.isArray(this.data)) {
|
|
183
|
-
delete this.data[key];
|
|
184
|
-
await this.save();
|
|
185
|
-
await this.logAudit("delete", key);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
async clear(): Promise<void> {
|
|
190
|
-
this.data = {};
|
|
191
|
-
await this.save();
|
|
192
|
-
await this.logAudit("clear", []);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async keys(): Promise<string[]> {
|
|
196
|
-
await this.load();
|
|
197
|
-
if (!Array.isArray(this.data)) {
|
|
198
|
-
return Object.keys(this.data);
|
|
199
|
-
}
|
|
200
|
-
return [];
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
async has(key: string): Promise<boolean> {
|
|
204
|
-
await this.load();
|
|
205
|
-
if (!Array.isArray(this.data)) {
|
|
206
|
-
return key in this.data;
|
|
207
|
-
}
|
|
208
|
-
return false;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
async getSchema(): Promise<SchemaDefinition> {
|
|
212
|
-
await this.load();
|
|
213
|
-
return this.rawSchema;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
private warnSchemaDiff(newSchema: SchemaDefinition): void {
|
|
217
|
-
const current = this.rawSchema;
|
|
218
|
-
for (const key in newSchema) {
|
|
219
|
-
if (!(key in current)) console.warn(`[JotDB] New key added: ${key}`);
|
|
220
|
-
else if (newSchema[key] !== current[key]) {
|
|
221
|
-
console.warn(
|
|
222
|
-
`[JotDB] Type changed for "${key}": ${current[key]} → ${newSchema[key]}`
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
for (const key in current) {
|
|
227
|
-
if (!(key in newSchema)) {
|
|
228
|
-
console.warn(`[JotDB] Key removed: ${key}`);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async setSchema(schemaObj: SchemaDefinition): Promise<void> {
|
|
234
|
-
await this.load();
|
|
235
|
-
if (Object.keys(this.rawSchema).length > 0) {
|
|
236
|
-
this.warnSchemaDiff(schemaObj);
|
|
237
|
-
}
|
|
238
|
-
this.rawSchema = schemaObj;
|
|
239
|
-
this.zodSchema = this.buildZodSchema(schemaObj);
|
|
240
|
-
await this.ctx.storage.put("__schema__", schemaObj);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
async setOptions(opts: Partial<JotDBOptions>): Promise<void> {
|
|
244
|
-
await this.load();
|
|
245
|
-
Object.assign(this.options, opts);
|
|
246
|
-
await this.ctx.storage.put("__options__", this.options);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
async getOptions(): Promise<JotDBOptions> {
|
|
250
|
-
await this.load();
|
|
251
|
-
return this.options;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
async getAuditLog(): Promise<AuditLogEntry[]> {
|
|
255
|
-
await this.load();
|
|
256
|
-
return this.auditLog;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async clearAuditLog(): Promise<void> {
|
|
260
|
-
this.auditLog = [];
|
|
261
|
-
await this.ctx.storage.put("__audit__", []);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
private buildZodSchema(schema: SchemaDefinition): ZodTypeAny {
|
|
265
|
-
if (isArraySchema(schema)) {
|
|
266
|
-
const t = schema.__arrayType;
|
|
267
|
-
if (typeof t === "string") {
|
|
268
|
-
switch (t) {
|
|
269
|
-
case "string": return z.string().array();
|
|
270
|
-
case "number": return z.number().array();
|
|
271
|
-
case "boolean": return z.boolean().array();
|
|
272
|
-
case "email": return z.string().email().array();
|
|
273
|
-
default: return z.any().array();
|
|
274
|
-
}
|
|
275
|
-
} else {
|
|
276
|
-
// Array of objects
|
|
277
|
-
return this.buildZodSchema(t).array();
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
// Object schema
|
|
281
|
-
const shape: Record<string, ZodTypeAny> = {};
|
|
282
|
-
for (const [key, type] of Object.entries(schema)) {
|
|
283
|
-
switch (type) {
|
|
284
|
-
case "string": shape[key] = z.string(); break;
|
|
285
|
-
case "number": shape[key] = z.number(); break;
|
|
286
|
-
case "boolean": shape[key] = z.boolean(); break;
|
|
287
|
-
case "email": shape[key] = z.string().email(); break;
|
|
288
|
-
case "array": shape[key] = z.array(z.any()); break;
|
|
289
|
-
case "object": shape[key] = z.record(z.any()); break;
|
|
290
|
-
default: shape[key] = z.any();
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
return z.object(shape);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
async fetch(request: Request) {
|
|
297
|
-
return new Response("Hello, World!");
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
async set(key: string, value: unknown): Promise<void> {
|
|
301
|
-
await this.load();
|
|
302
|
-
if (this.options.readOnly) {
|
|
303
|
-
throw new Error("Database is in read-only mode");
|
|
304
|
-
}
|
|
305
|
-
if (!Array.isArray(this.data)) {
|
|
306
|
-
// Auto-infer schema if not set
|
|
307
|
-
if (!this.zodSchema) {
|
|
308
|
-
const schema = this.inferSchemaFromValue({ [key]: value });
|
|
309
|
-
await this.setSchema(schema);
|
|
310
|
-
console.info('[JotDB] Auto-inferred and set schema from first set:', schema);
|
|
311
|
-
}
|
|
312
|
-
this.data[key] = value;
|
|
313
|
-
if (this.zodSchema) {
|
|
314
|
-
handleZod(() => this.zodSchema!.parse(this.data));
|
|
315
|
-
}
|
|
316
|
-
await this.save();
|
|
317
|
-
await this.logAudit("set", key);
|
|
318
|
-
} else {
|
|
319
|
-
throw new Error("Cannot use set() in array mode");
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
export interface Env {
|
|
325
|
-
JOTDB: DurableObjectNamespace;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const app = new Hono<{ Bindings: Env }>();
|
|
329
|
-
|
|
330
|
-
// Middleware
|
|
331
|
-
app.use('*', cors());
|
|
332
|
-
app.use('*', prettyJSON());
|
|
333
|
-
|
|
334
|
-
// Test endpoint
|
|
335
|
-
app.get('/test', async (c) => {
|
|
336
|
-
const JOB_ID = Date.now().toString();
|
|
337
|
-
const id = c.env.JOTDB.idFromName(JOB_ID);
|
|
338
|
-
const db = c.env.JOTDB.get(id) as unknown as JotDB;
|
|
339
|
-
|
|
340
|
-
const results = {
|
|
341
|
-
timestamp: Date.now(),
|
|
342
|
-
tests: [] as any[],
|
|
343
|
-
auditLog: [] as any[]
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
try {
|
|
347
|
-
|
|
348
|
-
// Test 1: Basic set/get
|
|
349
|
-
await db.set("test1", "hello");
|
|
350
|
-
const value1 = await db.get("test1");
|
|
351
|
-
results.tests.push({
|
|
352
|
-
name: "Basic set/get",
|
|
353
|
-
passed: value1 === "hello",
|
|
354
|
-
value: value1
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
// Test 2: Schema validation
|
|
358
|
-
await db.setSchema({
|
|
359
|
-
name: "string",
|
|
360
|
-
age: "number",
|
|
361
|
-
email: "email"
|
|
362
|
-
});
|
|
363
|
-
await db.setAll({
|
|
364
|
-
name: "John",
|
|
365
|
-
age: 30,
|
|
366
|
-
email: "john@example.com"
|
|
367
|
-
});
|
|
368
|
-
const all = await db.getAll() as { name: string, age: number, email: string };
|
|
369
|
-
results.tests.push({
|
|
370
|
-
name: "Schema validation",
|
|
371
|
-
passed: all.name === "John" && all.age === 30,
|
|
372
|
-
value: all
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
// Test 3: Read-only mode
|
|
376
|
-
await db.setOptions({ readOnly: true });
|
|
377
|
-
try {
|
|
378
|
-
await db.set("test3", "should fail");
|
|
379
|
-
results.tests.push({
|
|
380
|
-
name: "Read-only mode",
|
|
381
|
-
passed: false,
|
|
382
|
-
error: "Should have thrown"
|
|
383
|
-
});
|
|
384
|
-
} catch (e) {
|
|
385
|
-
results.tests.push({
|
|
386
|
-
name: "Read-only mode",
|
|
387
|
-
passed: true,
|
|
388
|
-
error: e instanceof Error ? e.message : String(e)
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Test 4: Auto-strip mode
|
|
393
|
-
await db.setOptions({ readOnly: false, autoStrip: true });
|
|
394
|
-
await db.setAll({
|
|
395
|
-
name: "Jane",
|
|
396
|
-
age: 25,
|
|
397
|
-
email: "jane@example.com",
|
|
398
|
-
extra: "should be stripped"
|
|
399
|
-
});
|
|
400
|
-
const stripped = await db.getAll() as { name: string, age: number, email: string };
|
|
401
|
-
results.tests.push({
|
|
402
|
-
name: "Auto-strip mode",
|
|
403
|
-
passed: !("extra" in stripped),
|
|
404
|
-
value: stripped
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
// Test 5: Array mode - setAll and getAll
|
|
408
|
-
const arrayId = c.env.JOTDB.idFromName(JOB_ID + "-test-array");
|
|
409
|
-
const arrayDb = c.env.JOTDB.get(arrayId) as unknown as JotDB;
|
|
410
|
-
await arrayDb.setAll([1, 2, 3]);
|
|
411
|
-
const arr = await arrayDb.getAll();
|
|
412
|
-
results.tests.push({
|
|
413
|
-
name: "Array mode setAll/getAll",
|
|
414
|
-
passed: Array.isArray(arr) && arr.length === 3 && arr[0] === 1 && arr[2] === 3,
|
|
415
|
-
value: arr
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
// Test 6: Array mode - push
|
|
419
|
-
await arrayDb.push(4);
|
|
420
|
-
const arr2 = await arrayDb.getAll();
|
|
421
|
-
results.tests.push({
|
|
422
|
-
name: "Array mode push",
|
|
423
|
-
passed: Array.isArray(arr2) && arr2.length === 4 && arr2[3] === 4,
|
|
424
|
-
value: arr2
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
// --- Schema inference tests-- -
|
|
428
|
-
// Object mode
|
|
429
|
-
const objId = c.env.JOTDB.idFromName("schema-obj");
|
|
430
|
-
const objDb = c.env.JOTDB.get(objId) as unknown as JotDB;
|
|
431
|
-
await objDb.setAll({ foo: "bar", count: 1 });
|
|
432
|
-
const objSchema = await objDb.getSchema();
|
|
433
|
-
let objPassed = false;
|
|
434
|
-
if (!('__arrayType' in objSchema)) {
|
|
435
|
-
objPassed = (objSchema as any).foo === "string" && (objSchema as any).count === "number";
|
|
436
|
-
}
|
|
437
|
-
results.tests.push({
|
|
438
|
-
name: "Object mode: inferred schema",
|
|
439
|
-
passed: objPassed,
|
|
440
|
-
value: objSchema
|
|
441
|
-
});
|
|
442
|
-
let objError = null;
|
|
443
|
-
try {
|
|
444
|
-
await objDb.setAll({ foo: 123, count: "not a number" });
|
|
445
|
-
} catch (e) {
|
|
446
|
-
objError = e instanceof Error ? e.message : String(e);
|
|
447
|
-
}
|
|
448
|
-
results.tests.push({
|
|
449
|
-
name: "Object mode: invalid shape fails",
|
|
450
|
-
passed: !!objError,
|
|
451
|
-
error: objError
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
// Array mode
|
|
455
|
-
const arrId = c.env.JOTDB.idFromName("schema-arr");
|
|
456
|
-
const arrDb = c.env.JOTDB.get(arrId) as unknown as JotDB;
|
|
457
|
-
await arrDb.setAll([{ foo: "bar", count: 1 }]);
|
|
458
|
-
const arrSchema = await arrDb.getSchema();
|
|
459
|
-
let arrPassed = false;
|
|
460
|
-
if ('__arrayType' in arrSchema && typeof arrSchema.__arrayType === 'object') {
|
|
461
|
-
arrPassed = arrSchema.__arrayType.foo === "string" && arrSchema.__arrayType.count === "number";
|
|
462
|
-
}
|
|
463
|
-
results.tests.push({
|
|
464
|
-
name: "Array mode: inferred schema",
|
|
465
|
-
passed: arrPassed,
|
|
466
|
-
value: arrSchema
|
|
467
|
-
});
|
|
468
|
-
let arrError = null;
|
|
469
|
-
try {
|
|
470
|
-
await arrDb.push({ foo: 123, count: "not a number" });
|
|
471
|
-
} catch (e) {
|
|
472
|
-
arrError = e instanceof Error ? e.message : String(e);
|
|
473
|
-
}
|
|
474
|
-
results.tests.push({
|
|
475
|
-
name: "Array mode: invalid item fails",
|
|
476
|
-
passed: !!arrError,
|
|
477
|
-
error: arrError
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
// Test 0: Clear database, set options to read-only: false
|
|
481
|
-
await db.clear();
|
|
482
|
-
results.tests.push({
|
|
483
|
-
name: "Clear database",
|
|
484
|
-
passed: true,
|
|
485
|
-
value: await db.getAll()
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
// Get audit log
|
|
489
|
-
results.auditLog = await db.getAuditLog();
|
|
490
|
-
|
|
491
|
-
// HTML output
|
|
492
|
-
let html = `<!DOCTYPE html><html><head><title>JotDB Test Results</title>
|
|
493
|
-
<style>
|
|
494
|
-
body { font-family: sans-serif; margin: 2em; }
|
|
495
|
-
.pass { color: green; }
|
|
496
|
-
.fail { color: red; }
|
|
497
|
-
.test { margin-bottom: 1em; }
|
|
498
|
-
pre { background: #f4f4f4; padding: 0.5em; }
|
|
499
|
-
</style>
|
|
500
|
-
</head><body>
|
|
501
|
-
<h1>JotDB Test Results</h1>
|
|
502
|
-
<p><b>Timestamp:</b> ${new Date(results.timestamp).toLocaleString()}</p>
|
|
503
|
-
<div>
|
|
504
|
-
${results.tests.map(test => `
|
|
505
|
-
<div class="test">
|
|
506
|
-
<b>${test.name}:</b> <span class="${test.passed ? 'pass' : 'fail'}">${test.passed ? 'PASS' : 'FAIL'}</span><br/>
|
|
507
|
-
<pre>${JSON.stringify(test.value ?? test.error, null, 2)}</pre>
|
|
508
|
-
</div>
|
|
509
|
-
`).join('')}
|
|
510
|
-
</div>
|
|
511
|
-
<h2>Audit Log</h2>
|
|
512
|
-
<pre>${JSON.stringify(results.auditLog, null, 2)}</pre>
|
|
513
|
-
</body></html>`;
|
|
514
|
-
|
|
515
|
-
return new Response(html, { headers: { 'Content-Type': 'text/html' } });
|
|
516
|
-
} catch (error) {
|
|
517
|
-
console.error(error);
|
|
518
|
-
let errorMessage = error instanceof Error ? error.message : String(error);
|
|
519
|
-
if (isZodError(error)) {
|
|
520
|
-
console.error(error.issues);
|
|
521
|
-
errorMessage = error.issues.map(issue => issue.message).join('\n');
|
|
522
|
-
}
|
|
523
|
-
let html = `<!DOCTYPE html><html><head><title>JotDB Test Error</title></head><body>` +
|
|
524
|
-
`<h1 style="color:red">Error</h1>` +
|
|
525
|
-
`<pre>${errorMessage}</pre>` +
|
|
526
|
-
`<h2>Partial Results</h2>` +
|
|
527
|
-
`<pre>${JSON.stringify(results.tests, null, 2)}</pre>` +
|
|
528
|
-
`<h2>Audit Log</h2>` +
|
|
529
|
-
`<pre>${JSON.stringify(results.auditLog, null, 2)}</pre>` +
|
|
530
|
-
`</body></html>`;
|
|
531
|
-
return new Response(html, { headers: { 'Content-Type': 'text/html' } });
|
|
532
|
-
}
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
// Health check endpoint
|
|
536
|
-
app.get('/', (c) => c.text('JotDB Durable Object'));
|
|
537
|
-
|
|
538
|
-
export default app;
|
package/tsconfig.json
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"lib": [
|
|
6
|
-
"ES2020"
|
|
7
|
-
],
|
|
8
|
-
"types": [
|
|
9
|
-
"@cloudflare/workers-types"
|
|
10
|
-
],
|
|
11
|
-
"declaration": true,
|
|
12
|
-
"emitDeclarationOnly": false,
|
|
13
|
-
"outDir": "dist",
|
|
14
|
-
"moduleResolution": "node",
|
|
15
|
-
"strict": true,
|
|
16
|
-
"noImplicitAny": true,
|
|
17
|
-
"strictNullChecks": true,
|
|
18
|
-
"strictFunctionTypes": true,
|
|
19
|
-
"strictBindCallApply": true,
|
|
20
|
-
"strictPropertyInitialization": true,
|
|
21
|
-
"noImplicitThis": true,
|
|
22
|
-
"alwaysStrict": true,
|
|
23
|
-
"esModuleInterop": true,
|
|
24
|
-
"skipLibCheck": true,
|
|
25
|
-
"forceConsistentCasingInFileNames": true
|
|
26
|
-
},
|
|
27
|
-
"include": [
|
|
28
|
-
"src/**/*"
|
|
29
|
-
],
|
|
30
|
-
"exclude": [
|
|
31
|
-
"node_modules"
|
|
32
|
-
]
|
|
33
|
-
}
|
package/wrangler.jsonc
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "node_modules/wrangler/config-schema.json",
|
|
3
|
-
"name": "jotdb",
|
|
4
|
-
"main": "src/index.ts",
|
|
5
|
-
"compatibility_date": "2024-05-01",
|
|
6
|
-
"compatibility_flags": [
|
|
7
|
-
"nodejs_compat"
|
|
8
|
-
],
|
|
9
|
-
"durable_objects": {
|
|
10
|
-
"bindings": [
|
|
11
|
-
{
|
|
12
|
-
"name": "JOTDB",
|
|
13
|
-
"class_name": "JotDB"
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
},
|
|
17
|
-
"migrations": [
|
|
18
|
-
{
|
|
19
|
-
"tag": "v1",
|
|
20
|
-
"new_classes": [
|
|
21
|
-
"JotDB"
|
|
22
|
-
]
|
|
23
|
-
}
|
|
24
|
-
],
|
|
25
|
-
"minify": true,
|
|
26
|
-
"observability": {
|
|
27
|
-
"enabled": true
|
|
28
|
-
}
|
|
29
|
-
}
|