jotdb 0.1.0 → 0.1.2

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 CHANGED
@@ -3,6 +3,9 @@
3
3
  "workspaces": {
4
4
  "": {
5
5
  "name": "jotdb",
6
+ "dependencies": {
7
+ "typescript": "^5.8.3",
8
+ },
6
9
  "devDependencies": {
7
10
  "@cloudflare/workers-types": "^4.20250517.0",
8
11
  "hono": "^4.7.10",
@@ -196,6 +199,8 @@
196
199
 
197
200
  "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
198
201
 
202
+ "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
203
+
199
204
  "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
200
205
 
201
206
  "undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
@@ -0,0 +1,49 @@
1
+ import type { DurableObjectState } from "@cloudflare/workers-types";
2
+ import { Hono } from 'hono';
3
+ import { DurableObject } from "cloudflare:workers";
4
+ type SchemaType = "string" | "number" | "boolean" | "email" | "array" | "object" | "any";
5
+ type SchemaDefinition = Record<string, SchemaType>;
6
+ interface JotDBOptions {
7
+ autoStrip: boolean;
8
+ readOnly: boolean;
9
+ }
10
+ interface AuditLogEntry {
11
+ timestamp: number;
12
+ action: string;
13
+ keys: string[];
14
+ }
15
+ export declare class JotDB extends DurableObject {
16
+ private data;
17
+ private rawSchema;
18
+ private zodSchema;
19
+ private options;
20
+ private auditLog;
21
+ constructor(state: DurableObjectState, env: Env);
22
+ load(): Promise<void>;
23
+ save(): Promise<void>;
24
+ logAudit(action: string, keys: string[] | string): Promise<void>;
25
+ 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
+ delete(key: string): Promise<void>;
30
+ clear(): Promise<void>;
31
+ keys(): Promise<string[]>;
32
+ has(key: string): Promise<boolean>;
33
+ getSchema(): Promise<SchemaDefinition>;
34
+ private warnSchemaDiff;
35
+ setSchema(schemaObj: SchemaDefinition): Promise<void>;
36
+ setOptions(opts: Partial<JotDBOptions>): Promise<void>;
37
+ getOptions(): Promise<JotDBOptions>;
38
+ getAuditLog(): Promise<AuditLogEntry[]>;
39
+ clearAuditLog(): Promise<void>;
40
+ private buildZodSchema;
41
+ fetch(request: Request): Promise<Response>;
42
+ }
43
+ export interface Env {
44
+ JOTDB: DurableObjectNamespace;
45
+ }
46
+ declare const app: Hono<{
47
+ Bindings: Env;
48
+ }, import("hono/types").BlankSchema, "/">;
49
+ export default app;
package/dist/index.js ADDED
@@ -0,0 +1,279 @@
1
+ import { z } 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
+ export class JotDB extends DurableObject {
7
+ constructor(state, env) {
8
+ super(state, env);
9
+ this.data = {};
10
+ this.rawSchema = {};
11
+ this.zodSchema = null;
12
+ this.options = {
13
+ autoStrip: false,
14
+ readOnly: false,
15
+ };
16
+ this.auditLog = [];
17
+ }
18
+ async load() {
19
+ if (Object.keys(this.data).length === 0) {
20
+ this.data = (await this.ctx.storage.get("data")) || {};
21
+ }
22
+ if (Object.keys(this.rawSchema).length === 0) {
23
+ this.rawSchema = (await this.ctx.storage.get("__schema__")) || {};
24
+ if (Object.keys(this.rawSchema).length > 0) {
25
+ this.zodSchema = this.buildZodSchema(this.rawSchema);
26
+ }
27
+ }
28
+ const storedOptions = await this.ctx.storage.get("__options__");
29
+ if (storedOptions)
30
+ this.options = storedOptions;
31
+ this.auditLog = (await this.ctx.storage.get("__audit__")) || [];
32
+ }
33
+ async save() {
34
+ await this.ctx.storage.put("data", this.data);
35
+ }
36
+ async logAudit(action, keys) {
37
+ const entry = {
38
+ timestamp: Date.now(),
39
+ action,
40
+ keys: Array.isArray(keys) ? keys : [keys],
41
+ };
42
+ this.auditLog.unshift(entry);
43
+ await this.ctx.storage.put("__audit__", this.auditLog.slice(0, 100)); // keep max 100 entries
44
+ }
45
+ async get(key) {
46
+ 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);
86
+ }
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));
98
+ }
99
+ async delete(key) {
100
+ await this.load();
101
+ delete this.data[key];
102
+ await this.save();
103
+ await this.logAudit("delete", key);
104
+ }
105
+ async clear() {
106
+ this.data = {};
107
+ await this.save();
108
+ await this.logAudit("clear", []);
109
+ }
110
+ async keys() {
111
+ await this.load();
112
+ return Object.keys(this.data);
113
+ }
114
+ async has(key) {
115
+ await this.load();
116
+ return key in this.data;
117
+ }
118
+ async getSchema() {
119
+ await this.load();
120
+ return this.rawSchema;
121
+ }
122
+ warnSchemaDiff(newSchema) {
123
+ const current = this.rawSchema;
124
+ for (const key in newSchema) {
125
+ if (!(key in current))
126
+ console.warn(`[JotDB] New key added: ${key}`);
127
+ else if (newSchema[key] !== current[key]) {
128
+ console.warn(`[JotDB] Type changed for "${key}": ${current[key]} → ${newSchema[key]}`);
129
+ }
130
+ }
131
+ for (const key in current) {
132
+ if (!(key in newSchema)) {
133
+ console.warn(`[JotDB] Key removed: ${key}`);
134
+ }
135
+ }
136
+ }
137
+ async setSchema(schemaObj) {
138
+ await this.load();
139
+ if (Object.keys(this.rawSchema).length > 0) {
140
+ this.warnSchemaDiff(schemaObj);
141
+ }
142
+ this.rawSchema = schemaObj;
143
+ this.zodSchema = this.buildZodSchema(schemaObj);
144
+ await this.ctx.storage.put("__schema__", schemaObj);
145
+ }
146
+ async setOptions(opts) {
147
+ await this.load();
148
+ Object.assign(this.options, opts);
149
+ await this.ctx.storage.put("__options__", this.options);
150
+ }
151
+ async getOptions() {
152
+ await this.load();
153
+ return this.options;
154
+ }
155
+ async getAuditLog() {
156
+ await this.load();
157
+ return this.auditLog;
158
+ }
159
+ async clearAuditLog() {
160
+ this.auditLog = [];
161
+ await this.ctx.storage.put("__audit__", []);
162
+ }
163
+ buildZodSchema(schema) {
164
+ const shape = {};
165
+ for (const [key, type] of Object.entries(schema)) {
166
+ switch (type) {
167
+ case "string":
168
+ shape[key] = z.string();
169
+ break;
170
+ case "number":
171
+ shape[key] = z.number();
172
+ break;
173
+ case "boolean":
174
+ shape[key] = z.boolean();
175
+ break;
176
+ case "email":
177
+ shape[key] = z.string().email();
178
+ break;
179
+ case "array":
180
+ shape[key] = z.array(z.any());
181
+ break;
182
+ case "object":
183
+ shape[key] = z.record(z.any());
184
+ break;
185
+ default:
186
+ shape[key] = z.any();
187
+ }
188
+ }
189
+ return z.object(shape);
190
+ }
191
+ async fetch(request) {
192
+ return new Response("Hello, World!");
193
+ }
194
+ }
195
+ const app = new Hono();
196
+ // Middleware
197
+ app.use('*', cors());
198
+ app.use('*', prettyJSON());
199
+ // Test endpoint
200
+ app.get('/test', async (c) => {
201
+ const id = c.env.JOTDB.idFromName("test-db");
202
+ const db = c.env.JOTDB.get(id);
203
+ const results = {
204
+ timestamp: Date.now(),
205
+ tests: [],
206
+ auditLog: []
207
+ };
208
+ try {
209
+ // Test 1: Basic set/get
210
+ await db.set("test1", "hello");
211
+ const value1 = await db.get("test1");
212
+ results.tests.push({
213
+ name: "Basic set/get",
214
+ passed: value1 === "hello",
215
+ value: value1
216
+ });
217
+ // Test 2: Schema validation
218
+ await db.setSchema({
219
+ name: "string",
220
+ age: "number",
221
+ email: "email"
222
+ });
223
+ await db.setAll({
224
+ name: "John",
225
+ age: 30,
226
+ email: "john@example.com"
227
+ });
228
+ const all = await db.getAll();
229
+ results.tests.push({
230
+ name: "Schema validation",
231
+ passed: all.name === "John" && all.age === 30,
232
+ value: all
233
+ });
234
+ // Test 3: Read-only mode
235
+ await db.setOptions({ readOnly: true });
236
+ try {
237
+ await db.set("test3", "should fail");
238
+ results.tests.push({
239
+ name: "Read-only mode",
240
+ passed: false,
241
+ error: "Should have thrown"
242
+ });
243
+ }
244
+ catch (e) {
245
+ results.tests.push({
246
+ name: "Read-only mode",
247
+ passed: true,
248
+ error: e instanceof Error ? e.message : String(e)
249
+ });
250
+ }
251
+ // Test 4: Auto-strip mode
252
+ await db.setOptions({ readOnly: false, autoStrip: true });
253
+ await db.setAll({
254
+ name: "Jane",
255
+ age: 25,
256
+ email: "jane@example.com",
257
+ extra: "should be stripped"
258
+ });
259
+ const stripped = await db.getAll();
260
+ results.tests.push({
261
+ name: "Auto-strip mode",
262
+ passed: !("extra" in stripped),
263
+ value: stripped
264
+ });
265
+ // Get audit log
266
+ results.auditLog = await db.getAuditLog();
267
+ return c.json(results);
268
+ }
269
+ catch (error) {
270
+ return c.json({
271
+ error: error instanceof Error ? error.message : String(error),
272
+ tests: results.tests,
273
+ auditLog: results.auditLog
274
+ }, 500);
275
+ }
276
+ });
277
+ // Health check endpoint
278
+ app.get('/', (c) => c.text('JotDB Durable Object'));
279
+ export default app;
package/jotdb.tests.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { assert, describe, it, beforeEach } from "bun:test";
2
- import { JotDB } from "./src/JotDB.ts";
2
+ import { JotDB } from "./src/index.ts";
3
3
 
4
4
  // Fake DurableObjectState stub for unit testing
5
5
  function createFakeState(): any {
package/package.json CHANGED
@@ -1,17 +1,29 @@
1
1
  {
2
2
  "name": "jotdb",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
+ "main": "dist/index.js",
6
+ "exports": {
7
+ ".": "./dist/index.js"
8
+ },
9
+ "types": "dist/index.d.ts",
5
10
  "scripts": {
6
11
  "dev": "wrangler dev --port 5173 --remote",
7
12
  "test": "bun test jotdb.tests.ts",
8
- "deploy": "wrangler deploy"
13
+ "deploy": "wrangler deploy",
14
+ "patch": "npm version patch && npm publish --access public"
15
+ },
16
+ "dependencies": {
17
+ "typescript": "^5.8.3"
9
18
  },
10
- "dependencies": {},
11
19
  "devDependencies": {
12
20
  "@cloudflare/workers-types": "^4.20250517.0",
13
21
  "hono": "^4.7.10",
14
22
  "wrangler": "^4.15.2",
15
23
  "zod": "^3.24.4"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/acoyfellow/jotdb.git"
16
28
  }
17
29
  }
package/src/index.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { z, ZodTypeAny, ZodObject } from "zod";
2
- import type { DurableObjectState } from "@cloudflare/workers-types";
3
2
  import { Hono } from 'hono';
4
3
  import { cors } from 'hono/cors';
5
4
  import { prettyJSON } from 'hono/pretty-json';
@@ -21,7 +20,6 @@ interface AuditLogEntry {
21
20
  }
22
21
 
23
22
  export class JotDB extends DurableObject {
24
- private ctx: DurableObjectState;
25
23
  private data: Record<string, unknown> = {};
26
24
  private rawSchema: SchemaDefinition = {};
27
25
  private zodSchema: ZodObject<any> | null = null;
@@ -31,9 +29,8 @@ export class JotDB extends DurableObject {
31
29
  };
32
30
  private auditLog: AuditLogEntry[] = [];
33
31
 
34
- constructor(state: DurableObjectState, env: Env) {
32
+ constructor(state: any, env: Env) {
35
33
  super(state, env);
36
- this.ctx = state;
37
34
  }
38
35
 
39
36
  async load(): Promise<void> {
@@ -229,6 +226,10 @@ export class JotDB extends DurableObject {
229
226
  }
230
227
  return z.object(shape);
231
228
  }
229
+
230
+ async fetch(request: Request) {
231
+ return new Response("Hello, World!");
232
+ }
232
233
  }
233
234
 
234
235
  export interface Env {
package/tsconfig.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "target": "ES2020",
4
- "module": "ES2020",
4
+ "module": "ESNext",
5
5
  "lib": [
6
6
  "ES2020"
7
7
  ],
8
8
  "types": [
9
9
  "@cloudflare/workers-types"
10
10
  ],
11
+ "declaration": true,
12
+ "emitDeclarationOnly": false,
13
+ "outDir": "dist",
11
14
  "moduleResolution": "node",
12
15
  "strict": true,
13
16
  "noImplicitAny": true,
package/wrangler.jsonc CHANGED
@@ -2,7 +2,6 @@
2
2
  "$schema": "node_modules/wrangler/config-schema.json",
3
3
  "name": "jotdb",
4
4
  "main": "src/index.ts",
5
- "account_id": "bfcb6ac5b3ceaf42a09607f6f7925823",
6
5
  "compatibility_date": "2024-05-01",
7
6
  "compatibility_flags": [
8
7
  "nodejs_compat"