@soulbatical/tetra-core 0.1.34 → 0.1.36
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/core/createApp.d.ts +11 -0
- package/dist/core/createApp.d.ts.map +1 -1
- package/dist/core/createApp.js +131 -3
- package/dist/core/createApp.js.map +1 -1
- package/dist/core/dualWriteProxy.d.ts +23 -0
- package/dist/core/dualWriteProxy.d.ts.map +1 -0
- package/dist/core/dualWriteProxy.js +220 -0
- package/dist/core/dualWriteProxy.js.map +1 -0
- package/dist/core/systemDb.d.ts +3 -0
- package/dist/core/systemDb.d.ts.map +1 -1
- package/dist/core/systemDb.js +9 -1
- package/dist/core/systemDb.js.map +1 -1
- package/package.json +1 -1
package/dist/core/createApp.d.ts
CHANGED
|
@@ -24,6 +24,15 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import express, { Express, Request, Response, NextFunction } from 'express';
|
|
26
26
|
import { SecurityConfig } from '../middleware/securityMiddleware.js';
|
|
27
|
+
export interface HealthCheckConfig {
|
|
28
|
+
/** Enable database connectivity check on ?deep=true (default: true if SUPABASE_URL is set) */
|
|
29
|
+
database?: boolean;
|
|
30
|
+
/** Custom health checks: name → async function returning boolean */
|
|
31
|
+
custom?: Array<{
|
|
32
|
+
name: string;
|
|
33
|
+
check: () => Promise<boolean>;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
27
36
|
export interface CreateAppConfig {
|
|
28
37
|
/** Project name for logging */
|
|
29
38
|
projectName: string;
|
|
@@ -53,6 +62,8 @@ export interface CreateAppConfig {
|
|
|
53
62
|
requiredEnvVars?: string[];
|
|
54
63
|
/** Skip environment validation (default: false) */
|
|
55
64
|
skipValidation?: boolean;
|
|
65
|
+
/** Health check configuration for /api/health?deep=true */
|
|
66
|
+
healthChecks?: HealthCheckConfig;
|
|
56
67
|
}
|
|
57
68
|
export declare function createApp(config: CreateAppConfig): {
|
|
58
69
|
app: import("express-serve-static-core").Express;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createApp.d.ts","sourceRoot":"","sources":["../../src/core/createApp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,OAAO,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG5E,OAAO,EAAqB,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAIxF,MAAM,WAAW,eAAe;IAC9B,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,2CAA2C;IAC3C,WAAW,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACpC,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACvC,wFAAwF;IACxF,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,mCAAmC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,yDAAyD;IACzD,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACrF,uEAAuE;IACvE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,mDAAmD;IACnD,cAAc,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"createApp.d.ts","sourceRoot":"","sources":["../../src/core/createApp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,OAAO,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG5E,OAAO,EAAqB,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAIxF,MAAM,WAAW,iBAAiB;IAChC,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oEAAoE;IACpE,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;CACjE;AAED,MAAM,WAAW,eAAe;IAC9B,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,2CAA2C;IAC3C,WAAW,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACpC,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACvC,wFAAwF;IACxF,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,mCAAmC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,yDAAyD;IACzD,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACrF,uEAAuE;IACvE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,mDAAmD;IACnD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,iBAAiB,CAAC;CAClC;AAmCD,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe;;;;;;6GAkEjC,CAAC;;;;;;;6CAyDf,YAEG,EAAC,aACE,EAAC,kBAET;;;;EAuGC"}
|
package/dist/core/createApp.js
CHANGED
|
@@ -29,6 +29,35 @@ import { createLogger } from '../utils/logger.js';
|
|
|
29
29
|
import { configureSecurity } from '../middleware/securityMiddleware.js';
|
|
30
30
|
import { validateEnvironment } from '../utils/validateEnvironment.js';
|
|
31
31
|
import { RFC7807ErrorResponse } from '../shared/rfc7807ErrorResponse.js';
|
|
32
|
+
/**
|
|
33
|
+
* Convert Express route regexp back to a path string.
|
|
34
|
+
* Express 4/5 stores route patterns as RegExp — this reverses common patterns.
|
|
35
|
+
*/
|
|
36
|
+
function regexpToPath(regexp, keys = []) {
|
|
37
|
+
let path = regexp.source;
|
|
38
|
+
// Remove Express wrapper patterns
|
|
39
|
+
path = path.replace(/^\^\\\//, '/'); // ^\/
|
|
40
|
+
path = path.replace(/\\\/\?\(\?=\\\/\|\$\)$/, ''); // \/?(?=\/|$)
|
|
41
|
+
path = path.replace(/\(\?:\(\[\^\\\/\]\+\?\)\)/, (_, offset) => {
|
|
42
|
+
const key = keys.shift();
|
|
43
|
+
return key ? `:${key.name}` : ':param';
|
|
44
|
+
});
|
|
45
|
+
// Clean up remaining escapes
|
|
46
|
+
path = path.replace(/\\\//g, '/');
|
|
47
|
+
path = path.replace(/\(\?:\(\[\^\\\/\]\+\?\)\)/g, () => {
|
|
48
|
+
const key = keys.shift();
|
|
49
|
+
return key ? `:${key.name}` : ':param';
|
|
50
|
+
});
|
|
51
|
+
// If we couldn't parse it, return empty
|
|
52
|
+
if (path.includes('(?') || path.includes('\\')) {
|
|
53
|
+
// Fallback: extract the literal prefix
|
|
54
|
+
const match = regexp.source.match(/^\^\\\/([\w\-\/]+)/);
|
|
55
|
+
if (match)
|
|
56
|
+
return '/' + match[1];
|
|
57
|
+
return '';
|
|
58
|
+
}
|
|
59
|
+
return path;
|
|
60
|
+
}
|
|
32
61
|
export function createApp(config) {
|
|
33
62
|
const logger = createLogger(`system:app:${config.projectName}`);
|
|
34
63
|
// Validate environment before anything else
|
|
@@ -79,12 +108,111 @@ export function createApp(config) {
|
|
|
79
108
|
if (config.initAuth) {
|
|
80
109
|
config.initAuth();
|
|
81
110
|
}
|
|
82
|
-
// Health check
|
|
83
|
-
app.get('/api/health', (
|
|
84
|
-
|
|
111
|
+
// Health check (supports ?deep=true for database + custom checks)
|
|
112
|
+
app.get('/api/health', async (req, res) => {
|
|
113
|
+
const isDeep = req.query.deep === 'true';
|
|
114
|
+
if (!isDeep) {
|
|
115
|
+
return res.json({ status: 'ok', project: config.projectName });
|
|
116
|
+
}
|
|
117
|
+
// Deep health check: database, uptime, memory, custom checks
|
|
118
|
+
const checks = {};
|
|
119
|
+
let allOk = true;
|
|
120
|
+
// Database check (if Supabase is configured)
|
|
121
|
+
const dbEnabled = config.healthChecks?.database ?? !!process.env.SUPABASE_URL;
|
|
122
|
+
if (dbEnabled && process.env.SUPABASE_URL && process.env.SUPABASE_SERVICE_ROLE_KEY) {
|
|
123
|
+
try {
|
|
124
|
+
const { createClient } = await import('@supabase/supabase-js');
|
|
125
|
+
const client = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_ROLE_KEY);
|
|
126
|
+
const { error } = await Promise.race([
|
|
127
|
+
client.from('_health_check_nonexistent').select('1').limit(0),
|
|
128
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000)),
|
|
129
|
+
]);
|
|
130
|
+
// PGRST116 (relation not found) is fine — it means the DB responded
|
|
131
|
+
checks.database = 'ok';
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
checks.database = 'error';
|
|
135
|
+
allOk = false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Custom checks
|
|
139
|
+
if (config.healthChecks?.custom) {
|
|
140
|
+
for (const { name, check } of config.healthChecks.custom) {
|
|
141
|
+
try {
|
|
142
|
+
const ok = await Promise.race([
|
|
143
|
+
check(),
|
|
144
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000)),
|
|
145
|
+
]);
|
|
146
|
+
checks[name] = ok ? 'ok' : 'error';
|
|
147
|
+
if (!ok)
|
|
148
|
+
allOk = false;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
checks[name] = 'error';
|
|
152
|
+
allOk = false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const statusCode = allOk ? 200 : 503;
|
|
157
|
+
res.status(statusCode).json({
|
|
158
|
+
status: allOk ? 'ok' : 'degraded',
|
|
159
|
+
project: config.projectName,
|
|
160
|
+
checks,
|
|
161
|
+
uptime: Math.floor(process.uptime()),
|
|
162
|
+
memory: {
|
|
163
|
+
rss: Math.round(process.memoryUsage().rss / 1024 / 1024),
|
|
164
|
+
heap: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
|
|
165
|
+
},
|
|
166
|
+
});
|
|
85
167
|
});
|
|
86
168
|
// Routes
|
|
87
169
|
config.setupRoutes(app);
|
|
170
|
+
// Route manifest — lists all registered routes for smoke testing
|
|
171
|
+
// GET /api/health/routes returns every Express route with method, path, and expected auth level
|
|
172
|
+
app.get('/api/health/routes', (_req, res) => {
|
|
173
|
+
const routes = [];
|
|
174
|
+
function extractRoutes(stack, prefix = '') {
|
|
175
|
+
for (const layer of stack) {
|
|
176
|
+
if (layer.route) {
|
|
177
|
+
// Direct route
|
|
178
|
+
const routePath = prefix + (layer.route.path === '/' ? '' : layer.route.path);
|
|
179
|
+
for (const method of Object.keys(layer.route.methods)) {
|
|
180
|
+
if (!layer.route.methods[method])
|
|
181
|
+
continue;
|
|
182
|
+
let auth = 'public';
|
|
183
|
+
if (routePath.startsWith('/api/admin'))
|
|
184
|
+
auth = 'admin';
|
|
185
|
+
else if (routePath.startsWith('/api/superadmin'))
|
|
186
|
+
auth = 'superadmin';
|
|
187
|
+
else if (routePath.startsWith('/api/user'))
|
|
188
|
+
auth = 'user';
|
|
189
|
+
routes.push({
|
|
190
|
+
method: method.toUpperCase(),
|
|
191
|
+
path: routePath || '/',
|
|
192
|
+
auth,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else if (layer.name === 'router' && layer.handle?.stack) {
|
|
197
|
+
// Nested router
|
|
198
|
+
const layerPrefix = layer.regexp?.source
|
|
199
|
+
? prefix + regexpToPath(layer.regexp, layer.keys)
|
|
200
|
+
: prefix;
|
|
201
|
+
extractRoutes(layer.handle.stack, layerPrefix);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
extractRoutes(app._router?.stack || []);
|
|
206
|
+
// Filter to API routes only, deduplicate
|
|
207
|
+
const apiRoutes = routes
|
|
208
|
+
.filter(r => r.path.startsWith('/api/'))
|
|
209
|
+
.filter(r => !r.path.includes('*') && !r.path.includes('health/routes'));
|
|
210
|
+
res.json({
|
|
211
|
+
project: config.projectName,
|
|
212
|
+
total: apiRoutes.length,
|
|
213
|
+
routes: apiRoutes,
|
|
214
|
+
});
|
|
215
|
+
});
|
|
88
216
|
// Error handler (RFC7807 compliant via RFC7807ErrorResponse)
|
|
89
217
|
// Hint: Custom errorHandler in CreateAppConfig overschrijft dit. Anders gebruikt Tetra RFC7807.
|
|
90
218
|
const errorHandler = config.errorHandler || ((err, _req, res, _next) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createApp.js","sourceRoot":"","sources":["../../src/core/createApp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,OAAqD,MAAM,SAAS,CAAC;AAC5E,OAAO,YAAY,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAkB,MAAM,qCAAqC,CAAC;AACxF,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"createApp.js","sourceRoot":"","sources":["../../src/core/createApp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dual-Write Proxy — Intercepts Supabase writes and mirrors to RuVector daemon.
|
|
3
|
+
*
|
|
4
|
+
* Wraps a SupabaseClient so that every insert/update/upsert/delete on .from()
|
|
5
|
+
* also fires a fire-and-forget RPC to the Stella daemon's RuVectorStore.
|
|
6
|
+
*
|
|
7
|
+
* The proxy is transparent: it returns the same SupabaseClient interface,
|
|
8
|
+
* same return types, same behavior. RuVector sync failures are silently logged.
|
|
9
|
+
*
|
|
10
|
+
* Activation: set DUAL_WRITE=1 or STELLA_STORES=supabase,ruvector
|
|
11
|
+
*/
|
|
12
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
13
|
+
/**
|
|
14
|
+
* Check if dual-write is enabled via environment.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isDualWriteEnabled(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Wrap a SupabaseClient with dual-write proxy.
|
|
19
|
+
* Returns the same interface — all reads go to Supabase only,
|
|
20
|
+
* all writes go to both Supabase and RuVector daemon.
|
|
21
|
+
*/
|
|
22
|
+
export declare function withDualWrite(client: SupabaseClient): SupabaseClient;
|
|
23
|
+
//# sourceMappingURL=dualWriteProxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dualWriteProxy.d.ts","sourceRoot":"","sources":["../../src/core/dualWriteProxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AA0N3D;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAI5C;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CASpE"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dual-Write Proxy — Intercepts Supabase writes and mirrors to RuVector daemon.
|
|
3
|
+
*
|
|
4
|
+
* Wraps a SupabaseClient so that every insert/update/upsert/delete on .from()
|
|
5
|
+
* also fires a fire-and-forget RPC to the Stella daemon's RuVectorStore.
|
|
6
|
+
*
|
|
7
|
+
* The proxy is transparent: it returns the same SupabaseClient interface,
|
|
8
|
+
* same return types, same behavior. RuVector sync failures are silently logged.
|
|
9
|
+
*
|
|
10
|
+
* Activation: set DUAL_WRITE=1 or STELLA_STORES=supabase,ruvector
|
|
11
|
+
*/
|
|
12
|
+
import { connect } from 'node:net';
|
|
13
|
+
const SOCKET_PATH = '/tmp/stella-daemon.sock';
|
|
14
|
+
const RPC_TIMEOUT_MS = 5_000;
|
|
15
|
+
// ─── Daemon RPC (lightweight, no dependency on stella) ──────
|
|
16
|
+
let daemonSocket = null;
|
|
17
|
+
let daemonConnected = false;
|
|
18
|
+
let daemonConnecting = false;
|
|
19
|
+
let rpcId = 0;
|
|
20
|
+
function connectDaemon() {
|
|
21
|
+
if (daemonConnected)
|
|
22
|
+
return Promise.resolve();
|
|
23
|
+
if (daemonConnecting)
|
|
24
|
+
return new Promise(r => setTimeout(r, 500));
|
|
25
|
+
daemonConnecting = true;
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
const sock = connect(SOCKET_PATH);
|
|
28
|
+
const timeout = setTimeout(() => {
|
|
29
|
+
sock.destroy();
|
|
30
|
+
daemonConnecting = false;
|
|
31
|
+
resolve(); // Don't throw — daemon unavailable is not fatal
|
|
32
|
+
}, 2000);
|
|
33
|
+
sock.on('connect', () => {
|
|
34
|
+
clearTimeout(timeout);
|
|
35
|
+
daemonSocket = sock;
|
|
36
|
+
daemonConnected = true;
|
|
37
|
+
daemonConnecting = false;
|
|
38
|
+
resolve();
|
|
39
|
+
});
|
|
40
|
+
sock.on('close', () => {
|
|
41
|
+
daemonConnected = false;
|
|
42
|
+
daemonSocket = null;
|
|
43
|
+
});
|
|
44
|
+
sock.on('error', () => {
|
|
45
|
+
clearTimeout(timeout);
|
|
46
|
+
daemonConnected = false;
|
|
47
|
+
daemonSocket = null;
|
|
48
|
+
daemonConnecting = false;
|
|
49
|
+
resolve(); // Silent fail
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/** Fire-and-forget RPC to daemon. Never throws, never blocks. */
|
|
54
|
+
function daemonRpc(method, params) {
|
|
55
|
+
connectDaemon().then(() => {
|
|
56
|
+
if (!daemonSocket || !daemonConnected)
|
|
57
|
+
return;
|
|
58
|
+
const id = ++rpcId;
|
|
59
|
+
try {
|
|
60
|
+
daemonSocket.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Socket write failed — daemon probably died, will reconnect next time
|
|
64
|
+
daemonConnected = false;
|
|
65
|
+
daemonSocket = null;
|
|
66
|
+
}
|
|
67
|
+
}).catch(() => { });
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Wraps a Supabase `.from(table)` query builder to intercept write operations.
|
|
71
|
+
* After the original Supabase call resolves, mirrors the write to RuVector.
|
|
72
|
+
*/
|
|
73
|
+
function proxyQueryBuilder(original, table) {
|
|
74
|
+
return new Proxy(original, {
|
|
75
|
+
get(target, prop) {
|
|
76
|
+
const value = target[prop];
|
|
77
|
+
// Intercept write methods
|
|
78
|
+
if (['insert', 'update', 'upsert', 'delete'].includes(prop)) {
|
|
79
|
+
return function proxiedWrite(...args) {
|
|
80
|
+
const result = value.apply(target, args);
|
|
81
|
+
return proxyWriteResult(result, table, prop, args);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// Pass through everything else (select, rpc, etc.)
|
|
85
|
+
if (typeof value === 'function') {
|
|
86
|
+
return value.bind(target);
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Wraps the PostgREST filter builder result to intercept .then()
|
|
94
|
+
* so we can mirror the write after Supabase confirms success.
|
|
95
|
+
*/
|
|
96
|
+
function proxyWriteResult(builder, table, op, args) {
|
|
97
|
+
// The builder is a thenable (PostgREST chain). We need to intercept
|
|
98
|
+
// the final .then() to know when Supabase has processed the write.
|
|
99
|
+
// But we also need to preserve the full chain (.select(), .eq(), .single(), etc.)
|
|
100
|
+
// Track filter state for update/delete
|
|
101
|
+
const filters = [];
|
|
102
|
+
return new Proxy(builder, {
|
|
103
|
+
get(target, prop) {
|
|
104
|
+
const value = target[prop];
|
|
105
|
+
// Capture filter calls for update/delete mirroring
|
|
106
|
+
if (['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'in', 'is'].includes(prop) && typeof value === 'function') {
|
|
107
|
+
return function (...filterArgs) {
|
|
108
|
+
filters.push({ column: filterArgs[0], op: prop, value: filterArgs[1] });
|
|
109
|
+
const result = value.apply(target, filterArgs);
|
|
110
|
+
return proxyWriteResult(result, table, op, args);
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// Intercept .then() to mirror after Supabase success
|
|
114
|
+
if (prop === 'then' && typeof value === 'function') {
|
|
115
|
+
return function (onFulfilled, onRejected) {
|
|
116
|
+
return value.call(target, (result) => {
|
|
117
|
+
// Only mirror if Supabase write succeeded
|
|
118
|
+
if (!result.error) {
|
|
119
|
+
mirrorToDaemon(table, op, args, filters);
|
|
120
|
+
}
|
|
121
|
+
return onFulfilled ? onFulfilled(result) : result;
|
|
122
|
+
}, onRejected);
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// Preserve chain methods (select, single, maybeSingle, etc.)
|
|
126
|
+
if (typeof value === 'function') {
|
|
127
|
+
return function (...chainArgs) {
|
|
128
|
+
const result = value.apply(target, chainArgs);
|
|
129
|
+
// If result is still a thenable builder, keep proxying
|
|
130
|
+
if (result && typeof result.then === 'function') {
|
|
131
|
+
return proxyWriteResult(result, table, op, args);
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Mirror a Supabase write operation to the RuVector daemon.
|
|
142
|
+
*/
|
|
143
|
+
function mirrorToDaemon(table, op, args, filters) {
|
|
144
|
+
try {
|
|
145
|
+
switch (op) {
|
|
146
|
+
case 'insert': {
|
|
147
|
+
const records = args[0];
|
|
148
|
+
if (Array.isArray(records)) {
|
|
149
|
+
daemonRpc('store.seed', { table, records });
|
|
150
|
+
}
|
|
151
|
+
else if (records) {
|
|
152
|
+
daemonRpc('store.insert', { table, record: records });
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case 'upsert': {
|
|
157
|
+
const record = args[0];
|
|
158
|
+
const options = args[1];
|
|
159
|
+
if (record && !Array.isArray(record)) {
|
|
160
|
+
daemonRpc('store.upsert', {
|
|
161
|
+
table,
|
|
162
|
+
record,
|
|
163
|
+
onConflict: options?.onConflict || 'id',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
else if (Array.isArray(record)) {
|
|
167
|
+
// Bulk upsert — send as individual upserts
|
|
168
|
+
for (const r of record) {
|
|
169
|
+
daemonRpc('store.upsert', {
|
|
170
|
+
table,
|
|
171
|
+
record: r,
|
|
172
|
+
onConflict: options?.onConflict || 'id',
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
case 'update': {
|
|
179
|
+
const changes = args[0];
|
|
180
|
+
if (changes && filters.length) {
|
|
181
|
+
daemonRpc('store.update', { table, filters, changes });
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
case 'delete': {
|
|
186
|
+
if (filters.length) {
|
|
187
|
+
daemonRpc('store.delete', { table, filters });
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// Never let daemon sync errors affect the main flow
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// ─── Public API ─────────────────────────────────────────────
|
|
198
|
+
/**
|
|
199
|
+
* Check if dual-write is enabled via environment.
|
|
200
|
+
*/
|
|
201
|
+
export function isDualWriteEnabled() {
|
|
202
|
+
if (process.env.DUAL_WRITE === '1')
|
|
203
|
+
return true;
|
|
204
|
+
const stores = process.env.STELLA_STORES || '';
|
|
205
|
+
return stores.includes('ruvector');
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Wrap a SupabaseClient with dual-write proxy.
|
|
209
|
+
* Returns the same interface — all reads go to Supabase only,
|
|
210
|
+
* all writes go to both Supabase and RuVector daemon.
|
|
211
|
+
*/
|
|
212
|
+
export function withDualWrite(client) {
|
|
213
|
+
const originalFrom = client.from.bind(client);
|
|
214
|
+
client.from = function proxiedFrom(table) {
|
|
215
|
+
const queryBuilder = originalFrom(table);
|
|
216
|
+
return proxyQueryBuilder(queryBuilder, table);
|
|
217
|
+
};
|
|
218
|
+
return client;
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=dualWriteProxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dualWriteProxy.js","sourceRoot":"","sources":["../../src/core/dualWriteProxy.ts"],"names":[],"mappings":"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"}
|
package/dist/core/systemDb.d.ts
CHANGED
|
@@ -34,6 +34,9 @@ import { SupabaseClient } from '@supabase/supabase-js';
|
|
|
34
34
|
/**
|
|
35
35
|
* Create a system-level Supabase client (service role key, bypasses RLS).
|
|
36
36
|
*
|
|
37
|
+
* When DUAL_WRITE=1 or STELLA_STORES contains 'ruvector', all writes are
|
|
38
|
+
* automatically mirrored to the RuVector daemon (fire-and-forget).
|
|
39
|
+
*
|
|
37
40
|
* @param context - WHY system access is needed (e.g., 'stripe-webhook', 'token-refresh-cron')
|
|
38
41
|
*/
|
|
39
42
|
export declare function systemDB(context: string): SupabaseClient;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"systemDb.d.ts","sourceRoot":"","sources":["../../src/core/systemDb.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAgB,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"systemDb.d.ts","sourceRoot":"","sources":["../../src/core/systemDb.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAgB,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAMrE;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAmBxD"}
|
package/dist/core/systemDb.js
CHANGED
|
@@ -32,10 +32,14 @@
|
|
|
32
32
|
*/
|
|
33
33
|
import { createClient } from '@supabase/supabase-js';
|
|
34
34
|
import { createLogger } from '../utils/logger.js';
|
|
35
|
+
import { isDualWriteEnabled, withDualWrite } from './dualWriteProxy.js';
|
|
35
36
|
const logger = createLogger('security:db:system');
|
|
36
37
|
/**
|
|
37
38
|
* Create a system-level Supabase client (service role key, bypasses RLS).
|
|
38
39
|
*
|
|
40
|
+
* When DUAL_WRITE=1 or STELLA_STORES contains 'ruvector', all writes are
|
|
41
|
+
* automatically mirrored to the RuVector daemon (fire-and-forget).
|
|
42
|
+
*
|
|
39
43
|
* @param context - WHY system access is needed (e.g., 'stripe-webhook', 'token-refresh-cron')
|
|
40
44
|
*/
|
|
41
45
|
export function systemDB(context) {
|
|
@@ -45,8 +49,12 @@ export function systemDB(context) {
|
|
|
45
49
|
throw new Error('Missing SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY environment variables');
|
|
46
50
|
}
|
|
47
51
|
logger.debug({ context }, 'systemDB access');
|
|
48
|
-
|
|
52
|
+
const client = createClient(url, serviceKey, {
|
|
49
53
|
auth: { persistSession: false }
|
|
50
54
|
});
|
|
55
|
+
if (isDualWriteEnabled()) {
|
|
56
|
+
return withDualWrite(client);
|
|
57
|
+
}
|
|
58
|
+
return client;
|
|
51
59
|
}
|
|
52
60
|
//# sourceMappingURL=systemDb.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"systemDb.js","sourceRoot":"","sources":["../../src/core/systemDb.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,YAAY,EAAkB,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"systemDb.js","sourceRoot":"","sources":["../../src/core/systemDb.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,YAAY,EAAkB,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAExE,MAAM,MAAM,GAAG,YAAY,CAAC,oBAAoB,CAAC,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IACrC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC;IAEzD,IAAI,CAAC,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,yEAAyE,CAAC,CAAC;IAC7F,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,EAAE,iBAAiB,CAAC,CAAC;IAE7C,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,EAAE,UAAU,EAAE;QAC3C,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE;KAChC,CAAC,CAAC;IAEH,IAAI,kBAAkB,EAAE,EAAE,CAAC;QACzB,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|