cogsbox-sync 0.0.1
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/bin/cli.d.ts +1 -0
- package/dist/bin/cli.js +61 -0
- package/dist/index.d.ts +903 -0
- package/dist/index.js +2755 -0
- package/package.json +36 -0
- package/templates/worker/.editorconfig +12 -0
- package/templates/worker/.prettierrc +6 -0
- package/templates/worker/bin/init.ts +53 -0
- package/templates/worker/package-lock.json +4087 -0
- package/templates/worker/package.json +30 -0
- package/templates/worker/schema-processor/Dockerfile +16 -0
- package/templates/worker/schema-processor/package.json +10 -0
- package/templates/worker/schema-processor/server.js +483 -0
- package/templates/worker/schema-processor/serverOldExpress.js +488 -0
- package/templates/worker/src/UserObject.ts +40 -0
- package/templates/worker/src/auth.ts +58 -0
- package/templates/worker/src/index.ts +1860 -0
- package/templates/worker/src/utility.ts +101 -0
- package/templates/worker/test/index.spec.ts +25 -0
- package/templates/worker/test/tsconfig.json +8 -0
- package/templates/worker/tsconfig.json +46 -0
- package/templates/worker/vitest.config.mts +11 -0
- package/templates/worker/worker-configuration.d.ts +7954 -0
- package/templates/worker/wrangler.jsonc +90 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-sync-worker",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"init": "tsx bin/init.ts",
|
|
6
|
+
"init:deploy": "tsx bin/init.ts --deploy",
|
|
7
|
+
"deploy": "wrangler deploy",
|
|
8
|
+
"dev": "wrangler dev --port 8788",
|
|
9
|
+
"start": "wrangler dev",
|
|
10
|
+
"test": "vitest",
|
|
11
|
+
"cf-typegen": "wrangler types"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@cloudflare/vitest-pool-workers": "^0.12.12",
|
|
15
|
+
"@cloudflare/workers-types": "^4.20260214.0",
|
|
16
|
+
"@vitest/runner": "3.2.4",
|
|
17
|
+
"@vitest/snapshot": "3.2.4",
|
|
18
|
+
"tsx": "^4.21.0",
|
|
19
|
+
"typescript": "^5.9.3",
|
|
20
|
+
"vitest": "~3.2.4",
|
|
21
|
+
"wrangler": "^4.65.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@cfworker/json-schema": "^4.1.1",
|
|
25
|
+
"@cloudflare/containers": "^0.1.0",
|
|
26
|
+
"@tsndr/cloudflare-worker-jwt": "^3.2.1",
|
|
27
|
+
"cogsbox-shape": "^0.5.158",
|
|
28
|
+
"fast-json-patch": "^3.1.1"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# schema-processor/Dockerfile
|
|
2
|
+
FROM node:20-alpine
|
|
3
|
+
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
|
|
6
|
+
# Copy package.json first
|
|
7
|
+
COPY package.json ./
|
|
8
|
+
RUN npm install
|
|
9
|
+
|
|
10
|
+
# Copy server code
|
|
11
|
+
COPY server.js ./
|
|
12
|
+
|
|
13
|
+
# Expose the port
|
|
14
|
+
EXPOSE 8080
|
|
15
|
+
|
|
16
|
+
CMD ["node", "server.js"]
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
// schema-processor/server.js - Hono version
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
import { cors } from 'hono/cors';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
|
|
6
|
+
const app = new Hono();
|
|
7
|
+
|
|
8
|
+
// Enable CORS if needed
|
|
9
|
+
app.use('*', cors());
|
|
10
|
+
|
|
11
|
+
// In-memory cache for schemas per tenant
|
|
12
|
+
const schemaCache = new Map();
|
|
13
|
+
|
|
14
|
+
// Utility function to safely import schema module
|
|
15
|
+
async function importSchemaModule(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const schemaModule = await import(`file://${filePath}?t=${Date.now()}`);
|
|
20
|
+
|
|
21
|
+
if (!schemaModule.syncSchema) {
|
|
22
|
+
throw new Error('syncSchema not found in module');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return schemaModule;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error(`Failed to import schema module from ${filePath}:`, error.message);
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Load schema for a tenant and cache it
|
|
33
|
+
async function loadSchemaForTenant(tenantId) {
|
|
34
|
+
console.log(`[loadSchemaForTenant] Starting load for tenant ${tenantId}`);
|
|
35
|
+
|
|
36
|
+
if (schemaCache.has(tenantId)) {
|
|
37
|
+
console.log(`[loadSchemaForTenant] Schema already cached for tenant ${tenantId}`);
|
|
38
|
+
return schemaCache.get(tenantId);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Load from file system cache first
|
|
42
|
+
const cacheFile = `/tmp/schema-${tenantId}.mjs`;
|
|
43
|
+
console.log(`[loadSchemaForTenant] Checking cache file: ${cacheFile}`);
|
|
44
|
+
|
|
45
|
+
if (fs.existsSync(cacheFile)) {
|
|
46
|
+
try {
|
|
47
|
+
console.log(`[loadSchemaForTenant] Cache file exists, importing...`);
|
|
48
|
+
const schemaModule = await importSchemaModule(cacheFile);
|
|
49
|
+
|
|
50
|
+
schemaCache.set(tenantId, schemaModule.syncSchema);
|
|
51
|
+
console.log(`[loadSchemaForTenant] Schema cached successfully for tenant ${tenantId}`);
|
|
52
|
+
return schemaModule.syncSchema;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error(`[loadSchemaForTenant] Cache file import failed:`, error.message);
|
|
55
|
+
console.log('Cache file invalid, will reload:', error.message);
|
|
56
|
+
try {
|
|
57
|
+
fs.unlinkSync(cacheFile);
|
|
58
|
+
console.log(`[loadSchemaForTenant] Removed invalid cache file`);
|
|
59
|
+
} catch (unlinkError) {
|
|
60
|
+
console.error(`[loadSchemaForTenant] Failed to remove cache file:`, unlinkError.message);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
console.log(`[loadSchemaForTenant] Cache file does not exist`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(`[loadSchemaForTenant] No schema available for tenant ${tenantId}`);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Health check endpoint
|
|
72
|
+
app.get('/health', (c) => {
|
|
73
|
+
return c.json({
|
|
74
|
+
status: 'healthy',
|
|
75
|
+
timestamp: new Date().toISOString(),
|
|
76
|
+
cachedSchemas: Array.from(schemaCache.keys())
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// GET endpoint to check schema status
|
|
81
|
+
app.get('/load-schema', async (c) => {
|
|
82
|
+
try {
|
|
83
|
+
const tenantId = c.req.query('tenantId');
|
|
84
|
+
|
|
85
|
+
if (!tenantId) {
|
|
86
|
+
return c.json({ error: 'tenantId is required' }, 400);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const schema = await loadSchemaForTenant(tenantId);
|
|
90
|
+
|
|
91
|
+
// Handle both schema formats - direct schemas or nested under 'schemas'
|
|
92
|
+
let schemaKeys = [];
|
|
93
|
+
if (schema) {
|
|
94
|
+
if (schema.schemas) {
|
|
95
|
+
schemaKeys = Object.keys(schema.schemas);
|
|
96
|
+
} else {
|
|
97
|
+
schemaKeys = Object.keys(schema);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return c.json({
|
|
102
|
+
hasSchema: !!schema,
|
|
103
|
+
schemaKeys: schemaKeys
|
|
104
|
+
});
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error('Error in GET /load-schema:', error);
|
|
107
|
+
return c.json({ error: error.message }, 500);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// POST endpoint to load/update schema
|
|
112
|
+
app.post('/load-schema', async (c) => {
|
|
113
|
+
try {
|
|
114
|
+
const tenantId = c.req.query('tenantId');
|
|
115
|
+
const { bundledCode } = await c.req.json();
|
|
116
|
+
|
|
117
|
+
if (!tenantId) {
|
|
118
|
+
return c.json({ error: 'tenantId is required' }, 400);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (bundledCode) {
|
|
122
|
+
// Save and cache new schema
|
|
123
|
+
const cacheFile = `/tmp/schema-${tenantId}.mjs`;
|
|
124
|
+
fs.writeFileSync(cacheFile, bundledCode);
|
|
125
|
+
|
|
126
|
+
const schemaModule = await importSchemaModule(cacheFile);
|
|
127
|
+
schemaCache.set(tenantId, schemaModule.syncSchema);
|
|
128
|
+
|
|
129
|
+
console.log(`Schema loaded and cached for tenant ${tenantId}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const schema = await loadSchemaForTenant(tenantId);
|
|
133
|
+
|
|
134
|
+
// Handle both schema formats
|
|
135
|
+
let schemaKeys = [];
|
|
136
|
+
if (schema) {
|
|
137
|
+
if (schema.schemas) {
|
|
138
|
+
schemaKeys = Object.keys(schema.schemas);
|
|
139
|
+
} else {
|
|
140
|
+
schemaKeys = Object.keys(schema);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return c.json({
|
|
145
|
+
success: true,
|
|
146
|
+
hasSchema: !!schema,
|
|
147
|
+
schemaKeys: schemaKeys
|
|
148
|
+
});
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error('Error loading schema:', error);
|
|
151
|
+
return c.json({ error: error.message }, 500);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Validate endpoint
|
|
156
|
+
app.post('/validate', async (c) => {
|
|
157
|
+
try {
|
|
158
|
+
const { data, schemaKey, bundledCode, context } = await c.req.json();
|
|
159
|
+
const tenantId = c.req.query('tenantId');
|
|
160
|
+
|
|
161
|
+
if (!tenantId || !schemaKey || data === undefined || !bundledCode) {
|
|
162
|
+
return c.json({
|
|
163
|
+
valid: false,
|
|
164
|
+
errors: [{ path: [], message: 'Missing tenantId, schemaKey, data, or bundledCode' }]
|
|
165
|
+
}, 400);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const tempFile = `/tmp/schema-${tenantId}-${Date.now()}.mjs`;
|
|
169
|
+
fs.writeFileSync(tempFile, bundledCode);
|
|
170
|
+
|
|
171
|
+
const schemaModule = await importSchemaModule(tempFile);
|
|
172
|
+
fs.unlinkSync(tempFile);
|
|
173
|
+
|
|
174
|
+
let schemaDefinition = null;
|
|
175
|
+
if (schemaModule.syncSchema && schemaModule.syncSchema.schemas && schemaModule.syncSchema.schemas[schemaKey]) {
|
|
176
|
+
schemaDefinition = schemaModule.syncSchema.schemas[schemaKey];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!schemaDefinition) {
|
|
180
|
+
return c.json({
|
|
181
|
+
valid: false,
|
|
182
|
+
errors: [{ path: [], message: `Schema '${schemaKey}' not found within the provided bundle.` }]
|
|
183
|
+
}, 404);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const validator = schemaDefinition.validate;
|
|
187
|
+
if (typeof validator !== 'function') {
|
|
188
|
+
throw new Error(`Validator function not found for schema '${schemaKey}'`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Pass the context from the request
|
|
192
|
+
const result = validator(data, context || {});
|
|
193
|
+
|
|
194
|
+
if (!result.success) {
|
|
195
|
+
return c.json({
|
|
196
|
+
valid: false,
|
|
197
|
+
errors: result.error.issues // Using the correct "issues" property from Zod
|
|
198
|
+
}, 422);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return c.json({ valid: true, errors: [] });
|
|
202
|
+
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error('[Container] /validate Error:', error);
|
|
205
|
+
if (error.name === 'ZodError') {
|
|
206
|
+
return c.json({
|
|
207
|
+
valid: false,
|
|
208
|
+
errors: error.issues
|
|
209
|
+
}, 422);
|
|
210
|
+
}
|
|
211
|
+
return c.json({
|
|
212
|
+
valid: false,
|
|
213
|
+
errors: [{ path: [], message: error.message }]
|
|
214
|
+
}, 500);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Validate path endpoint
|
|
219
|
+
app.post('/validate-path', async (c) => {
|
|
220
|
+
try {
|
|
221
|
+
const { path, value, schemaKey, bundledCode } = await c.req.json();
|
|
222
|
+
const tenantId = c.req.query('tenantId');
|
|
223
|
+
|
|
224
|
+
if (!tenantId || !schemaKey || !bundledCode || !path) {
|
|
225
|
+
return c.json({
|
|
226
|
+
valid: false,
|
|
227
|
+
errors: [{ path: [], message: 'Missing required parameters' }]
|
|
228
|
+
}, 400);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Write and load schema
|
|
232
|
+
const tempFile = `/tmp/schema-${tenantId}-${Date.now()}.mjs`;
|
|
233
|
+
fs.writeFileSync(tempFile, bundledCode);
|
|
234
|
+
const schemaModule = await importSchemaModule(tempFile);
|
|
235
|
+
fs.unlinkSync(tempFile);
|
|
236
|
+
|
|
237
|
+
// Get the specific schema from the schemas collection
|
|
238
|
+
const schemaDefinition = schemaModule.syncSchema?.schemas?.[schemaKey];
|
|
239
|
+
|
|
240
|
+
if (!schemaDefinition) {
|
|
241
|
+
return c.json({
|
|
242
|
+
valid: false,
|
|
243
|
+
errors: [{ path: [], message: `Schema '${schemaKey}' not found` }]
|
|
244
|
+
}, 404);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!schemaDefinition.schemas || !schemaDefinition.schemas.validation) {
|
|
248
|
+
return c.json({
|
|
249
|
+
valid: false,
|
|
250
|
+
errors: [{ path: [], message: `Validation schema not found for '${schemaKey}'` }]
|
|
251
|
+
}, 404);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Walk to the target field
|
|
255
|
+
let fieldValidator = schemaDefinition.schemas.validation;
|
|
256
|
+
|
|
257
|
+
for (let i = 0; i < path.length; i++) {
|
|
258
|
+
const segment = path[i];
|
|
259
|
+
|
|
260
|
+
if (typeof segment === 'number' || segment.startsWith('id:')) {
|
|
261
|
+
// Array element
|
|
262
|
+
if (fieldValidator._def?.typeName === 'ZodArray') {
|
|
263
|
+
fieldValidator = fieldValidator._def.type || fieldValidator.element;
|
|
264
|
+
} else {
|
|
265
|
+
return c.json({ valid: true });
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
// Object property
|
|
269
|
+
if (fieldValidator.shape) {
|
|
270
|
+
fieldValidator = fieldValidator.shape[segment];
|
|
271
|
+
} else if (fieldValidator._def?.shape) {
|
|
272
|
+
const shape = fieldValidator._def.shape();
|
|
273
|
+
fieldValidator = shape[segment];
|
|
274
|
+
} else {
|
|
275
|
+
return c.json({ valid: true });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!fieldValidator) {
|
|
280
|
+
return c.json({ valid: true });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Validate the value
|
|
285
|
+
const result = fieldValidator.safeParse(value);
|
|
286
|
+
|
|
287
|
+
if (!result.success) {
|
|
288
|
+
return c.json({
|
|
289
|
+
valid: false,
|
|
290
|
+
errors: result.error.issues.map(issue => ({
|
|
291
|
+
path: issue.path,
|
|
292
|
+
message: issue.message,
|
|
293
|
+
code: issue.code
|
|
294
|
+
}))
|
|
295
|
+
}, 422);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return c.json({ valid: true, errors: [] });
|
|
299
|
+
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.error('[Container] /validate-path Error:', error);
|
|
302
|
+
return c.json({
|
|
303
|
+
valid: false,
|
|
304
|
+
errors: [{ path: [], message: error.message }]
|
|
305
|
+
}, 500);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Get API routes endpoint
|
|
310
|
+
app.post('/get-api-routes', async (c) => {
|
|
311
|
+
try {
|
|
312
|
+
const { tenantId, schemaKey, bundledCode, params } = await c.req.json();
|
|
313
|
+
|
|
314
|
+
if (!tenantId || !schemaKey || !bundledCode) {
|
|
315
|
+
return c.json({ error: 'Missing tenantId, schemaKey, or bundledCode' }, 400);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const justSchemaKey = schemaKey.split('[')[0];
|
|
319
|
+
const tempFile = `/tmp/schema-${tenantId}-${Date.now()}.mjs`;
|
|
320
|
+
fs.writeFileSync(tempFile, bundledCode);
|
|
321
|
+
|
|
322
|
+
const schemaModule = await importSchemaModule(tempFile);
|
|
323
|
+
fs.unlinkSync(tempFile);
|
|
324
|
+
|
|
325
|
+
const schemaDefinition = schemaModule.syncSchema?.schemas?.[justSchemaKey];
|
|
326
|
+
|
|
327
|
+
if (!schemaDefinition) {
|
|
328
|
+
return c.json({ error: `Schema '${justSchemaKey}' not found in bundle.` }, 404);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const apiConfig = schemaDefinition.api || {};
|
|
332
|
+
|
|
333
|
+
// Process both queryData and mutateData with the same logic
|
|
334
|
+
const processApiConfig = (config) => {
|
|
335
|
+
if (!config) return null;
|
|
336
|
+
|
|
337
|
+
if (config && typeof config.handler === 'function') {
|
|
338
|
+
if (!params || typeof params !== 'object') {
|
|
339
|
+
throw new Error('Missing or invalid params object for parameterized route.');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const validationResult = schemaDefinition.validateApiParams(config, params);
|
|
343
|
+
|
|
344
|
+
if (!validationResult.success) {
|
|
345
|
+
throw new Error(`API parameter validation failed: ${JSON.stringify(validationResult.error.flatten())}`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const result = config.handler(validationResult.data);
|
|
349
|
+
|
|
350
|
+
if (typeof result === 'string') {
|
|
351
|
+
return { url: result, method: 'GET' };
|
|
352
|
+
} else if (result && typeof result === 'object' && result.url) {
|
|
353
|
+
return {
|
|
354
|
+
url: result.url,
|
|
355
|
+
method: result.data ? 'POST' : 'GET',
|
|
356
|
+
data: result.data
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
throw new Error('Invalid handler return value');
|
|
361
|
+
} else if (typeof config === 'string') {
|
|
362
|
+
return { url: config, method: 'GET' };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return null;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
const queryConfig = processApiConfig(apiConfig.queryData);
|
|
370
|
+
const mutateConfig = processApiConfig(apiConfig.mutateData);
|
|
371
|
+
|
|
372
|
+
return c.json({
|
|
373
|
+
queryData: queryConfig,
|
|
374
|
+
mutateData: mutateConfig
|
|
375
|
+
});
|
|
376
|
+
} catch (error) {
|
|
377
|
+
return c.json({ error: error.message }, 400);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
} catch (error) {
|
|
381
|
+
console.error('[Container] /get-api-routes Error:', error);
|
|
382
|
+
return c.json({ error: error.message }, 500);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// Process notifications batch endpoint
|
|
387
|
+
app.post('/process-notifications-batch', async (c) => {
|
|
388
|
+
try {
|
|
389
|
+
const { bundledCode, fullState, contexts, operation } = await c.req.json();
|
|
390
|
+
const tenantId = c.req.query('tenantId');
|
|
391
|
+
|
|
392
|
+
if (!tenantId || !bundledCode || !fullState || !Array.isArray(contexts)) {
|
|
393
|
+
return c.json({ error: 'Missing tenantId, bundledCode, fullState, or contexts array' }, 400);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const tempFile = `/tmp/schema-${tenantId}-${Date.now()}.mjs`;
|
|
397
|
+
fs.writeFileSync(tempFile, bundledCode);
|
|
398
|
+
const schemaModule = await importSchemaModule(tempFile);
|
|
399
|
+
fs.unlinkSync(tempFile);
|
|
400
|
+
|
|
401
|
+
const schema = schemaModule.syncSchema;
|
|
402
|
+
if (!schema) {
|
|
403
|
+
return c.json({ error: 'syncSchema not found in the provided bundle.' }, 404);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const notificationFunctions = schema.notifications;
|
|
407
|
+
if (!notificationFunctions) {
|
|
408
|
+
return c.json({});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const notificationsByUserId = {};
|
|
412
|
+
|
|
413
|
+
for (const context of contexts) {
|
|
414
|
+
if (!context || !context.userId) continue;
|
|
415
|
+
|
|
416
|
+
const matchedNotificationsForUser = [];
|
|
417
|
+
|
|
418
|
+
for (const channelName in notificationFunctions) {
|
|
419
|
+
try {
|
|
420
|
+
const notificationFn = notificationFunctions[channelName];
|
|
421
|
+
const result = notificationFn(context, fullState);
|
|
422
|
+
|
|
423
|
+
if (result !== null && result !== undefined) {
|
|
424
|
+
matchedNotificationsForUser.push({
|
|
425
|
+
channel: channelName,
|
|
426
|
+
payload: { ...result },
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
} catch (fnError) {
|
|
430
|
+
console.error(`[Container] Error executing notification function '${channelName}' for user '${context.userId}':`, fnError);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (matchedNotificationsForUser.length > 0) {
|
|
435
|
+
notificationsByUserId[context.userId] = matchedNotificationsForUser;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return c.json(notificationsByUserId);
|
|
440
|
+
|
|
441
|
+
} catch (error) {
|
|
442
|
+
console.error('[Container] /process-notifications-batch Error:', error);
|
|
443
|
+
return c.json({
|
|
444
|
+
error: 'Internal server error processing notification batch',
|
|
445
|
+
details: error.message
|
|
446
|
+
}, 500);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// Error handling
|
|
451
|
+
app.onError((err, c) => {
|
|
452
|
+
console.error('Unhandled error:', err);
|
|
453
|
+
return c.json({ error: 'Internal server error' }, 500);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// Graceful shutdown handlers
|
|
457
|
+
process.on('SIGTERM', () => {
|
|
458
|
+
console.log('Received SIGTERM, shutting down gracefully');
|
|
459
|
+
process.exit(0);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
process.on('SIGINT', () => {
|
|
463
|
+
console.log('Received SIGINT, shutting down gracefully');
|
|
464
|
+
process.exit(0);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// Export for Cloudflare Container or start server for Node.js
|
|
468
|
+
const PORT = process.env.PORT || 8080;
|
|
469
|
+
|
|
470
|
+
// For Cloudflare Container Runtime
|
|
471
|
+
export default {
|
|
472
|
+
fetch: app.fetch,
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// For Node.js runtime (if needed for local testing)
|
|
476
|
+
if (typeof process !== 'undefined' && process.versions && process.versions.node) {
|
|
477
|
+
const { serve } = await import('@hono/node-server');
|
|
478
|
+
serve({
|
|
479
|
+
fetch: app.fetch,
|
|
480
|
+
port: PORT,
|
|
481
|
+
});
|
|
482
|
+
console.log(`Schema processor listening on port ${PORT}`);
|
|
483
|
+
}
|