@noy-db/in-rest 0.1.0-pre.3

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/dist/index.js ADDED
@@ -0,0 +1,314 @@
1
+ // src/sessions.ts
2
+ var DEFAULT_SWEEP_INTERVAL_MS = 5 * 60 * 1e3;
3
+ var SessionStore = class {
4
+ sessions = /* @__PURE__ */ new Map();
5
+ ttlMs;
6
+ sweepTimer;
7
+ constructor(ttlSeconds, options = {}) {
8
+ this.ttlMs = ttlSeconds * 1e3;
9
+ const sweepMs = options.sweepIntervalMs ?? DEFAULT_SWEEP_INTERVAL_MS;
10
+ if (sweepMs > 0) {
11
+ const timer = setInterval(() => this.sweep(), sweepMs);
12
+ if (typeof timer.unref === "function") {
13
+ timer.unref();
14
+ }
15
+ this.sweepTimer = timer;
16
+ } else {
17
+ this.sweepTimer = null;
18
+ }
19
+ }
20
+ create(db) {
21
+ const token = crypto.randomUUID();
22
+ this.sessions.set(token, { db, expiresAt: Date.now() + this.ttlMs });
23
+ return token;
24
+ }
25
+ /**
26
+ * Look up a session and refresh its sliding-window TTL. Call from
27
+ * auth-guarded routes that are treated as "activity".
28
+ */
29
+ get(token) {
30
+ const session = this.sessions.get(token);
31
+ if (!session) return null;
32
+ if (Date.now() > session.expiresAt) {
33
+ this.sessions.delete(token);
34
+ return null;
35
+ }
36
+ session.expiresAt = Date.now() + this.ttlMs;
37
+ return session.db;
38
+ }
39
+ delete(token) {
40
+ this.sessions.delete(token);
41
+ }
42
+ /**
43
+ * Non-refreshing existence check. Used by polling endpoints like
44
+ * `GET /sessions/current` that should not extend the session merely
45
+ * by being queried.
46
+ */
47
+ peek(token) {
48
+ const session = this.sessions.get(token);
49
+ if (!session) return false;
50
+ if (Date.now() > session.expiresAt) {
51
+ this.sessions.delete(token);
52
+ return false;
53
+ }
54
+ return true;
55
+ }
56
+ /**
57
+ * Drop every session whose TTL has elapsed. Called automatically by
58
+ * the internal interval timer; exposed so tests can deterministically
59
+ * trigger the sweep without waiting. Returns the count of entries
60
+ * removed — useful for logging / metrics hooks.
61
+ */
62
+ sweep() {
63
+ const now = Date.now();
64
+ let removed = 0;
65
+ for (const [token, session] of this.sessions) {
66
+ if (now > session.expiresAt) {
67
+ this.sessions.delete(token);
68
+ removed++;
69
+ }
70
+ }
71
+ return removed;
72
+ }
73
+ /**
74
+ * Current number of tracked sessions (expired or not). Primarily for
75
+ * tests that verify the sweep is working; operators wanting live
76
+ * metrics should wrap the store or emit their own counters.
77
+ */
78
+ size() {
79
+ return this.sessions.size;
80
+ }
81
+ /**
82
+ * Stop the background sweep timer and drop every session. Call this
83
+ * on shutdown to allow the event loop to exit cleanly — `unref()`
84
+ * handles most cases, but explicit cleanup is cheaper than relying
85
+ * on finalization.
86
+ */
87
+ close() {
88
+ if (this.sweepTimer !== null) clearInterval(this.sweepTimer);
89
+ this.sessions.clear();
90
+ }
91
+ };
92
+
93
+ // src/router.ts
94
+ import { createNoydb, PermissionDeniedError, NotFoundError, ConflictError, ValidationError } from "@noy-db/hub";
95
+
96
+ // src/query-params.ts
97
+ var OP_MAP = {
98
+ eq: "==",
99
+ neq: "!=",
100
+ gt: ">",
101
+ gte: ">=",
102
+ lt: "<",
103
+ lte: "<="
104
+ };
105
+ function parseQueryParams(searchParams) {
106
+ const wheres = searchParams.getAll("where");
107
+ const orderByParam = searchParams.get("orderBy");
108
+ const limitParam = searchParams.get("limit");
109
+ const whereClauses = [];
110
+ for (const clause of wheres) {
111
+ const parts = clause.split(":");
112
+ if (parts.length < 3) {
113
+ return {
114
+ error: { error: "invalid_where", op: clause },
115
+ apply: (q) => q,
116
+ limit: null
117
+ };
118
+ }
119
+ const field = parts[0];
120
+ const opStr = parts[1];
121
+ const value = parts.slice(2).join(":");
122
+ const op = OP_MAP[opStr];
123
+ if (!op) {
124
+ return {
125
+ error: { error: "invalid_op", op: opStr },
126
+ apply: (q) => q,
127
+ limit: null
128
+ };
129
+ }
130
+ whereClauses.push({ field, op, value: coerce(value) });
131
+ }
132
+ let orderBy = null;
133
+ if (orderByParam) {
134
+ const obParts = orderByParam.split(":");
135
+ const obField = obParts[0];
136
+ const obDir = obParts[1];
137
+ orderBy = { field: obField, dir: obDir === "desc" ? "desc" : "asc" };
138
+ }
139
+ const limit = limitParam ? parseInt(limitParam, 10) : null;
140
+ return {
141
+ apply(q) {
142
+ let result = q;
143
+ for (const { field, op, value } of whereClauses) {
144
+ result = result.where(field, op, value);
145
+ }
146
+ if (orderBy) {
147
+ result = result.orderBy(orderBy.field, orderBy.dir);
148
+ }
149
+ return result;
150
+ },
151
+ limit
152
+ };
153
+ }
154
+ function coerce(raw) {
155
+ if (raw === "true") return true;
156
+ if (raw === "false") return false;
157
+ const n = Number(raw);
158
+ if (!isNaN(n) && raw.trim() !== "") return n;
159
+ return raw;
160
+ }
161
+
162
+ // src/router.ts
163
+ function json(status, body) {
164
+ return {
165
+ status,
166
+ headers: { "content-type": "application/json" },
167
+ body: JSON.stringify(body)
168
+ };
169
+ }
170
+ function extractToken(req) {
171
+ const auth = req.headers["authorization"] ?? req.headers["Authorization"];
172
+ if (!auth?.startsWith("Bearer ")) return null;
173
+ return auth.slice(7);
174
+ }
175
+ function buildRouter(store, user, sessions, basePath) {
176
+ function stripBase(pathname) {
177
+ if (!basePath) return pathname;
178
+ if (pathname === basePath) return "/";
179
+ if (pathname.startsWith(basePath + "/")) return pathname.slice(basePath.length);
180
+ return pathname;
181
+ }
182
+ return async function route(req) {
183
+ const path = stripBase(req.pathname);
184
+ const method = req.method.toUpperCase();
185
+ if (method === "POST" && path === "/sessions/unlock/passphrase") {
186
+ let body;
187
+ try {
188
+ body = await req.json();
189
+ } catch {
190
+ return json(400, { error: "invalid_json" });
191
+ }
192
+ const passphrase = body?.passphrase;
193
+ if (typeof passphrase !== "string" || !passphrase) {
194
+ return json(400, { error: "passphrase_required" });
195
+ }
196
+ try {
197
+ const db2 = await createNoydb({ store, user, secret: passphrase });
198
+ const token2 = sessions.create(db2);
199
+ return json(200, { token: token2 });
200
+ } catch (err) {
201
+ if (err instanceof ValidationError) {
202
+ return json(400, { error: "weak_passphrase", message: err.message });
203
+ }
204
+ return json(401, { error: "invalid_passphrase" });
205
+ }
206
+ }
207
+ if (method === "GET" && path === "/sessions/current") {
208
+ const token2 = extractToken(req);
209
+ const active = token2 !== null && sessions.peek(token2);
210
+ return json(200, { active });
211
+ }
212
+ if (method === "DELETE" && path === "/sessions/current") {
213
+ const token2 = extractToken(req);
214
+ if (!token2 || !sessions.peek(token2)) return json(401, { error: "unauthorized" });
215
+ sessions.delete(token2);
216
+ return { status: 204, headers: {}, body: null };
217
+ }
218
+ const token = extractToken(req);
219
+ const db = token ? sessions.get(token) : null;
220
+ if (!db) return json(401, { error: "unauthorized" });
221
+ if (method === "GET" && path === "/vaults") {
222
+ try {
223
+ if (typeof store.listVaults === "function") {
224
+ const vaults = await db.listAccessibleVaults();
225
+ return json(200, vaults.map((v) => v.id));
226
+ }
227
+ return json(200, []);
228
+ } catch {
229
+ return json(200, []);
230
+ }
231
+ }
232
+ const recordMatch = path.match(/^\/vaults\/([^/]+)\/collections\/([^/]+)\/([^/]+)$/);
233
+ if (recordMatch) {
234
+ const vaultName = recordMatch[1];
235
+ const collName = recordMatch[2];
236
+ const id = recordMatch[3];
237
+ if (vaultName === void 0 || collName === void 0 || id === void 0) {
238
+ return json(500, { error: "internal_error" });
239
+ }
240
+ try {
241
+ const vault = await db.openVault(vaultName);
242
+ const coll = vault.collection(collName);
243
+ if (method === "GET") {
244
+ const record = await coll.get(id);
245
+ if (!record) return json(404, { error: "not_found" });
246
+ return json(200, record);
247
+ }
248
+ if (method === "POST") {
249
+ let body;
250
+ try {
251
+ body = await req.json();
252
+ } catch {
253
+ return json(400, { error: "invalid_json" });
254
+ }
255
+ await coll.put(id, body);
256
+ return json(200, { ok: true });
257
+ }
258
+ if (method === "DELETE") {
259
+ await coll.delete(id);
260
+ return json(200, { ok: true });
261
+ }
262
+ return {
263
+ status: 405,
264
+ headers: { "content-type": "application/json", allow: "GET, POST, DELETE" },
265
+ body: JSON.stringify({ error: "method_not_allowed" })
266
+ };
267
+ } catch (err) {
268
+ if (err instanceof PermissionDeniedError) return json(403, { error: "forbidden" });
269
+ if (err instanceof NotFoundError) return json(404, { error: "not_found" });
270
+ if (err instanceof ConflictError) return json(409, { error: "conflict" });
271
+ return json(500, { error: "internal_error" });
272
+ }
273
+ }
274
+ const collMatch = path.match(/^\/vaults\/([^/]+)\/collections\/([^/]+)$/);
275
+ if (collMatch) {
276
+ if (method !== "GET") {
277
+ return {
278
+ status: 405,
279
+ headers: { "content-type": "application/json", allow: "GET" },
280
+ body: JSON.stringify({ error: "method_not_allowed" })
281
+ };
282
+ }
283
+ const vaultName = collMatch[1];
284
+ const collName = collMatch[2];
285
+ if (vaultName === void 0 || collName === void 0) {
286
+ return json(500, { error: "internal_error" });
287
+ }
288
+ const params = parseQueryParams(req.searchParams);
289
+ if (params.error) return json(400, params.error);
290
+ try {
291
+ const vault = await db.openVault(vaultName);
292
+ const coll = vault.collection(collName);
293
+ let results = params.apply(coll.query()).toArray();
294
+ if (params.limit !== null) results = results.slice(0, params.limit);
295
+ return json(200, results);
296
+ } catch (err) {
297
+ if (err instanceof PermissionDeniedError) return json(403, { error: "forbidden" });
298
+ return json(500, { error: "internal_error" });
299
+ }
300
+ }
301
+ return json(404, { error: "not_found" });
302
+ };
303
+ }
304
+
305
+ // src/index.ts
306
+ function createRestHandler(options) {
307
+ const sessions = new SessionStore(options.ttlSeconds ?? 900);
308
+ const route = buildRouter(options.store, options.user, sessions, options.basePath ?? "");
309
+ return { handle: route };
310
+ }
311
+ export {
312
+ createRestHandler
313
+ };
314
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sessions.ts","../src/router.ts","../src/query-params.ts","../src/index.ts"],"sourcesContent":["import type { Noydb } from '@noy-db/hub'\n\ninterface Session {\n db: Noydb\n expiresAt: number\n}\n\n/**\n * Options for `SessionStore`.\n *\n * `sweepIntervalMs` controls the periodic background sweep that drops\n * expired sessions from the map. Without it, expired\n * tokens only clear when a matching `get()` / `peek()` call happens —\n * a long-running process with many short-lived sessions accumulates\n * dead entries unbounded. Default is 5 minutes; pass `0` to disable\n * the sweep (appropriate for tests or very short-lived CLI processes).\n */\nexport interface SessionStoreOptions {\n sweepIntervalMs?: number\n}\n\nconst DEFAULT_SWEEP_INTERVAL_MS = 5 * 60 * 1000\n\nexport class SessionStore {\n private readonly sessions = new Map<string, Session>()\n private readonly ttlMs: number\n private readonly sweepTimer: ReturnType<typeof setInterval> | null\n\n constructor(ttlSeconds: number, options: SessionStoreOptions = {}) {\n this.ttlMs = ttlSeconds * 1000\n const sweepMs = options.sweepIntervalMs ?? DEFAULT_SWEEP_INTERVAL_MS\n if (sweepMs > 0) {\n // Node's setInterval returns a Timeout whose `.unref()` prevents\n // the timer from keeping the event loop alive. That matters in\n // CLI / serverless contexts where the Node process should exit\n // cleanly once the request cycle ends.\n const timer = setInterval(() => this.sweep(), sweepMs)\n if (typeof (timer as { unref?: () => void }).unref === 'function') {\n (timer as { unref: () => void }).unref()\n }\n this.sweepTimer = timer\n } else {\n this.sweepTimer = null\n }\n }\n\n create(db: Noydb): string {\n const token = crypto.randomUUID()\n this.sessions.set(token, { db, expiresAt: Date.now() + this.ttlMs })\n return token\n }\n\n /**\n * Look up a session and refresh its sliding-window TTL. Call from\n * auth-guarded routes that are treated as \"activity\".\n */\n get(token: string): Noydb | null {\n const session = this.sessions.get(token)\n if (!session) return null\n if (Date.now() > session.expiresAt) {\n this.sessions.delete(token)\n return null\n }\n session.expiresAt = Date.now() + this.ttlMs\n return session.db\n }\n\n delete(token: string): void {\n this.sessions.delete(token)\n }\n\n /**\n * Non-refreshing existence check. Used by polling endpoints like\n * `GET /sessions/current` that should not extend the session merely\n * by being queried.\n */\n peek(token: string): boolean {\n const session = this.sessions.get(token)\n if (!session) return false\n if (Date.now() > session.expiresAt) {\n this.sessions.delete(token)\n return false\n }\n return true\n }\n\n /**\n * Drop every session whose TTL has elapsed. Called automatically by\n * the internal interval timer; exposed so tests can deterministically\n * trigger the sweep without waiting. Returns the count of entries\n * removed — useful for logging / metrics hooks.\n */\n sweep(): number {\n const now = Date.now()\n let removed = 0\n for (const [token, session] of this.sessions) {\n if (now > session.expiresAt) {\n this.sessions.delete(token)\n removed++\n }\n }\n return removed\n }\n\n /**\n * Current number of tracked sessions (expired or not). Primarily for\n * tests that verify the sweep is working; operators wanting live\n * metrics should wrap the store or emit their own counters.\n */\n size(): number {\n return this.sessions.size\n }\n\n /**\n * Stop the background sweep timer and drop every session. Call this\n * on shutdown to allow the event loop to exit cleanly — `unref()`\n * handles most cases, but explicit cleanup is cheaper than relying\n * on finalization.\n */\n close(): void {\n if (this.sweepTimer !== null) clearInterval(this.sweepTimer)\n this.sessions.clear()\n }\n}\n","import { createNoydb, PermissionDeniedError, NotFoundError, ConflictError, ValidationError } from '@noy-db/hub'\nimport type { NoydbStore } from '@noy-db/hub'\nimport type { RestRequest, RestResponse } from './index.js'\nimport type { SessionStore } from './sessions.js'\nimport { parseQueryParams } from './query-params.js'\n\nfunction json(status: number, body: unknown): RestResponse {\n return {\n status,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n }\n}\n\nfunction extractToken(req: RestRequest): string | null {\n // HTTP headers are case-insensitive; check both common casings.\n const auth = req.headers['authorization'] ?? req.headers['Authorization']\n if (!auth?.startsWith('Bearer ')) return null\n return auth.slice(7)\n}\n\nexport function buildRouter(store: NoydbStore, user: string, sessions: SessionStore, basePath: string) {\n function stripBase(pathname: string): string {\n // Segment-aware prefix match: basePath '/api' matches '/api' or '/api/...'\n // but NOT '/apifoo' or '/api-other/...'.\n if (!basePath) return pathname\n if (pathname === basePath) return '/'\n if (pathname.startsWith(basePath + '/')) return pathname.slice(basePath.length)\n return pathname\n }\n\n return async function route(req: RestRequest): Promise<RestResponse> {\n const path = stripBase(req.pathname)\n const method = req.method.toUpperCase()\n\n // ── Session routes (no auth required) ─────────────────────────\n\n if (method === 'POST' && path === '/sessions/unlock/passphrase') {\n let body: unknown\n try { body = await req.json() } catch { return json(400, { error: 'invalid_json' }) }\n const passphrase = (body as Record<string, unknown>)?.passphrase\n if (typeof passphrase !== 'string' || !passphrase) {\n return json(400, { error: 'passphrase_required' })\n }\n try {\n const db = await createNoydb({ store, user, secret: passphrase })\n const token = sessions.create(db)\n return json(200, { token })\n } catch (err) {\n if (err instanceof ValidationError) {\n return json(400, { error: 'weak_passphrase', message: err.message })\n }\n return json(401, { error: 'invalid_passphrase' })\n }\n }\n\n if (method === 'GET' && path === '/sessions/current') {\n const token = extractToken(req)\n const active = token !== null && sessions.peek(token)\n return json(200, { active })\n }\n\n if (method === 'DELETE' && path === '/sessions/current') {\n const token = extractToken(req)\n if (!token || !sessions.peek(token)) return json(401, { error: 'unauthorized' })\n sessions.delete(token)\n return { status: 204, headers: {}, body: null }\n }\n\n // ── Auth guard ────────────────────────────────────────────────\n\n const token = extractToken(req)\n const db = token ? sessions.get(token) : null\n if (!db) return json(401, { error: 'unauthorized' })\n\n // ── Vault routes ──────────────────────────────────────────────\n\n if (method === 'GET' && path === '/vaults') {\n try {\n if (typeof store.listVaults === 'function') {\n const vaults = await db.listAccessibleVaults()\n return json(200, vaults.map(v => v.id))\n }\n return json(200, [])\n } catch {\n return json(200, [])\n }\n }\n\n // Match /vaults/:vault/collections/:collection/:id\n const recordMatch = path.match(/^\\/vaults\\/([^/]+)\\/collections\\/([^/]+)\\/([^/]+)$/)\n if (recordMatch) {\n const vaultName = recordMatch[1]\n const collName = recordMatch[2]\n const id = recordMatch[3]\n if (vaultName === undefined || collName === undefined || id === undefined) {\n return json(500, { error: 'internal_error' })\n }\n try {\n const vault = await db.openVault(vaultName)\n const coll = vault.collection<Record<string, unknown>>(collName)\n\n if (method === 'GET') {\n const record = await coll.get(id)\n if (!record) return json(404, { error: 'not_found' })\n return json(200, record)\n }\n\n if (method === 'POST') {\n let body: unknown\n try { body = await req.json() } catch { return json(400, { error: 'invalid_json' }) }\n await coll.put(id, body as Record<string, unknown>)\n return json(200, { ok: true })\n }\n\n if (method === 'DELETE') {\n await coll.delete(id)\n return json(200, { ok: true })\n }\n\n return {\n status: 405,\n headers: { 'content-type': 'application/json', allow: 'GET, POST, DELETE' },\n body: JSON.stringify({ error: 'method_not_allowed' }),\n }\n } catch (err) {\n if (err instanceof PermissionDeniedError) return json(403, { error: 'forbidden' })\n if (err instanceof NotFoundError) return json(404, { error: 'not_found' })\n if (err instanceof ConflictError) return json(409, { error: 'conflict' })\n return json(500, { error: 'internal_error' })\n }\n }\n\n // Match /vaults/:vault/collections/:collection (list)\n const collMatch = path.match(/^\\/vaults\\/([^/]+)\\/collections\\/([^/]+)$/)\n if (collMatch) {\n if (method !== 'GET') {\n return {\n status: 405,\n headers: { 'content-type': 'application/json', allow: 'GET' },\n body: JSON.stringify({ error: 'method_not_allowed' }),\n }\n }\n const vaultName = collMatch[1]\n const collName = collMatch[2]\n if (vaultName === undefined || collName === undefined) {\n return json(500, { error: 'internal_error' })\n }\n const params = parseQueryParams(req.searchParams)\n if (params.error) return json(400, params.error)\n try {\n const vault = await db.openVault(vaultName)\n const coll = vault.collection<Record<string, unknown>>(collName)\n let results = params.apply(coll.query()).toArray()\n if (params.limit !== null) results = results.slice(0, params.limit)\n return json(200, results)\n } catch (err) {\n if (err instanceof PermissionDeniedError) return json(403, { error: 'forbidden' })\n return json(500, { error: 'internal_error' })\n }\n }\n\n return json(404, { error: 'not_found' })\n }\n}\n","import type { Query } from '@noy-db/hub'\n\ntype QueryOp = '==' | '!=' | '>' | '>=' | '<' | '<='\n\nconst OP_MAP: Record<string, QueryOp> = {\n eq: '==',\n neq: '!=',\n gt: '>',\n gte: '>=',\n lt: '<',\n lte: '<=',\n}\n\nexport interface ParsedQueryParams {\n error?: { error: string; op?: string }\n apply<T>(q: Query<T>): Query<T>\n limit: number | null\n}\n\nexport function parseQueryParams(searchParams: URLSearchParams): ParsedQueryParams {\n const wheres = searchParams.getAll('where')\n const orderByParam = searchParams.get('orderBy')\n const limitParam = searchParams.get('limit')\n\n const whereClauses: Array<{ field: string; op: QueryOp; value: unknown }> = []\n\n for (const clause of wheres) {\n const parts = clause.split(':')\n if (parts.length < 3) {\n return {\n error: { error: 'invalid_where', op: clause },\n apply: (q) => q,\n limit: null,\n }\n }\n const field = parts[0] as string\n const opStr = parts[1] as string\n const value = parts.slice(2).join(':')\n const op = OP_MAP[opStr]\n if (!op) {\n return {\n error: { error: 'invalid_op', op: opStr },\n apply: (q) => q,\n limit: null,\n }\n }\n whereClauses.push({ field, op, value: coerce(value) })\n }\n\n let orderBy: { field: string; dir: 'asc' | 'desc' } | null = null\n if (orderByParam) {\n const obParts = orderByParam.split(':')\n const obField = obParts[0] as string\n const obDir = obParts[1]\n orderBy = { field: obField, dir: obDir === 'desc' ? 'desc' : 'asc' }\n }\n\n const limit = limitParam ? parseInt(limitParam, 10) : null\n\n return {\n apply<T>(q: Query<T>): Query<T> {\n let result = q\n for (const { field, op, value } of whereClauses) {\n result = result.where(field, op, value as T[keyof T & string])\n }\n if (orderBy) {\n result = result.orderBy(orderBy.field, orderBy.dir)\n }\n return result\n },\n limit,\n }\n}\n\nfunction coerce(raw: string): unknown {\n if (raw === 'true') return true\n if (raw === 'false') return false\n const n = Number(raw)\n if (!isNaN(n) && raw.trim() !== '') return n\n return raw\n}\n","/**\n * **@noy-db/in-rest** — Framework-neutral REST API integration for noy-db.\n *\n * @example\n * ```ts\n * import { createRestHandler } from '@noy-db/in-rest'\n * import { honoAdapter } from '@noy-db/in-rest/hono'\n *\n * const handler = createRestHandler({ store, user: 'api' })\n * app.route('/api/noydb', honoAdapter(handler))\n * ```\n *\n * @packageDocumentation\n */\n\nimport type { NoydbStore } from '@noy-db/hub'\nimport { SessionStore } from './sessions.js'\nimport { buildRouter } from './router.js'\n\nexport interface RestRequest {\n readonly method: string\n readonly pathname: string\n readonly searchParams: URLSearchParams\n readonly headers: Record<string, string>\n json(): Promise<unknown>\n}\n\nexport interface RestResponse {\n readonly status: number\n readonly headers: Record<string, string>\n readonly body: string | Uint8Array | null\n}\n\nexport interface NoydbRestHandler {\n handle(req: RestRequest): Promise<RestResponse>\n}\n\nexport interface RestHandlerOptions {\n readonly store: NoydbStore\n readonly user: string\n readonly ttlSeconds?: number\n readonly basePath?: string\n}\n\nexport function createRestHandler(options: RestHandlerOptions): NoydbRestHandler {\n const sessions = new SessionStore(options.ttlSeconds ?? 900)\n const route = buildRouter(options.store, options.user, sessions, options.basePath ?? '')\n return { handle: route }\n}\n"],"mappings":"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","names":["db","token"]}
package/package.json ADDED
@@ -0,0 +1,123 @@
1
+ {
2
+ "name": "@noy-db/in-rest",
3
+ "version": "0.1.0-pre.3",
4
+ "description": "Framework-neutral REST API integration for noy-db — createRestHandler with Hono, Express, Fastify, and Nitro subpath adapters.",
5
+ "license": "MIT",
6
+ "author": "vLannaAi <vicio@lanna.ai>",
7
+ "homepage": "https://github.com/vLannaAi/noy-db/tree/main/packages/in-rest#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/vLannaAi/noy-db.git",
11
+ "directory": "packages/in-rest"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/vLannaAi/noy-db/issues"
15
+ },
16
+ "type": "module",
17
+ "sideEffects": false,
18
+ "exports": {
19
+ ".": {
20
+ "import": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "require": {
25
+ "types": "./dist/index.d.cts",
26
+ "default": "./dist/index.cjs"
27
+ }
28
+ },
29
+ "./hono": {
30
+ "import": {
31
+ "types": "./dist/adapters/hono.d.ts",
32
+ "default": "./dist/adapters/hono.js"
33
+ },
34
+ "require": {
35
+ "types": "./dist/adapters/hono.d.cts",
36
+ "default": "./dist/adapters/hono.cjs"
37
+ }
38
+ },
39
+ "./express": {
40
+ "import": {
41
+ "types": "./dist/adapters/express.d.ts",
42
+ "default": "./dist/adapters/express.js"
43
+ },
44
+ "require": {
45
+ "types": "./dist/adapters/express.d.cts",
46
+ "default": "./dist/adapters/express.cjs"
47
+ }
48
+ },
49
+ "./fastify": {
50
+ "import": {
51
+ "types": "./dist/adapters/fastify.d.ts",
52
+ "default": "./dist/adapters/fastify.js"
53
+ },
54
+ "require": {
55
+ "types": "./dist/adapters/fastify.d.cts",
56
+ "default": "./dist/adapters/fastify.cjs"
57
+ }
58
+ },
59
+ "./nitro": {
60
+ "import": {
61
+ "types": "./dist/adapters/nitro.d.ts",
62
+ "default": "./dist/adapters/nitro.js"
63
+ },
64
+ "require": {
65
+ "types": "./dist/adapters/nitro.d.cts",
66
+ "default": "./dist/adapters/nitro.cjs"
67
+ }
68
+ }
69
+ },
70
+ "main": "./dist/index.cjs",
71
+ "module": "./dist/index.js",
72
+ "types": "./dist/index.d.ts",
73
+ "files": [
74
+ "dist",
75
+ "README.md",
76
+ "LICENSE"
77
+ ],
78
+ "engines": {
79
+ "node": ">=18.0.0"
80
+ },
81
+ "peerDependencies": {
82
+ "@noy-db/hub": "0.1.0-pre.3"
83
+ },
84
+ "peerDependenciesMeta": {
85
+ "hono": {
86
+ "optional": true
87
+ },
88
+ "express": {
89
+ "optional": true
90
+ },
91
+ "fastify": {
92
+ "optional": true
93
+ },
94
+ "h3": {
95
+ "optional": true
96
+ }
97
+ },
98
+ "devDependencies": {
99
+ "@types/express": "^5.0.0",
100
+ "express": "^5.0.0",
101
+ "fastify": "^5.0.0",
102
+ "h3": "^1.13.0",
103
+ "hono": "^4.0.0",
104
+ "@noy-db/hub": "0.1.0-pre.3"
105
+ },
106
+ "keywords": [
107
+ "noy-db",
108
+ "in-rest",
109
+ "rest",
110
+ "http",
111
+ "api"
112
+ ],
113
+ "publishConfig": {
114
+ "access": "public",
115
+ "tag": "latest"
116
+ },
117
+ "scripts": {
118
+ "build": "tsup",
119
+ "test": "vitest run",
120
+ "lint": "eslint src/",
121
+ "typecheck": "tsc --noEmit"
122
+ }
123
+ }