jotdb 0.1.5 → 0.1.7
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 +105 -27
- package/package.json +3 -2
- package/src/index.ts +186 -30
package/README.md
CHANGED
|
@@ -1,16 +1,56 @@
|
|
|
1
1
|
# JotDB
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://deploy.workers.cloudflare.com/?url=https://github.com/acoyfellow/jotdb)
|
|
4
|
+
|
|
5
|
+
A lightweight, schema-less database built on Cloudflare Durable Objects. Think of it as Firestore's security rules, but with Zod validation built-in. Perfect for both internal and external APIs, with automatic type safety and validation.
|
|
6
|
+
|
|
7
|
+
> **Cloudflare Products**: JotDB works with any Cloudflare product that supports Durable Objects:
|
|
8
|
+
> - Cloudflare Workers
|
|
9
|
+
> - Cloudflare Pages (with Functions)
|
|
10
|
+
> - Cloudflare Workflows
|
|
11
|
+
> - Cloudflare Queues
|
|
12
|
+
> - Cloudflare Cron Triggers
|
|
4
13
|
|
|
5
14
|
## Why JotDB?
|
|
6
15
|
|
|
7
|
-
|
|
16
|
+
JotDB combines the best of both worlds: the simplicity of NoSQL with the safety of schema validation. Here's what makes it special:
|
|
17
|
+
|
|
18
|
+
- **Built-in Type Safety**: Automatic Zod validation ensures your data is always in the right shape
|
|
19
|
+
- **Edge-Native**: Runs directly on Cloudflare's edge network, with sub-millisecond latency
|
|
20
|
+
- **RPC-First**: Direct method calls instead of HTTP endpoints (though you can easily wrap it in HTTP)
|
|
21
|
+
- **Durable Storage**: Built on Durable Objects for reliable, consistent storage
|
|
22
|
+
- **Zero Setup**: No database configuration, no connection strings, just instantiate and go
|
|
23
|
+
- **Perfect for APIs**: Use it as an internal database or wrap it with auth for external APIs
|
|
24
|
+
- **Real-time Ready**: Durable Objects provide strong consistency guarantees
|
|
25
|
+
|
|
26
|
+
Perfect for:
|
|
27
|
+
- Quick prototypes that need data validation
|
|
28
|
+
- Small to medium applications that need reliable storage
|
|
29
|
+
- Serverless environments where you want type safety
|
|
30
|
+
- Real-time data storage with strong consistency
|
|
31
|
+
- Collaborative applications that need data validation
|
|
32
|
+
- APIs that need both flexibility and safety
|
|
33
|
+
|
|
34
|
+
## Design Patterns
|
|
8
35
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
36
|
+
JotDB uses Cloudflare Durable Objects under the hood, which means you can organize your data in several ways:
|
|
37
|
+
|
|
38
|
+
1. **Global Store**: Use a single instance for your entire application
|
|
39
|
+
```typescript
|
|
40
|
+
const db = env.JOTDB.get(env.JOTDB.idFromName("global"));
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
2. **Per-User Store**: Create a separate instance for each user
|
|
44
|
+
```typescript
|
|
45
|
+
const userDb = env.JOTDB.get(env.JOTDB.idFromName(`user:${userId}`));
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
3. **Per-Event Store**: Create temporary stores for events or sessions
|
|
49
|
+
```typescript
|
|
50
|
+
const eventDb = env.JOTDB.get(env.JOTDB.idFromName(`event:${eventId}`));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Each instance is isolated and can have its own schema and options. This follows the Actor Model pattern, where each instance is an independent actor that manages its own state.
|
|
14
54
|
|
|
15
55
|
## Installation
|
|
16
56
|
|
|
@@ -25,24 +65,47 @@ yarn add jotdb
|
|
|
25
65
|
pnpm add jotdb
|
|
26
66
|
```
|
|
27
67
|
|
|
68
|
+
### Configure wrangler.jsonc
|
|
69
|
+
|
|
70
|
+
```jsonc
|
|
71
|
+
{
|
|
72
|
+
"durable_objects": {
|
|
73
|
+
"bindings": [
|
|
74
|
+
{
|
|
75
|
+
"name": "JOTDB",
|
|
76
|
+
"class_name": "JotDB"
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
28
83
|
## Full Example
|
|
29
84
|
|
|
30
85
|
```typescript
|
|
31
86
|
import { JotDB } from 'jotdb';
|
|
32
87
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// Set a value
|
|
38
|
-
await db.set("user:123", { name: "John", age: 30 });
|
|
39
|
-
|
|
40
|
-
// Get a value
|
|
41
|
-
const user = await db.get("user:123");
|
|
42
|
-
console.log(user); // { name: "John", age: 30 }
|
|
88
|
+
export interface Env {
|
|
89
|
+
JOTDB: DurableObjectNamespace;
|
|
90
|
+
}
|
|
43
91
|
|
|
44
|
-
|
|
45
|
-
|
|
92
|
+
export default {
|
|
93
|
+
async fetch(request: Request, env: Env) {
|
|
94
|
+
// Initialize the database
|
|
95
|
+
const jotId = env.JOTDB.idFromName("my-db");
|
|
96
|
+
const db = env.JOTDB.get(jotId);
|
|
97
|
+
|
|
98
|
+
// Example operations
|
|
99
|
+
await db.set("user:123", { name: "John", age: 30 });
|
|
100
|
+
const user = await db.get("user:123");
|
|
101
|
+
await db.delete("user:123");
|
|
102
|
+
|
|
103
|
+
// Return the result
|
|
104
|
+
return new Response(JSON.stringify({ user }), {
|
|
105
|
+
headers: { 'Content-Type': 'application/json' }
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
};
|
|
46
109
|
```
|
|
47
110
|
|
|
48
111
|
## API Reference
|
|
@@ -52,19 +115,34 @@ await db.delete("user:123");
|
|
|
52
115
|
| `set(key, value)` | Store a value | `key: string`, `value: any` | `Promise<void>` |
|
|
53
116
|
| `get(key)` | Retrieve a value | `key: string` | `Promise<any>` |
|
|
54
117
|
| `delete(key)` | Remove a value | `key: string` | `Promise<void>` |
|
|
55
|
-
| `
|
|
56
|
-
|
|
57
|
-
|
|
118
|
+
| `clear()` | Remove all values | none | `Promise<void>` |
|
|
119
|
+
| `keys()` | Get all keys | none | `Promise<string[]>` |
|
|
120
|
+
| `has(key)` | Check if key exists | `key: string` | `Promise<boolean>` |
|
|
121
|
+
| `getAll()` | Get all data | none | `Promise<Record<string, unknown> \| unknown[]>` |
|
|
122
|
+
| `setAll(objOrArr)` | Set all data at once | `objOrArr: Record<string, unknown> \| unknown[]` | `Promise<void>` |
|
|
123
|
+
| `push(item)` | Add item to array | `item: unknown` | `Promise<void>` |
|
|
124
|
+
| `getSchema()` | Get current schema | none | `Promise<SchemaDefinition>` |
|
|
125
|
+
| `setSchema(schema)` | Set data schema | `schema: SchemaDefinition` | `Promise<void>` |
|
|
126
|
+
| `getOptions()` | Get current options | none | `Promise<JotDBOptions>` |
|
|
127
|
+
| `setOptions(opts)` | Set database options | `opts: Partial<JotDBOptions>` | `Promise<void>` |
|
|
128
|
+
| `getAuditLog()` | Get audit log entries | none | `Promise<AuditLogEntry[]>` |
|
|
129
|
+
| `clearAuditLog()` | Clear audit log | none | `Promise<void>` |
|
|
130
|
+
|
|
131
|
+
### Options
|
|
58
132
|
|
|
59
133
|
```typescript
|
|
60
|
-
interface
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
delete(key: string): Promise<void>;
|
|
64
|
-
list(prefix?: string): Promise<string[]>;
|
|
134
|
+
interface JotDBOptions {
|
|
135
|
+
autoStrip: boolean; // Automatically strip unknown fields
|
|
136
|
+
readOnly: boolean; // Enable read-only mode
|
|
65
137
|
}
|
|
66
138
|
```
|
|
67
139
|
|
|
140
|
+
### Schema Types
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
type SchemaType = "string" | "number" | "boolean" | "email" | "array" | "object" | "any";
|
|
144
|
+
```
|
|
145
|
+
|
|
68
146
|
## License
|
|
69
147
|
|
|
70
148
|
MIT License - feel free to use this in your own projects!
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jotdb",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"dev": "wrangler dev --port 5173",
|
|
15
15
|
"deploy": "wrangler deploy",
|
|
16
|
-
"patch": "npm version patch
|
|
16
|
+
"patch": "npm version patch",
|
|
17
|
+
"publish": "npm publish --access public"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
19
20
|
"typescript": "^5.8.3",
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z, ZodTypeAny, ZodObject } from "zod";
|
|
1
|
+
import { z, ZodTypeAny, ZodObject, ZodError } from "zod";
|
|
2
2
|
import { Hono } from 'hono';
|
|
3
3
|
import { cors } from 'hono/cors';
|
|
4
4
|
import { prettyJSON } from 'hono/pretty-json';
|
|
@@ -6,7 +6,9 @@ import { DurableObject } from "cloudflare:workers";
|
|
|
6
6
|
|
|
7
7
|
// Type definitions
|
|
8
8
|
type SchemaType = "string" | "number" | "boolean" | "email" | "array" | "object" | "any";
|
|
9
|
-
type
|
|
9
|
+
type ObjectSchema = Record<string, SchemaType>;
|
|
10
|
+
type ArraySchema = { __arrayType: SchemaType | ObjectSchema };
|
|
11
|
+
type SchemaDefinition = ObjectSchema | ArraySchema;
|
|
10
12
|
|
|
11
13
|
interface JotDBOptions {
|
|
12
14
|
autoStrip: boolean;
|
|
@@ -19,10 +21,39 @@ interface AuditLogEntry {
|
|
|
19
21
|
keys: string[];
|
|
20
22
|
}
|
|
21
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
|
+
|
|
22
53
|
export class JotDB extends DurableObject {
|
|
23
54
|
private data: Record<string, unknown> | unknown[] = {};
|
|
24
55
|
private rawSchema: SchemaDefinition = {};
|
|
25
|
-
private zodSchema:
|
|
56
|
+
private zodSchema: ZodTypeAny | null = null;
|
|
26
57
|
private options: JotDBOptions = {
|
|
27
58
|
autoStrip: false,
|
|
28
59
|
readOnly: false,
|
|
@@ -56,11 +87,49 @@ export class JotDB extends DurableObject {
|
|
|
56
87
|
return Array.isArray(this.data);
|
|
57
88
|
}
|
|
58
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
|
+
|
|
59
118
|
async push(item: unknown): Promise<void> {
|
|
60
119
|
await this.load();
|
|
61
120
|
if (!Array.isArray(this.data)) {
|
|
62
121
|
this.data = [];
|
|
63
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
|
+
}
|
|
64
133
|
(this.data as unknown[]).push(item);
|
|
65
134
|
await this.save();
|
|
66
135
|
await this.logAudit("push", []);
|
|
@@ -68,8 +137,17 @@ export class JotDB extends DurableObject {
|
|
|
68
137
|
|
|
69
138
|
async setAll(objOrArr: Record<string, unknown> | unknown[]): Promise<void> {
|
|
70
139
|
await this.load();
|
|
71
|
-
if (!
|
|
72
|
-
|
|
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)));
|
|
73
151
|
}
|
|
74
152
|
this.data = objOrArr;
|
|
75
153
|
await this.save();
|
|
@@ -183,30 +261,33 @@ export class JotDB extends DurableObject {
|
|
|
183
261
|
await this.ctx.storage.put("__audit__", []);
|
|
184
262
|
}
|
|
185
263
|
|
|
186
|
-
private buildZodSchema(schema: SchemaDefinition):
|
|
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
|
|
187
281
|
const shape: Record<string, ZodTypeAny> = {};
|
|
188
282
|
for (const [key, type] of Object.entries(schema)) {
|
|
189
283
|
switch (type) {
|
|
190
|
-
case "string":
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
case "
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
shape[key] = z.boolean();
|
|
198
|
-
break;
|
|
199
|
-
case "email":
|
|
200
|
-
shape[key] = z.string().email();
|
|
201
|
-
break;
|
|
202
|
-
case "array":
|
|
203
|
-
shape[key] = z.array(z.any());
|
|
204
|
-
break;
|
|
205
|
-
case "object":
|
|
206
|
-
shape[key] = z.record(z.any());
|
|
207
|
-
break;
|
|
208
|
-
default:
|
|
209
|
-
shape[key] = z.any();
|
|
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();
|
|
210
291
|
}
|
|
211
292
|
}
|
|
212
293
|
return z.object(shape);
|
|
@@ -222,9 +303,15 @@ export class JotDB extends DurableObject {
|
|
|
222
303
|
throw new Error("Database is in read-only mode");
|
|
223
304
|
}
|
|
224
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
|
+
}
|
|
225
312
|
this.data[key] = value;
|
|
226
313
|
if (this.zodSchema) {
|
|
227
|
-
this.zodSchema
|
|
314
|
+
handleZod(() => this.zodSchema!.parse(this.data));
|
|
228
315
|
}
|
|
229
316
|
await this.save();
|
|
230
317
|
await this.logAudit("set", key);
|
|
@@ -246,7 +333,8 @@ app.use('*', prettyJSON());
|
|
|
246
333
|
|
|
247
334
|
// Test endpoint
|
|
248
335
|
app.get('/test', async (c) => {
|
|
249
|
-
const
|
|
336
|
+
const JOB_ID = Date.now().toString();
|
|
337
|
+
const id = c.env.JOTDB.idFromName(JOB_ID);
|
|
250
338
|
const db = c.env.JOTDB.get(id) as unknown as JotDB;
|
|
251
339
|
|
|
252
340
|
const results = {
|
|
@@ -256,6 +344,7 @@ app.get('/test', async (c) => {
|
|
|
256
344
|
};
|
|
257
345
|
|
|
258
346
|
try {
|
|
347
|
+
|
|
259
348
|
// Test 1: Basic set/get
|
|
260
349
|
await db.set("test1", "hello");
|
|
261
350
|
const value1 = await db.get("test1");
|
|
@@ -316,7 +405,7 @@ app.get('/test', async (c) => {
|
|
|
316
405
|
});
|
|
317
406
|
|
|
318
407
|
// Test 5: Array mode - setAll and getAll
|
|
319
|
-
const arrayId = c.env.JOTDB.idFromName("test-array");
|
|
408
|
+
const arrayId = c.env.JOTDB.idFromName(JOB_ID + "-test-array");
|
|
320
409
|
const arrayDb = c.env.JOTDB.get(arrayId) as unknown as JotDB;
|
|
321
410
|
await arrayDb.setAll([1, 2, 3]);
|
|
322
411
|
const arr = await arrayDb.getAll();
|
|
@@ -335,6 +424,67 @@ app.get('/test', async (c) => {
|
|
|
335
424
|
value: arr2
|
|
336
425
|
});
|
|
337
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
|
+
|
|
338
488
|
// Get audit log
|
|
339
489
|
results.auditLog = await db.getAuditLog();
|
|
340
490
|
|
|
@@ -364,9 +514,15 @@ app.get('/test', async (c) => {
|
|
|
364
514
|
|
|
365
515
|
return new Response(html, { headers: { 'Content-Type': 'text/html' } });
|
|
366
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
|
+
}
|
|
367
523
|
let html = `<!DOCTYPE html><html><head><title>JotDB Test Error</title></head><body>` +
|
|
368
524
|
`<h1 style="color:red">Error</h1>` +
|
|
369
|
-
`<pre>${
|
|
525
|
+
`<pre>${errorMessage}</pre>` +
|
|
370
526
|
`<h2>Partial Results</h2>` +
|
|
371
527
|
`<pre>${JSON.stringify(results.tests, null, 2)}</pre>` +
|
|
372
528
|
`<h2>Audit Log</h2>` +
|