jotdb 0.1.3 โ†’ 0.1.5

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 CHANGED
@@ -1,203 +1,78 @@
1
1
  # JotDB
2
2
 
3
- ## ๐Ÿš€ Quick Start: Using JotDB in Your Cloudflare Worker
3
+ A lightweight, schema-less database built on Cloudflare Durable Objects. Perfect for quick prototyping and applications that need simple data storage without the complexity of traditional databases.
4
4
 
5
- ### 1. **Install JotDB**
5
+ ## Why JotDB?
6
6
 
7
- ```bash
8
- bun add jotdb
9
- # or
10
- npm install jotdb
11
- ```
12
-
13
- ### 2. **Bind the Durable Object in your wrangler.toml or wrangler.json**
14
-
15
- ```toml
16
- [[durable_objects.bindings]]
17
- name = "JOTDB"
18
- class_name = "JotDB"
19
- ```
20
-
21
- ### 3. **Register the Durable Object in your Worker**
22
-
23
- ```ts
24
- import { JotDB } from 'jotdb';
25
-
26
- export interface Env {
27
- JOTDB: DurableObjectNamespace<JotDB>;
28
- }
29
-
30
- export default {
31
- async fetch(request: Request, env: Env) {
32
- // Get a stub for your JotDB instance
33
- const id = env.JOTDB.idFromName("my-db");
34
- const db = env.JOTDB.get(id);
35
-
36
- // Use RPC (recommended, requires extends DurableObject)
37
- await db.set("key", "value");
38
- const value = await db.get("key");
39
-
40
- return new Response(`Value: ${value}`);
41
- }
42
- };
43
- ```
44
-
45
- ### 4. **Deploy or run locally**
46
-
47
- ```bash
48
- wrangler dev
49
- # or
50
- wrangler deploy
51
- ```
52
-
53
- ---
54
-
55
- ## ๐Ÿ“ Notes
56
-
57
- - **RPC support:** JotDB uses Cloudflare's new JavaScript-native RPC. You can call methods directly on the stub (e.g., `db.set(...)`, `db.get(...)`).
58
- - **No fetch needed:** You do not need to use HTTP fetch to communicate with your Durable Objectโ€”just call methods!
59
- - **TypeScript:** Use `DurableObjectNamespace<JotDB>` for full type safety.
60
- - **See the API section below for all available methods.**
61
-
62
- ---
7
+ I needed a quick way to save data without dealing with schemas, SQL, or complex database setup. While Firestore is great, it can be overkill for simple use cases. JotDB provides a simpler alternative by leveraging Cloudflare Durable Objects, making it perfect for:
63
8
 
64
- ## ๐Ÿ“š Full Example
65
-
66
- ```ts
67
- import { JotDB } from 'jotdb';
68
-
69
- export interface Env {
70
- JOTDB: DurableObjectNamespace<JotDB>;
71
- }
72
-
73
- export default {
74
- async fetch(request: Request, env: Env) {
75
- const id = env.JOTDB.idFromName("my-db");
76
- const db = env.JOTDB.get(id);
77
-
78
- await db.setSchema({
79
- name: "string",
80
- age: "number",
81
- email: "email"
82
- });
83
-
84
- await db.setAll({
85
- name: "Alice",
86
- age: 42,
87
- email: "alice@example.com"
88
- });
89
-
90
- const all = await db.getAll();
91
-
92
- return new Response(JSON.stringify(all, null, 2), {
93
- headers: { "Content-Type": "application/json" }
94
- });
95
- }
96
- };
97
- ```
98
-
99
- ---
100
-
101
- A lightweight, schema-validated key-value store built on Cloudflare Durable Objects.
102
-
103
- ## Features
104
-
105
- - Schema validation using Zod
106
- - Automatic schema inference
107
- - Audit logging
108
- - TypeScript support
109
- - Read-only mode
110
- - Auto-strip mode for schema validation
9
+ - Quick prototypes
10
+ - Small to medium applications
11
+ - Serverless environments
12
+ - Real-time data storage
13
+ - Collaborative applications
111
14
 
112
15
  ## Installation
113
16
 
114
17
  ```bash
18
+ # Using npm
115
19
  npm install jotdb
116
- # or
117
- bun add jotdb
20
+
21
+ # Using yarn
22
+ yarn add jotdb
23
+
24
+ # Using pnpm
25
+ pnpm add jotdb
118
26
  ```
119
27
 
120
- ## Usage
28
+ ## Full Example
121
29
 
122
30
  ```typescript
123
31
  import { JotDB } from 'jotdb';
124
32
 
125
- // In your Worker
126
- export interface Env {
127
- JOTDB: DurableObjectNamespace;
128
- }
129
-
130
- export default {
131
- async fetch(request: Request, env: Env) {
132
- const id = env.JOTDB.idFromName("my-db");
133
- const db = env.JOTDB.get(id);
134
-
135
- // Set a value
136
- await db.set("key", "value");
137
-
138
- // Get a value
139
- const value = await db.get("key");
140
-
141
- // Set schema
142
- await db.setSchema({
143
- name: "string",
144
- age: "number",
145
- email: "email"
146
- });
147
-
148
- // Set multiple values
149
- await db.setAll({
150
- name: "John",
151
- age: 30,
152
- email: "john@example.com"
153
- });
154
- }
155
- };
156
- ```
33
+ // Initialize the database
34
+ const jotId = env.JotDB.idFromName("my-db");
35
+ const db = env.JotDB.get(jotId);
157
36
 
158
- ## API
37
+ // Set a value
38
+ await db.set("user:123", { name: "John", age: 30 });
159
39
 
160
- ### Methods
40
+ // Get a value
41
+ const user = await db.get("user:123");
42
+ console.log(user); // { name: "John", age: 30 }
161
43
 
162
- - `get<T>(key: string): Promise<T | undefined>`
163
- - `set<T>(key: string, value: T): Promise<void>`
164
- - `getAll(): Promise<Record<string, unknown>>`
165
- - `setAll(obj: Record<string, unknown>): Promise<void>`
166
- - `delete(key: string): Promise<void>`
167
- - `clear(): Promise<void>`
168
- - `keys(): Promise<string[]>`
169
- - `has(key: string): Promise<boolean>`
170
- - `getSchema(): Promise<SchemaDefinition>`
171
- - `setSchema(schema: SchemaDefinition): Promise<void>`
172
- - `getOptions(): Promise<JotDBOptions>`
173
- - `setOptions(opts: Partial<JotDBOptions>): Promise<void>`
174
- - `getAuditLog(): Promise<AuditLogEntry[]>`
175
- - `clearAuditLog(): Promise<void>`
44
+ // Delete a value
45
+ await db.delete("user:123");
46
+ ```
176
47
 
177
- ### Types
48
+ ## API Reference
178
49
 
179
- ```typescript
180
- type SchemaType = "string" | "number" | "boolean" | "email" | "array" | "object" | "any";
181
- type SchemaDefinition = Record<string, SchemaType>;
50
+ | Method | Description | Parameters | Returns |
51
+ |--------|-------------|------------|---------|
52
+ | `set(key, value)` | Store a value | `key: string`, `value: any` | `Promise<void>` |
53
+ | `get(key)` | Retrieve a value | `key: string` | `Promise<any>` |
54
+ | `delete(key)` | Remove a value | `key: string` | `Promise<void>` |
55
+ | `list(prefix?)` | List all keys (optionally filtered by prefix) | `prefix?: string` | `Promise<string[]>` |
182
56
 
183
- interface JotDBOptions {
184
- autoStrip: boolean;
185
- readOnly: boolean;
186
- }
57
+ ## Types
187
58
 
188
- interface AuditLogEntry {
189
- timestamp: number;
190
- action: string;
191
- keys: string[];
59
+ ```typescript
60
+ interface JotDB {
61
+ set(key: string, value: any): Promise<void>;
62
+ get(key: string): Promise<any>;
63
+ delete(key: string): Promise<void>;
64
+ list(prefix?: string): Promise<string[]>;
192
65
  }
193
66
  ```
194
67
 
195
68
  ## License
196
69
 
197
- MIT
70
+ MIT License - feel free to use this in your own projects!
198
71
 
199
72
  ## Contributing
200
73
 
74
+ Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
75
+
201
76
  1. Fork the repository
202
77
  2. Create your feature branch (`git checkout -b feature/amazing-feature`)
203
78
  3. Commit your changes (`git commit -m 'Add some amazing feature'`)
@@ -206,6 +81,8 @@ MIT
206
81
 
207
82
  ## Testing
208
83
 
209
- ```bash
210
- bun test
211
- ```
84
+ Currently, testing is done manually in production. We're working on adding a comprehensive test suite. For now, you can test the functionality by:
85
+
86
+ 1. Deploying to Cloudflare Workers
87
+ 2. Using the example endpoints
88
+ 3. Verifying data persistence
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: DurableObjectState, env: Env);
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
- return this.data[key];
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
- if (this.zodSchema) {
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
- delete this.data[key];
102
- await this.save();
103
- await this.logAudit("delete", key);
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
- return Object.keys(this.data);
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
- return key in this.data;
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,6 +1,6 @@
1
1
  {
2
2
  "name": "jotdb",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -11,15 +11,12 @@
11
11
  },
12
12
  "types": "dist/index.d.ts",
13
13
  "scripts": {
14
- "dev": "wrangler dev --port 5173 --remote",
15
- "test": "bun test jotdb.tests.ts",
14
+ "dev": "wrangler dev --port 5173",
16
15
  "deploy": "wrangler deploy",
17
16
  "patch": "npm version patch && npm publish --access public"
18
17
  },
19
- "dependencies": {
20
- "typescript": "^5.8.3"
21
- },
22
18
  "devDependencies": {
19
+ "typescript": "^5.8.3",
23
20
  "@cloudflare/workers-types": "^4.20250517.0",
24
21
  "hono": "^4.7.10",
25
22
  "wrangler": "^4.15.2",
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
- async logAudit(action: string, keys: string[] | string): Promise<void> {
57
- const entry: AuditLogEntry = {
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 getAll(): Promise<Record<string, unknown>> {
59
+ async push(item: unknown): Promise<void> {
72
60
  await this.load();
73
- return this.data;
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 set<T>(key: string, value: T): Promise<void> {
69
+ async setAll(objOrArr: Record<string, unknown> | unknown[]): Promise<void> {
77
70
  await this.load();
78
- if (this.options.readOnly) throw new Error("JotDB is in read-only mode");
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[key] = value;
74
+ this.data = objOrArr;
85
75
  await this.save();
86
- await this.logAudit("set", key);
76
+ await this.logAudit("setAll", Array.isArray(objOrArr) ? [] : Object.keys(objOrArr));
87
77
  }
88
78
 
89
- async setAll(obj: Record<string, unknown>): Promise<void> {
79
+ async getAll(): Promise<unknown> {
90
80
  await this.load();
91
- if (this.options.readOnly) throw new Error("JotDB is in read-only mode");
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
- if (!this.zodSchema) {
105
- const inferred: SchemaDefinition = {};
106
- for (const [k, v] of Object.entries(obj)) {
107
- inferred[k] = typeOfValue(v) as SchemaType;
108
- }
109
- await this.setSchema(inferred);
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
- if (this.zodSchema) {
113
- if (this.options.autoStrip) {
114
- obj = this.zodSchema.parse(obj); // returns stripped
115
- } else {
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
- delete this.data[key];
128
- await this.save();
129
- await this.logAudit("delete", key);
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
- return Object.keys(this.data);
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
- return key in this.data;
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
- return c.json(results);
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
- return c.json({
322
- error: error instanceof Error ? error.message : String(error),
323
- tests: results.tests,
324
- auditLog: results.auditLog
325
- }, 500);
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
- });