@ismaelsoilet/synapsejs 1.8.0 → 1.9.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/README.md +95 -4
- package/bin/synapse.ts +217 -239
- package/package.json +1 -1
- package/src/compiler/context-bench.ts +7 -1
- package/src/compiler/contract.ts +31 -4
- package/src/compiler/db-schema-generator.ts +39 -8
- package/src/compiler/fields-parser.ts +28 -1
- package/src/compiler/impact-analyzer.ts +31 -1
- package/src/compiler/migration-runner.ts +10 -2
- package/src/compiler/optional-coverage.ts +137 -0
- package/src/compiler/oracle-runner.ts +8 -3
- package/src/compiler/scaffolder.ts +114 -5
- package/src/compiler/slice-discovery.ts +8 -1
- package/src/compiler/slice-splitter.ts +47 -3
- package/src/core/action-context.ts +51 -14
- package/src/core/config.ts +88 -10
- package/src/core/database-client.ts +68 -6
- package/src/core/index.ts +3 -0
- package/src/core/path-guard.ts +105 -0
- package/src/core/postgres-queue.ts +25 -7
- package/src/core/session-token.ts +108 -7
- package/src/core/storage.ts +60 -19
- package/src/core/topics.ts +84 -0
- package/src/core/webhook-signature.ts +151 -0
- package/src/index.ts +2 -1
- package/src/mcp/server.ts +60 -217
- package/src/mcp/tools.ts +238 -0
- package/src/project-status.ts +330 -0
- package/src/runtime/network-guard.ts +3 -1
- package/src/runtime/queue-engine.ts +47 -0
- package/src/runtime/rate-limiter.ts +14 -4
- package/src/runtime/server.ts +876 -180
- package/src/runtime/uploads.ts +199 -19
- package/src/version.ts +9 -2
- package/templates/starter/tsconfig.json +1 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SynapseJS - Project Status (single source of truth)
|
|
3
|
+
*
|
|
4
|
+
* Version, feature maturity, adoption and the tool inventory live here, in one
|
|
5
|
+
* machine-readable module. `synapse info`, the health endpoint, the MCP server
|
|
6
|
+
* metadata and the documentation gate all read it, so a claim cannot drift from a
|
|
7
|
+
* value a test is able to check — and no surface carries a version literal of its own.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { MCP_TOOL_NAMES } from './mcp/tools';
|
|
11
|
+
import { SYNAPSE_VERSION } from './version';
|
|
12
|
+
|
|
13
|
+
export interface FeatureStatus {
|
|
14
|
+
feature: string;
|
|
15
|
+
/**
|
|
16
|
+
* `stable` requires a test that fails when the behaviour breaks and, where the
|
|
17
|
+
* capability wraps an engine, a suite that executes against a real engine.
|
|
18
|
+
* `experimental` means it works but its evidence is a template assertion or a
|
|
19
|
+
* test double. `roadmap` means it is not shipped.
|
|
20
|
+
*/
|
|
21
|
+
status: 'stable' | 'experimental' | 'roadmap';
|
|
22
|
+
evidence: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The feature inventory. `experimental` is not an insult: it states which capabilities
|
|
27
|
+
* have no behavioural test or no real-engine suite yet, instead of advertising them.
|
|
28
|
+
*/
|
|
29
|
+
export const FEATURE_STATUS: FeatureStatus[] = [
|
|
30
|
+
{
|
|
31
|
+
feature: 'Vertical slices (N = 1) with Locality of Behavior',
|
|
32
|
+
status: 'stable',
|
|
33
|
+
evidence: 'bun test packages/synapse/test/locality.test.ts'
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
feature: 'Result<T, E> control flow (no public throwing helper)',
|
|
37
|
+
status: 'stable',
|
|
38
|
+
evidence: 'bun test packages/synapse/test/machine-types.test.ts'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
feature: 'TypeBox JIT input contracts',
|
|
42
|
+
status: 'stable',
|
|
43
|
+
evidence: 'bun test packages/synapse/test/slice-contract.test.ts'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
feature: 'Declarative sliceSchema migrations, applied once per statement',
|
|
47
|
+
status: 'stable',
|
|
48
|
+
evidence: 'bun test packages/synapse/test/migration-runner.test.ts'
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
feature: 'Domain errors map to HTTP status (401/403/404/409/422/500)',
|
|
52
|
+
status: 'stable',
|
|
53
|
+
evidence: 'bun test packages/synapse/test/runtime-server.test.ts'
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
feature: 'Zero-wiring routing, SSR shell and RPC dispatcher',
|
|
57
|
+
status: 'stable',
|
|
58
|
+
evidence: 'bun --cwd examples/enterprise-crm test:e2e'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
feature: 'Signed sessions (SYNAPSE_SESSION_SECRET), tenant from verified claims only',
|
|
62
|
+
status: 'stable',
|
|
63
|
+
evidence: 'bun test packages/synapse/test/session-security.test.ts'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
feature: 'Request-time session revocation with a bounded in-process set and prunable table',
|
|
67
|
+
status: 'stable',
|
|
68
|
+
evidence: 'bun test packages/synapse/test/session-security.test.ts'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
feature: 'Login with Bun.password, signed token and cookie session (storeSession/clearSession)',
|
|
72
|
+
status: 'stable',
|
|
73
|
+
evidence: 'bun --cwd apps/crm test (login oracle: the server accepts the issued token)'
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
feature: 'Caller-supplied identity headers only under the explicit SYNAPSE_DEV_HEADERS opt-in',
|
|
77
|
+
status: 'stable',
|
|
78
|
+
evidence: 'bun test packages/synapse/test/session-security.test.ts'
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
feature: 'Cross-slice atomicity through src/shared modules (db.transaction, rollback proven on SQLite)',
|
|
82
|
+
status: 'stable',
|
|
83
|
+
evidence: 'bun test packages/synapse/test/shared-modules.test.ts'
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
feature: 'Slice boundary: a slice never imports another slice (SLICE_IMPORTS_SLICE)',
|
|
87
|
+
status: 'stable',
|
|
88
|
+
evidence: 'bun test packages/synapse/test/slice-splitter.test.ts'
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
feature: 'Browser RPC transport that never throws (RPC_MALFORMED/RPC_UNREACHABLE as values)',
|
|
92
|
+
status: 'stable',
|
|
93
|
+
evidence: 'bun test packages/synapse/test/rpc-client.test.ts'
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
feature: 'Pre-built client bundles with a manifest (synapse build)',
|
|
97
|
+
status: 'stable',
|
|
98
|
+
evidence: 'bun test packages/synapse/test/client-bundler.test.ts'
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
feature: 'Uploads with session, byte ceiling, content sniffing and traversal guard (/_synapse/files)',
|
|
102
|
+
status: 'stable',
|
|
103
|
+
evidence: 'bun test packages/synapse/test/uploads.test.ts'
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
feature: 'Embedded SQLite engine (WAL, prepared-statement cache)',
|
|
107
|
+
status: 'stable',
|
|
108
|
+
evidence: 'bun test packages/synapse/test/sqlite-client.test.ts'
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
feature: 'Slice discovery resolution (never reports PASS with zero slices)',
|
|
112
|
+
status: 'stable',
|
|
113
|
+
evidence: 'bun test packages/synapse/test/slice-discovery.test.ts'
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
feature: 'Centralized Database Schema Catalog (.codebase/db-schema.d.ts)',
|
|
117
|
+
status: 'stable',
|
|
118
|
+
evidence: 'bun test packages/synapse/test/db-schema-generator.test.ts'
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
feature: 'Unified Object Storage (LocalStorage + AWS S3/R2 with SigV4 signing on upload and delete)',
|
|
122
|
+
status: 'stable',
|
|
123
|
+
evidence: 'bun test packages/synapse/test/storage.test.ts'
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
feature: 'Isomorphic slice splitter (shared/server/client modules)',
|
|
127
|
+
status: 'stable',
|
|
128
|
+
evidence: 'bun test packages/synapse/test/slice-splitter.test.ts'
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
feature: 'Slice invariants executed by bun:test (per-invariant reporting)',
|
|
132
|
+
status: 'stable',
|
|
133
|
+
evidence: 'bun test packages/synapse/test/oracle-runner.test.ts'
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
feature: 'Rate limiting with an opt-in proxy trust and a registry that refuses the excess',
|
|
137
|
+
status: 'stable',
|
|
138
|
+
evidence: 'bun test packages/synapse/test/production-hardening.test.ts'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
feature: 'Token bucket rate limiting on RPC, uploads, webhooks, SSE, images, machine endpoints and pages',
|
|
142
|
+
status: 'stable',
|
|
143
|
+
evidence: 'bun test packages/synapse/test/abuse-resistance.test.ts'
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
feature: 'SSRF & network guard with the validated address pinned for the actual fetch',
|
|
147
|
+
status: 'stable',
|
|
148
|
+
evidence: 'bun test packages/synapse/test/abuse-resistance.test.ts'
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
feature: 'Bounded LRU SSR cache and query normalization',
|
|
152
|
+
status: 'stable',
|
|
153
|
+
evidence: 'bun test packages/synapse/test/production-hardening.test.ts'
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
feature: 'Isolated SQLite queue engine (defineJob, exponential backoff, interrupt-safe shutdown)',
|
|
157
|
+
status: 'stable',
|
|
158
|
+
evidence: 'bun test packages/synapse/test/graceful-shutdown.test.ts'
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
feature: 'SQLite queue zombie-job visibility recovery',
|
|
162
|
+
status: 'stable',
|
|
163
|
+
evidence: 'bun test packages/synapse/test/production-hardening.test.ts'
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
feature: 'Graceful HTTP shutdown: configured drain, 503 DRAINING for the excess, persistence closed last',
|
|
167
|
+
status: 'stable',
|
|
168
|
+
evidence: 'bun test packages/synapse/test/graceful-shutdown.test.ts'
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
feature: 'Bidirectional DDL migrations with transactional rollback (synapse rollback)',
|
|
172
|
+
status: 'stable',
|
|
173
|
+
evidence:
|
|
174
|
+
'bun test packages/synapse/test/feature-exercise.test.ts (a shipped slice declares a reversible block and rolls it back)'
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
feature: 'Declared realtime topics over SSE (defineTopic, tenant-namespaced, bounded registry)',
|
|
178
|
+
status: 'stable',
|
|
179
|
+
evidence: 'bun test packages/synapse/test/sse-gateway.test.ts'
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
feature: 'Native bidirectional WebSockets (defineSocket) with a fail-closed origin check',
|
|
183
|
+
status: 'stable',
|
|
184
|
+
evidence: 'bun test packages/synapse/test/http-response-hardening.test.ts'
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
feature: 'In-memory SSR micro-cache & ISR (defineCache, stale-while-revalidate)',
|
|
188
|
+
status: 'stable',
|
|
189
|
+
evidence: 'bun test packages/synapse/test/onda7-deficiencies.test.tsx'
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
feature: 'Vendor code-splitting and dynamic metadata (sliceMeta)',
|
|
193
|
+
status: 'stable',
|
|
194
|
+
evidence: 'bun test packages/synapse/test/onda1.test.ts'
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
feature: 'Hierarchical domain layouts (_layout.tsx)',
|
|
198
|
+
status: 'stable',
|
|
199
|
+
evidence: 'bun test packages/synapse/test/onda1.test.ts'
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
feature: 'MCP stdio server (JSON-RPC 2.0) with the tool inventory derived from the registered list',
|
|
203
|
+
status: 'stable',
|
|
204
|
+
evidence: 'bun test packages/synapse/test/mcp-server.test.ts'
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
feature: 'AST skeletonizer to .codebase/repo-map.d.ts',
|
|
208
|
+
status: 'stable',
|
|
209
|
+
evidence: 'bun test packages/synapse/test/repo-map.test.ts'
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
feature: 'PostgreSQL parity (migrations, DDL, action round-trip) against a live engine',
|
|
213
|
+
status: 'stable',
|
|
214
|
+
evidence: 'TEST_DATABASE_URL=... bun run test:postgres (CI provides a postgres:16 service)'
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
feature: 'Distributed PostgreSQL queue (SKIP LOCKED, full jitter, dead-letter queue)',
|
|
218
|
+
status: 'experimental',
|
|
219
|
+
evidence:
|
|
220
|
+
'real-engine suite: packages/synapse/test/postgres-adapters.test.ts (gated on TEST_DATABASE_URL; reports skipped without an engine, run by CI) — the substring suite postgres-queue.test.ts stays only as a change detector'
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
feature: 'Distributed PostgreSQL event hub (LISTEN, payload offload)',
|
|
224
|
+
status: 'experimental',
|
|
225
|
+
evidence:
|
|
226
|
+
'real-engine suite: packages/synapse/test/postgres-adapters.test.ts (gated on TEST_DATABASE_URL; reports skipped without an engine, run by CI); onda3.test.ts covers the in-process path'
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
feature: 'On-demand image optimizer (resize, convert, remote allowlist)',
|
|
230
|
+
status: 'experimental',
|
|
231
|
+
evidence:
|
|
232
|
+
'bun test packages/synapse/test/onda4.test.ts exercises the guard rails; the conversion path has no real test yet'
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
feature: 'Webhooks (POST /_synapse/webhooks/<domain>/<name>) with HMAC signature and a bounded replay window',
|
|
236
|
+
status: 'stable',
|
|
237
|
+
evidence:
|
|
238
|
+
'bun --cwd examples/enterprise-crm test (stripe-payment oracle: valid, tampered, replayed and stale deliveries)'
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
feature: 'React hydration from a client bundle the splitter produces',
|
|
242
|
+
status: 'stable',
|
|
243
|
+
evidence:
|
|
244
|
+
'bun test packages/synapse/test/hydration.test.tsx (mounts the layout tree, asserts no mismatch and live state)'
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
feature: 'Isomorphic i18n routing (/<locale>/...)',
|
|
248
|
+
status: 'stable',
|
|
249
|
+
evidence: 'bun test packages/synapse/test/onda3.test.ts'
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
feature: 'Multi-tenancy: tenant from verified claims, per-tenant topic and cache isolation',
|
|
253
|
+
status: 'stable',
|
|
254
|
+
evidence: 'bun test packages/synapse/test/multi-tenancy.test.ts'
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
feature: 'Context surface benchmark (files/tokens an agent must read)',
|
|
258
|
+
status: 'stable',
|
|
259
|
+
evidence: 'bun run bench'
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
feature: 'Background jobs (defineJob) exercised end to end by a committed slice',
|
|
263
|
+
status: 'stable',
|
|
264
|
+
evidence:
|
|
265
|
+
'bun --cwd examples/enterprise-crm test (enqueue-welcome-email oracle: the job handler writes the delivery row)'
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
feature: 'WebSocket definitions (defineSocket) exercised by a committed slice',
|
|
269
|
+
status: 'stable',
|
|
270
|
+
evidence: 'bun --cwd examples/enterprise-crm test (chat/room oracle: join and echo frames)'
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
feature: 'SSR cache definitions (defineCache) exercised by a committed slice',
|
|
274
|
+
status: 'stable',
|
|
275
|
+
evidence: 'bun --cwd examples/enterprise-crm test (catalog/products oracle: policy and filtered loader)'
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
feature: 'Server-side metadata (sliceMeta) and reversible migration blocks in shipped slices',
|
|
279
|
+
status: 'stable',
|
|
280
|
+
evidence: 'bun test packages/synapse/test/feature-exercise.test.ts (rollback rolls back a real statement)'
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
feature: 'Incremental diagnostics cache across processes',
|
|
284
|
+
status: 'roadmap',
|
|
285
|
+
evidence: 'removido na 0.4.0: medido mais lento que o check completo (2.5s vs 1.9s)'
|
|
286
|
+
}
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
/** What the project can honestly say about external adoption today. */
|
|
290
|
+
export const ADOPTION = {
|
|
291
|
+
externalAdopters: 0,
|
|
292
|
+
basis:
|
|
293
|
+
'Sem adoção externa registrada: nenhum deploy de terceiros é conhecido, e a classificação acima reflete a evidência de teste, não uso em produção.'
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
export function projectStatus() {
|
|
297
|
+
return {
|
|
298
|
+
framework: 'SynapseJS',
|
|
299
|
+
version: SYNAPSE_VERSION,
|
|
300
|
+
runtime: 'Bun + Bun.serve',
|
|
301
|
+
database: 'Embedded SQLite (WAL) and PostgreSQL, both verified',
|
|
302
|
+
protocols: ['REST/HTTP', 'Isomorphic RPC', 'Model Context Protocol (MCP)'],
|
|
303
|
+
adoption: ADOPTION,
|
|
304
|
+
toolCount: MCP_TOOL_NAMES.length,
|
|
305
|
+
tools: MCP_TOOL_NAMES,
|
|
306
|
+
features: FEATURE_STATUS,
|
|
307
|
+
commands: [
|
|
308
|
+
'new',
|
|
309
|
+
'dev',
|
|
310
|
+
'start',
|
|
311
|
+
'check',
|
|
312
|
+
'migrate',
|
|
313
|
+
'rollback',
|
|
314
|
+
'db-drift',
|
|
315
|
+
'impact',
|
|
316
|
+
'mcp',
|
|
317
|
+
'skeleton',
|
|
318
|
+
'db-schema',
|
|
319
|
+
'split',
|
|
320
|
+
'build',
|
|
321
|
+
'test',
|
|
322
|
+
'worker',
|
|
323
|
+
'new-slice',
|
|
324
|
+
'new-shared',
|
|
325
|
+
'contract',
|
|
326
|
+
'coverage',
|
|
327
|
+
'info'
|
|
328
|
+
]
|
|
329
|
+
};
|
|
330
|
+
}
|
|
@@ -159,7 +159,9 @@ export async function validateExternalUrl(
|
|
|
159
159
|
return { ok: true, resolvedIp: hostname };
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
//
|
|
162
|
+
// Pre-flight resolution. The address returned here is what the caller must connect
|
|
163
|
+
// to — this check alone does not prevent rebinding, pinning the fetch to
|
|
164
|
+
// `resolvedIp` is what does (see fetchPinnedExternal in runtime/server.ts).
|
|
163
165
|
try {
|
|
164
166
|
const records = await dns.promises.lookup(hostname, { all: true });
|
|
165
167
|
if (!records || records.length === 0) {
|
|
@@ -50,6 +50,8 @@ export class QueueEngine {
|
|
|
50
50
|
private registry: Map<string, JobDefinition<unknown>> = new Map();
|
|
51
51
|
private logger: StructuredLogger;
|
|
52
52
|
private visibilityTimeoutMs: number;
|
|
53
|
+
/** Set at the start of shutdown: no new claim is taken from that moment on. */
|
|
54
|
+
private claimingStopped = false;
|
|
53
55
|
|
|
54
56
|
constructor(options: QueueEngineOptions = {}) {
|
|
55
57
|
this.logger = options.logger || createDefaultLogger('QueueEngine');
|
|
@@ -142,11 +144,56 @@ export class QueueEngine {
|
|
|
142
144
|
return id;
|
|
143
145
|
}
|
|
144
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Stops the engine from claiming any new work. Jobs enqueued by a request that
|
|
149
|
+
* was in flight remain pending for the next process, instead of being claimed
|
|
150
|
+
* during a shutdown that is about to close the database.
|
|
151
|
+
*/
|
|
152
|
+
stopClaiming(): void {
|
|
153
|
+
this.claimingStopped = true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
get isClaimingStopped(): boolean {
|
|
157
|
+
return this.claimingStopped;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Returns every job that is still marked running to the queue. The attempt
|
|
162
|
+
* counter is left exactly as the interrupted claim left it — releasing must not
|
|
163
|
+
* consume a second attempt for one claim — and a job that already reached its
|
|
164
|
+
* maximum is marked failed instead of being handed back for another run.
|
|
165
|
+
*/
|
|
166
|
+
interruptRunningJobs(): { requeued: number; failed: number } {
|
|
167
|
+
const now = Date.now();
|
|
168
|
+
|
|
169
|
+
const failed = this.db
|
|
170
|
+
.prepare(`
|
|
171
|
+
UPDATE _synapse_jobs
|
|
172
|
+
SET status = 'failed', locked_at = NULL, updated_at = ?1, last_error = 'Interrompido pelo encerramento do processo'
|
|
173
|
+
WHERE status = 'running' AND attempts >= max_attempts
|
|
174
|
+
`)
|
|
175
|
+
.run(now);
|
|
176
|
+
|
|
177
|
+
const requeued = this.db
|
|
178
|
+
.prepare(`
|
|
179
|
+
UPDATE _synapse_jobs
|
|
180
|
+
SET status = 'pending', locked_at = NULL, updated_at = ?1, last_error = 'Interrompido pelo encerramento do processo'
|
|
181
|
+
WHERE status = 'running' AND attempts < max_attempts
|
|
182
|
+
`)
|
|
183
|
+
.run(now);
|
|
184
|
+
|
|
185
|
+
return { requeued: Number(requeued.changes ?? 0), failed: Number(failed.changes ?? 0) };
|
|
186
|
+
}
|
|
187
|
+
|
|
145
188
|
/**
|
|
146
189
|
* Atomically claims the next pending job eligible for execution, or recovers
|
|
147
190
|
* any stale 'running' job whose worker has died (visibility timeout exceeded).
|
|
148
191
|
*/
|
|
149
192
|
claimNextJob(visibilityTimeoutMs?: number): JobRecord | null {
|
|
193
|
+
if (this.claimingStopped) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
150
197
|
const now = Date.now();
|
|
151
198
|
const timeout = visibilityTimeoutMs ?? this.visibilityTimeoutMs;
|
|
152
199
|
const staleThreshold = now - timeout;
|
|
@@ -27,6 +27,8 @@ export interface RateLimitResult {
|
|
|
27
27
|
limit: number;
|
|
28
28
|
remaining: number;
|
|
29
29
|
resetMs: number;
|
|
30
|
+
/** True when the refusal is due to a saturated registry rather than an exhausted bucket. */
|
|
31
|
+
saturated?: boolean;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
interface Bucket {
|
|
@@ -61,6 +63,11 @@ export class TokenBucketRateLimiter {
|
|
|
61
63
|
|
|
62
64
|
/**
|
|
63
65
|
* Consumes `cost` tokens from the bucket for `key`.
|
|
66
|
+
*
|
|
67
|
+
* When the registry is saturated with distinct identities the excess is refused
|
|
68
|
+
* instead of evicting a live bucket: eviction would hand a client that is over its
|
|
69
|
+
* allowance a fresh one, which is precisely the bypass a spoofed identity stream
|
|
70
|
+
* is looking for.
|
|
64
71
|
*/
|
|
65
72
|
consume(key: string, cost = 1): RateLimitResult {
|
|
66
73
|
const now = Date.now();
|
|
@@ -68,10 +75,13 @@ export class TokenBucketRateLimiter {
|
|
|
68
75
|
|
|
69
76
|
if (!bucket) {
|
|
70
77
|
if (this.buckets.size >= this.maxBuckets) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
this.
|
|
74
|
-
|
|
78
|
+
return {
|
|
79
|
+
allowed: false,
|
|
80
|
+
limit: this.capacity,
|
|
81
|
+
remaining: 0,
|
|
82
|
+
resetMs: Math.max(1000, Math.ceil(this.idleTimeoutMs / 4)),
|
|
83
|
+
saturated: true
|
|
84
|
+
};
|
|
75
85
|
}
|
|
76
86
|
bucket = {
|
|
77
87
|
tokens: this.capacity,
|