@fortemi/core 2026.5.0
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/LICENSE +235 -0
- package/README.md +66 -0
- package/dist/index.d.ts +2375 -0
- package/dist/index.js +5138 -0
- package/dist/index.js.map +1 -0
- package/dist/service-worker/sw.js +155 -0
- package/dist/service-worker/sw.js.map +1 -0
- package/dist/worker/pglite-worker.js +122 -0
- package/dist/worker/pglite-worker.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// src/service-worker/routes.ts
|
|
2
|
+
async function parseJsonBody(request) {
|
|
3
|
+
try {
|
|
4
|
+
return await request.json();
|
|
5
|
+
} catch {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function jsonResponse(data, status = 200) {
|
|
10
|
+
return new Response(JSON.stringify(data), {
|
|
11
|
+
status,
|
|
12
|
+
headers: { "Content-Type": "application/json" }
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function errorResponse(message, status) {
|
|
16
|
+
return jsonResponse({ error: message }, status);
|
|
17
|
+
}
|
|
18
|
+
var DB_NOT_CONNECTED = "Database not connected. REST API available in standalone mode only.";
|
|
19
|
+
function createRoutes() {
|
|
20
|
+
return [
|
|
21
|
+
// GET /api/v1/notes — list notes
|
|
22
|
+
{
|
|
23
|
+
method: "GET",
|
|
24
|
+
pattern: /^\/api\/v1\/notes\/?$/,
|
|
25
|
+
handler: async () => {
|
|
26
|
+
return errorResponse(DB_NOT_CONNECTED, 503);
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
// POST /api/v1/notes — create note
|
|
30
|
+
{
|
|
31
|
+
method: "POST",
|
|
32
|
+
pattern: /^\/api\/v1\/notes\/?$/,
|
|
33
|
+
handler: async (request) => {
|
|
34
|
+
const body = await parseJsonBody(request);
|
|
35
|
+
if (body === null) return errorResponse("Invalid JSON body", 400);
|
|
36
|
+
return errorResponse(DB_NOT_CONNECTED, 503);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
// POST /api/v1/notes/:id/restore — restore soft-deleted note (must come before /:id)
|
|
40
|
+
{
|
|
41
|
+
method: "POST",
|
|
42
|
+
pattern: /^\/api\/v1\/notes\/([^/]+)\/restore\/?$/,
|
|
43
|
+
handler: async () => {
|
|
44
|
+
return errorResponse(DB_NOT_CONNECTED, 503);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
// POST /api/v1/notes/:id/star — star or unstar a note
|
|
48
|
+
{
|
|
49
|
+
method: "POST",
|
|
50
|
+
pattern: /^\/api\/v1\/notes\/([^/]+)\/star\/?$/,
|
|
51
|
+
handler: async () => {
|
|
52
|
+
return errorResponse(DB_NOT_CONNECTED, 503);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
// POST /api/v1/notes/:id/archive — archive or unarchive a note
|
|
56
|
+
{
|
|
57
|
+
method: "POST",
|
|
58
|
+
pattern: /^\/api\/v1\/notes\/([^/]+)\/archive\/?$/,
|
|
59
|
+
handler: async () => {
|
|
60
|
+
return errorResponse(DB_NOT_CONNECTED, 503);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
// GET /api/v1/notes/:id — get a single note
|
|
64
|
+
{
|
|
65
|
+
method: "GET",
|
|
66
|
+
pattern: /^\/api\/v1\/notes\/([^/]+)\/?$/,
|
|
67
|
+
handler: async () => {
|
|
68
|
+
return errorResponse(DB_NOT_CONNECTED, 503);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
// PUT /api/v1/notes/:id — update a note
|
|
72
|
+
{
|
|
73
|
+
method: "PUT",
|
|
74
|
+
pattern: /^\/api\/v1\/notes\/([^/]+)\/?$/,
|
|
75
|
+
handler: async () => {
|
|
76
|
+
return errorResponse(DB_NOT_CONNECTED, 503);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
// DELETE /api/v1/notes/:id — soft-delete a note
|
|
80
|
+
{
|
|
81
|
+
method: "DELETE",
|
|
82
|
+
pattern: /^\/api\/v1\/notes\/([^/]+)\/?$/,
|
|
83
|
+
handler: async () => {
|
|
84
|
+
return errorResponse(DB_NOT_CONNECTED, 503);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
// GET /api/v1/search — full-text search
|
|
88
|
+
{
|
|
89
|
+
method: "GET",
|
|
90
|
+
pattern: /^\/api\/v1\/search\/?$/,
|
|
91
|
+
handler: async () => {
|
|
92
|
+
return errorResponse(DB_NOT_CONNECTED, 503);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
function matchRoute(routes2, request, url) {
|
|
98
|
+
for (const route of routes2) {
|
|
99
|
+
if (request.method !== route.method) continue;
|
|
100
|
+
const match = url.pathname.match(route.pattern);
|
|
101
|
+
if (match) return route;
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/service-worker/sw.ts
|
|
107
|
+
var API_PREFIX = "/api/v1/";
|
|
108
|
+
var MCP_PREFIX = "/mcp/";
|
|
109
|
+
var routes = createRoutes();
|
|
110
|
+
self.addEventListener("install", (event) => {
|
|
111
|
+
event.waitUntil(self.skipWaiting());
|
|
112
|
+
});
|
|
113
|
+
self.addEventListener("activate", (event) => {
|
|
114
|
+
event.waitUntil(self.clients.claim());
|
|
115
|
+
});
|
|
116
|
+
self.addEventListener("fetch", (event) => {
|
|
117
|
+
const url = new URL(event.request.url);
|
|
118
|
+
if (url.origin !== self.location.origin) return;
|
|
119
|
+
if (url.pathname.startsWith(API_PREFIX) || url.pathname.startsWith(MCP_PREFIX)) {
|
|
120
|
+
event.respondWith(handleRequest(event.request, url));
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
async function handleRequest(request, url) {
|
|
124
|
+
if (url.pathname.startsWith(MCP_PREFIX)) {
|
|
125
|
+
return new Response(
|
|
126
|
+
JSON.stringify({
|
|
127
|
+
jsonrpc: "2.0",
|
|
128
|
+
error: { code: -32601, message: "Not implemented" }
|
|
129
|
+
}),
|
|
130
|
+
{
|
|
131
|
+
status: 501,
|
|
132
|
+
headers: { "Content-Type": "application/json" }
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (url.pathname.startsWith(API_PREFIX)) {
|
|
137
|
+
const route = matchRoute(routes, request, url);
|
|
138
|
+
if (route) {
|
|
139
|
+
const match = url.pathname.match(route.pattern);
|
|
140
|
+
return route.handler(request, match, url.searchParams);
|
|
141
|
+
}
|
|
142
|
+
return new Response(
|
|
143
|
+
JSON.stringify({
|
|
144
|
+
error: "Not found",
|
|
145
|
+
path: url.pathname
|
|
146
|
+
}),
|
|
147
|
+
{
|
|
148
|
+
status: 404,
|
|
149
|
+
headers: { "Content-Type": "application/json" }
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
return fetch(request);
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=sw.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/service-worker/routes.ts","../../src/service-worker/sw.ts"],"sourcesContent":["/**\n * REST route definitions for Service Worker.\n * These are pure functions that transform HTTP Request → tool input and tool output → Response.\n * The actual DB connection is injected at registration time.\n *\n * All routes currently return 503 Not Implemented — the DB wiring happens in a later issue.\n * The URL structure and request/response shapes are the valuable contract defined here.\n */\n\nexport interface RouteHandler {\n method: string\n pattern: RegExp\n handler: (request: Request, match: RegExpMatchArray, params: URLSearchParams) => Promise<Response>\n}\n\n/** Parse JSON body safely — returns null when the body is absent or malformed */\nasync function parseJsonBody(request: Request): Promise<unknown> {\n try {\n return await request.json()\n } catch {\n return null\n }\n}\n\n/** Create a JSON response */\nfunction jsonResponse(data: unknown, status = 200): Response {\n return new Response(JSON.stringify(data), {\n status,\n headers: { 'Content-Type': 'application/json' },\n })\n}\n\n/** Create an error response */\nfunction errorResponse(message: string, status: number): Response {\n return jsonResponse({ error: message }, status)\n}\n\nconst DB_NOT_CONNECTED = 'Database not connected. REST API available in standalone mode only.'\n\n/**\n * Create route handlers.\n * db parameter will be injected when the SW gets access to PGlite.\n * For now, returns 503 Not Implemented for all routes.\n */\nexport function createRoutes(): RouteHandler[] {\n return [\n // GET /api/v1/notes — list notes\n {\n method: 'GET',\n pattern: /^\\/api\\/v1\\/notes\\/?$/,\n handler: async () => {\n return errorResponse(DB_NOT_CONNECTED, 503)\n },\n },\n\n // POST /api/v1/notes — create note\n {\n method: 'POST',\n pattern: /^\\/api\\/v1\\/notes\\/?$/,\n handler: async (request) => {\n const body = await parseJsonBody(request)\n if (body === null) return errorResponse('Invalid JSON body', 400)\n return errorResponse(DB_NOT_CONNECTED, 503)\n },\n },\n\n // POST /api/v1/notes/:id/restore — restore soft-deleted note (must come before /:id)\n {\n method: 'POST',\n pattern: /^\\/api\\/v1\\/notes\\/([^/]+)\\/restore\\/?$/,\n handler: async () => {\n return errorResponse(DB_NOT_CONNECTED, 503)\n },\n },\n\n // POST /api/v1/notes/:id/star — star or unstar a note\n {\n method: 'POST',\n pattern: /^\\/api\\/v1\\/notes\\/([^/]+)\\/star\\/?$/,\n handler: async () => {\n return errorResponse(DB_NOT_CONNECTED, 503)\n },\n },\n\n // POST /api/v1/notes/:id/archive — archive or unarchive a note\n {\n method: 'POST',\n pattern: /^\\/api\\/v1\\/notes\\/([^/]+)\\/archive\\/?$/,\n handler: async () => {\n return errorResponse(DB_NOT_CONNECTED, 503)\n },\n },\n\n // GET /api/v1/notes/:id — get a single note\n {\n method: 'GET',\n pattern: /^\\/api\\/v1\\/notes\\/([^/]+)\\/?$/,\n handler: async () => {\n return errorResponse(DB_NOT_CONNECTED, 503)\n },\n },\n\n // PUT /api/v1/notes/:id — update a note\n {\n method: 'PUT',\n pattern: /^\\/api\\/v1\\/notes\\/([^/]+)\\/?$/,\n handler: async () => {\n return errorResponse(DB_NOT_CONNECTED, 503)\n },\n },\n\n // DELETE /api/v1/notes/:id — soft-delete a note\n {\n method: 'DELETE',\n pattern: /^\\/api\\/v1\\/notes\\/([^/]+)\\/?$/,\n handler: async () => {\n return errorResponse(DB_NOT_CONNECTED, 503)\n },\n },\n\n // GET /api/v1/search — full-text search\n {\n method: 'GET',\n pattern: /^\\/api\\/v1\\/search\\/?$/,\n handler: async () => {\n return errorResponse(DB_NOT_CONNECTED, 503)\n },\n },\n ]\n}\n\n/**\n * Match a request against the registered routes and return the first matching\n * handler, or null if no route matches.\n */\nexport function matchRoute(routes: RouteHandler[], request: Request, url: URL): RouteHandler | null {\n for (const route of routes) {\n if (request.method !== route.method) continue\n const match = url.pathname.match(route.pattern)\n if (match) return route\n }\n return null\n}\n","/// <reference lib=\"webworker\" />\ndeclare const self: ServiceWorkerGlobalScope\n\nimport { createRoutes, matchRoute } from './routes.js'\n\nconst API_PREFIX = '/api/v1/'\nconst MCP_PREFIX = '/mcp/'\n\nconst routes = createRoutes()\n\nself.addEventListener('install', (event) => {\n event.waitUntil(self.skipWaiting())\n})\n\nself.addEventListener('activate', (event) => {\n event.waitUntil(self.clients.claim())\n})\n\nself.addEventListener('fetch', (event) => {\n const url = new URL(event.request.url)\n\n // Only intercept our API and MCP paths on same origin\n if (url.origin !== self.location.origin) return\n\n if (url.pathname.startsWith(API_PREFIX) || url.pathname.startsWith(MCP_PREFIX)) {\n event.respondWith(handleRequest(event.request, url))\n }\n})\n\nasync function handleRequest(request: Request, url: URL): Promise<Response> {\n if (url.pathname.startsWith(MCP_PREFIX)) {\n return new Response(\n JSON.stringify({\n jsonrpc: '2.0',\n error: { code: -32601, message: 'Not implemented' },\n }),\n {\n status: 501,\n headers: { 'Content-Type': 'application/json' },\n },\n )\n }\n\n if (url.pathname.startsWith(API_PREFIX)) {\n const route = matchRoute(routes, request, url)\n if (route) {\n const match = url.pathname.match(route.pattern)!\n return route.handler(request, match, url.searchParams)\n }\n\n return new Response(\n JSON.stringify({\n error: 'Not found',\n path: url.pathname,\n }),\n {\n status: 404,\n headers: { 'Content-Type': 'application/json' },\n },\n )\n }\n\n return fetch(request)\n}\n"],"mappings":"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","names":["routes"]}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// src/worker/pglite-worker.ts
|
|
2
|
+
import { PGlite } from "@electric-sql/pglite";
|
|
3
|
+
import { vector } from "@electric-sql/pglite/vector";
|
|
4
|
+
var db = null;
|
|
5
|
+
var transactions = /* @__PURE__ */ new Map();
|
|
6
|
+
function respond(msg) {
|
|
7
|
+
self.postMessage(msg);
|
|
8
|
+
}
|
|
9
|
+
async function initDb(persistence, archiveName) {
|
|
10
|
+
const dataDir = persistence === "memory" ? void 0 : persistence === "opfs" ? `opfs-ahp://fortemi-${archiveName}` : `idb://fortemi-${archiveName}`;
|
|
11
|
+
const options = {
|
|
12
|
+
database: "postgres",
|
|
13
|
+
extensions: { vector }
|
|
14
|
+
};
|
|
15
|
+
if (dataDir) {
|
|
16
|
+
options.dataDir = dataDir;
|
|
17
|
+
}
|
|
18
|
+
db = await PGlite.create(options);
|
|
19
|
+
await db.exec("CREATE EXTENSION IF NOT EXISTS vector");
|
|
20
|
+
respond({ type: "READY" });
|
|
21
|
+
}
|
|
22
|
+
self.addEventListener(
|
|
23
|
+
"message",
|
|
24
|
+
async (e) => {
|
|
25
|
+
const msg = e.data;
|
|
26
|
+
if (msg.type === "INIT") {
|
|
27
|
+
try {
|
|
28
|
+
await initDb(msg.persistence, msg.archiveName ?? "default");
|
|
29
|
+
} catch (err) {
|
|
30
|
+
respond({
|
|
31
|
+
id: "",
|
|
32
|
+
type: "ERROR",
|
|
33
|
+
error: err instanceof Error ? err.message : String(err)
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (!db) {
|
|
39
|
+
respond({
|
|
40
|
+
id: msg.id,
|
|
41
|
+
type: "ERROR",
|
|
42
|
+
error: "Database not initialized"
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const req = msg;
|
|
47
|
+
try {
|
|
48
|
+
switch (req.type) {
|
|
49
|
+
case "PING":
|
|
50
|
+
respond({ id: req.id, type: "PONG" });
|
|
51
|
+
break;
|
|
52
|
+
case "QUERY": {
|
|
53
|
+
const result = await db.query(req.sql, req.params);
|
|
54
|
+
respond({
|
|
55
|
+
id: req.id,
|
|
56
|
+
type: "RESULT",
|
|
57
|
+
rows: result.rows,
|
|
58
|
+
fields: result.fields
|
|
59
|
+
});
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case "EXEC":
|
|
63
|
+
await db.exec(req.sql);
|
|
64
|
+
respond({ id: req.id, type: "EXEC_DONE" });
|
|
65
|
+
break;
|
|
66
|
+
case "BEGIN": {
|
|
67
|
+
const txId = crypto.randomUUID();
|
|
68
|
+
await db.exec("BEGIN");
|
|
69
|
+
transactions.set(txId, true);
|
|
70
|
+
respond({ id: req.id, type: "TX_STARTED", txId });
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case "COMMIT":
|
|
74
|
+
if (transactions.has(req.txId)) {
|
|
75
|
+
await db.exec("COMMIT");
|
|
76
|
+
transactions.delete(req.txId);
|
|
77
|
+
}
|
|
78
|
+
respond({ id: req.id, type: "TX_DONE" });
|
|
79
|
+
break;
|
|
80
|
+
case "ROLLBACK":
|
|
81
|
+
if (transactions.has(req.txId)) {
|
|
82
|
+
await db.exec("ROLLBACK");
|
|
83
|
+
transactions.delete(req.txId);
|
|
84
|
+
}
|
|
85
|
+
respond({ id: req.id, type: "TX_DONE" });
|
|
86
|
+
break;
|
|
87
|
+
case "TX_QUERY": {
|
|
88
|
+
const result = await db.query(req.sql, req.params);
|
|
89
|
+
respond({
|
|
90
|
+
id: req.id,
|
|
91
|
+
type: "RESULT",
|
|
92
|
+
rows: result.rows,
|
|
93
|
+
fields: result.fields
|
|
94
|
+
});
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case "TX_EXEC":
|
|
98
|
+
await db.exec(req.sql);
|
|
99
|
+
respond({ id: req.id, type: "EXEC_DONE" });
|
|
100
|
+
break;
|
|
101
|
+
case "CLOSE":
|
|
102
|
+
await db.close();
|
|
103
|
+
db = null;
|
|
104
|
+
respond({ id: req.id, type: "EXEC_DONE" });
|
|
105
|
+
break;
|
|
106
|
+
default:
|
|
107
|
+
respond({
|
|
108
|
+
id: req.id,
|
|
109
|
+
type: "ERROR",
|
|
110
|
+
error: `Unknown request type: ${req.type}`
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
} catch (err) {
|
|
114
|
+
respond({
|
|
115
|
+
id: req.id,
|
|
116
|
+
type: "ERROR",
|
|
117
|
+
error: err instanceof Error ? err.message : String(err)
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
//# sourceMappingURL=pglite-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/worker/pglite-worker.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n/**\n * PGlite Worker script.\n *\n * Runs inside a Web Worker context. Initialises a PGlite instance on receipt\n * of an INIT message, then dispatches typed postMessage requests to the\n * matching PGlite API call and replies with a typed WorkerResponse.\n *\n * Transaction state is tracked in a local Map keyed by txId. Since PGlite\n * is single-threaded within the worker there is no concurrency hazard — only\n * one transaction can be active at a time over raw SQL BEGIN/COMMIT.\n */\n\nimport { PGlite } from '@electric-sql/pglite'\nimport { vector } from '@electric-sql/pglite/vector'\nimport type { WorkerRequest, WorkerResponse } from './protocol.js'\nimport type { PersistenceMode } from '../db.js'\n\nlet db: PGlite | null = null\n// Track open raw-SQL transactions by txId so COMMIT/ROLLBACK are gated.\nconst transactions = new Map<string, true>()\n\nfunction respond(msg: WorkerResponse): void {\n self.postMessage(msg)\n}\n\nasync function initDb(persistence: PersistenceMode, archiveName: string): Promise<void> {\n const dataDir =\n persistence === 'memory'\n ? undefined\n : persistence === 'opfs'\n ? `opfs-ahp://fortemi-${archiveName}`\n : `idb://fortemi-${archiveName}`\n\n const options: Record<string, unknown> = {\n database: 'postgres',\n extensions: { vector },\n }\n\n if (dataDir) {\n options.dataDir = dataDir\n }\n\n db = await PGlite.create(options)\n await db.exec('CREATE EXTENSION IF NOT EXISTS vector')\n respond({ type: 'READY' })\n}\n\ntype InitMessage = {\n type: 'INIT'\n persistence: PersistenceMode\n archiveName?: string\n}\n\nself.addEventListener(\n 'message',\n async (e: MessageEvent<WorkerRequest | InitMessage>) => {\n const msg = e.data\n\n if (msg.type === 'INIT') {\n try {\n await initDb(msg.persistence, msg.archiveName ?? 'default')\n } catch (err) {\n respond({\n id: '',\n type: 'ERROR',\n error: err instanceof Error ? err.message : String(err),\n })\n }\n return\n }\n\n if (!db) {\n respond({\n id: (msg as WorkerRequest).id,\n type: 'ERROR',\n error: 'Database not initialized',\n })\n return\n }\n\n const req = msg as WorkerRequest\n\n try {\n switch (req.type) {\n case 'PING':\n respond({ id: req.id, type: 'PONG' })\n break\n\n case 'QUERY': {\n const result = await db.query(req.sql, req.params)\n respond({\n id: req.id,\n type: 'RESULT',\n rows: result.rows,\n fields: result.fields as Array<{ name: string; dataTypeID: number }>,\n })\n break\n }\n\n case 'EXEC':\n await db.exec(req.sql)\n respond({ id: req.id, type: 'EXEC_DONE' })\n break\n\n case 'BEGIN': {\n const txId = crypto.randomUUID()\n await db.exec('BEGIN')\n transactions.set(txId, true)\n respond({ id: req.id, type: 'TX_STARTED', txId })\n break\n }\n\n case 'COMMIT':\n if (transactions.has(req.txId)) {\n await db.exec('COMMIT')\n transactions.delete(req.txId)\n }\n respond({ id: req.id, type: 'TX_DONE' })\n break\n\n case 'ROLLBACK':\n if (transactions.has(req.txId)) {\n await db.exec('ROLLBACK')\n transactions.delete(req.txId)\n }\n respond({ id: req.id, type: 'TX_DONE' })\n break\n\n case 'TX_QUERY': {\n const result = await db.query(req.sql, req.params)\n respond({\n id: req.id,\n type: 'RESULT',\n rows: result.rows,\n fields: result.fields as Array<{ name: string; dataTypeID: number }>,\n })\n break\n }\n\n case 'TX_EXEC':\n await db.exec(req.sql)\n respond({ id: req.id, type: 'EXEC_DONE' })\n break\n\n case 'CLOSE':\n await db.close()\n db = null\n respond({ id: req.id, type: 'EXEC_DONE' })\n break\n\n default:\n respond({\n id: (req as { id: string }).id,\n type: 'ERROR',\n error: `Unknown request type: ${(req as { type: string }).type}`,\n })\n }\n } catch (err) {\n respond({\n id: req.id,\n type: 'ERROR',\n error: err instanceof Error ? err.message : String(err),\n })\n }\n },\n)\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fortemi/core",
|
|
3
|
+
"version": "2026.5.0",
|
|
4
|
+
"description": "Browser-only knowledge management core: PGlite (PostgreSQL WASM) data layer, single-writer worker, MCP tools, job queue, capability system, SKOS taxonomy, hybrid search. 100% JSON format parity with the fortemi Rust server.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"fortemi",
|
|
7
|
+
"knowledge-management",
|
|
8
|
+
"pglite",
|
|
9
|
+
"postgresql",
|
|
10
|
+
"wasm",
|
|
11
|
+
"semantic-search",
|
|
12
|
+
"mcp",
|
|
13
|
+
"skos",
|
|
14
|
+
"browser-database"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"license": "AGPL-3.0-only",
|
|
18
|
+
"author": "Joseph Magly <joe@magly.net>",
|
|
19
|
+
"homepage": "https://github.com/Fortemi/fortemi-react",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/Fortemi/fortemi-react.git",
|
|
23
|
+
"directory": "packages/core"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/Fortemi/fortemi-react/issues"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"module": "./dist/index.js",
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"import": "./dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./worker/pglite-worker": "./dist/worker/pglite-worker.js",
|
|
40
|
+
"./service-worker/sw": "./dist/service-worker/sw.js",
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"README.md",
|
|
46
|
+
"LICENSE"
|
|
47
|
+
],
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@electric-sql/pglite": "^0.4.1",
|
|
50
|
+
"@noble/hashes": "^1.7.2",
|
|
51
|
+
"fflate": "^0.8.2",
|
|
52
|
+
"uuid": "^13.0.0",
|
|
53
|
+
"zod": "^3.24.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^22.0.0",
|
|
57
|
+
"@vitest/coverage-v8": "^4.1.1",
|
|
58
|
+
"tsup": "^8.3.5",
|
|
59
|
+
"typescript": "^5.8.0",
|
|
60
|
+
"vitest": "^4.1.0"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsup",
|
|
64
|
+
"typecheck": "tsc --noEmit",
|
|
65
|
+
"test": "vitest run",
|
|
66
|
+
"test:watch": "vitest"
|
|
67
|
+
}
|
|
68
|
+
}
|