@ismaelsoilet/synapsejs 1.4.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.
Files changed (53) hide show
  1. package/README.md +108 -10
  2. package/bin/synapse.ts +263 -218
  3. package/package.json +1 -1
  4. package/src/client/components.tsx +26 -9
  5. package/src/client/outlet.tsx +40 -0
  6. package/src/client/sub-slice.tsx +45 -0
  7. package/src/client/use-websocket.ts +146 -0
  8. package/src/client.ts +12 -0
  9. package/src/compiler/client-bundler.ts +17 -7
  10. package/src/compiler/context-bench.ts +7 -1
  11. package/src/compiler/contract.ts +31 -4
  12. package/src/compiler/db-schema-generator.ts +39 -8
  13. package/src/compiler/fields-parser.ts +28 -1
  14. package/src/compiler/impact-analyzer.ts +31 -1
  15. package/src/compiler/migration-runner.ts +144 -11
  16. package/src/compiler/optional-coverage.ts +137 -0
  17. package/src/compiler/oracle-runner.ts +8 -3
  18. package/src/compiler/scaffolder.ts +174 -3
  19. package/src/compiler/schema-dag.ts +6 -3
  20. package/src/compiler/slice-discovery.ts +8 -1
  21. package/src/compiler/slice-splitter.ts +67 -6
  22. package/src/compiler/slice-templates.ts +10 -0
  23. package/src/compiler/standalone-builder.ts +16 -2
  24. package/src/core/action-context.ts +101 -8
  25. package/src/core/cache.ts +139 -0
  26. package/src/core/config.ts +124 -10
  27. package/src/core/database-client.ts +68 -6
  28. package/src/core/index.ts +5 -0
  29. package/src/core/path-guard.ts +105 -0
  30. package/src/core/postgres-client.ts +6 -0
  31. package/src/core/postgres-queue.ts +45 -7
  32. package/src/core/query-builder.ts +75 -60
  33. package/src/core/session-context.ts +1 -1
  34. package/src/core/session-token.ts +115 -8
  35. package/src/core/sqlite-client.ts +26 -1
  36. package/src/core/storage.ts +61 -24
  37. package/src/core/topics.ts +84 -0
  38. package/src/core/webhook-signature.ts +151 -0
  39. package/src/core/websocket.ts +30 -0
  40. package/src/index.ts +10 -2
  41. package/src/mcp/server.ts +78 -173
  42. package/src/mcp/tools.ts +238 -0
  43. package/src/project-status.ts +330 -0
  44. package/src/runtime/discovery-rules.ts +20 -1
  45. package/src/runtime/event-hub.ts +6 -1
  46. package/src/runtime/network-guard.ts +182 -0
  47. package/src/runtime/queue-engine.ts +102 -21
  48. package/src/runtime/rate-limiter.ts +24 -0
  49. package/src/runtime/server.ts +1380 -251
  50. package/src/runtime/uploads.ts +199 -19
  51. package/src/version.ts +11 -0
  52. package/templates/starter/.jev.json +1 -1
  53. package/templates/starter/tsconfig.json +1 -0
package/README.md CHANGED
@@ -21,7 +21,9 @@ Features are listed with the command that fails when they break. Nothing is docu
21
21
  without one — see the repository's `AGENTS.md` for the full contract.
22
22
 
23
23
  **Stable:** vertical slices (N = 1) · `Result<T, E>` flows · TypeBox JIT contracts · declarative
24
- `sliceSchema` migrations with DAG topological ordering · embedded SQLite (WAL, prepared-statement cache, CTE-safe) ·
24
+ `sliceSchema` migrations with DAG topological ordering · bidirectional `-- up:` / `-- down:` rollbacks (`synapse rollback`) · embedded SQLite (WAL, tuned busy_timeout & cache pragmas, CTE-safe; measured by `bun run bench:concurrency`) ·
25
+ full-duplex WebSockets (`defineSocket` + `useWebSocket`) · hierarchical sub-slice composition & outlets (`<SubSlice>`, `<SliceOutlet>`) · in-memory SSR micro-cache & ISR (`defineCache`) ·
26
+ vendor bundle code-splitting via Import Maps (`compiler.vendorChunks`) · standardized shared domain modules (`synapse new-shared`) ·
25
27
  isomorphic Query Builder with parameterized relational joins and sub-object nesting (`nestJoinedRow`) · centralized Schema Catalog (`.codebase/db-schema.d.ts`) ·
26
28
  declarative UI primitives & hooks (`DataTable`, `DataForm`, `useAction`, `useLoaderData`, `useSubscription`, `SynapseProvider`) ·
27
29
  realtime SSE gateway (`EventHub` + `ctx.broadcast`) · schema drift detector (`synapse db-drift`) · AST diff & cross-slice impact analysis (`synapse impact`) ·
@@ -31,9 +33,11 @@ with rawBody preservation · B2B multi-tenancy & IDOR prevention (`requireTenant
31
33
  and Turbo Morphing router · explicit RBAC via `requireAuth(session, roles)` · zero-wiring routing + SSR + RPC · slice discovery
32
34
  that never reports PASS with zero slices · isomorphic splitter with compile and leak gates · typed AST skeleton map ·
33
35
  scaffolder with `--fields` grammar and templates (create, list, update, delete, login, oauth-github, crud) · standalone production bundler (`synapse build --standalone`) ·
34
- MCP stdio server with 10 tools · slice invariants under `bun:test` with per-invariant reporting · PostgreSQL parity · real concurrency & load stress benchmarks.
36
+ MCP stdio server with 18 native tools (11 Synapse + 7 Jev) · slice invariants under `bun:test` with per-invariant reporting · PostgreSQL parity · real concurrency & load stress benchmarks ·
37
+ SSRF & Network Guard (`validateExternalUrl`, `isPrivateOrReservedIp`) · anti-spoofing & bounded LRU rate limiting · bounded LRU SSR micro-cache with parameter normalization · strict payload size guards (RPC 5MB, Webhook 10MB) & malformed JSON protection · SQLite queue zombie job recovery with `locked_at` visibility timeout · HTTP 500 SSR error isolation & bundle suppression · secure HttpOnly cookies (`ctx.setCookie`) · graceful HTTP shutdown (`server.stop` with connection drain) ·
38
+ Prototype Pollution Shield (`setNestedProperty`, `expandNestedObject`) · Parameterized Query Operator Allowlist (`VALID_WHERE_OPERATORS`) · Reentrant SQLite Savepoints (`txDepth` + `SAVEPOINT`) · Atomic Queue Poison-Pill Neutralization (claim increment & DLQ timeout) · Strict Multi-Tenant Context Derivation (spoofing-proof token claims) · Streaming Upload & Chunked DoS Armor (`readBodyWithinLimit`) · Origin-Validated WebSocket Upgrades (`SYNAPSE_ALLOWED_ORIGINS`) · Safe SSR Serialization (`<script type="application/json">`) & Cookie CRLF Guard.
35
39
 
36
- **Roadmap:** down-migrations / DDL rollbacks, multi-region distributed cache adapters (Redis/Dragonfly).
40
+ **Roadmap:** multi-node distributed cache adapters (Redis/Dragonfly cluster sync), multi-host worker clustering.
37
41
 
38
42
  The repository README carries a measured context-surface benchmark of the same two features
39
43
  implemented with and without the slice convention, including what the numbers do not show.
@@ -43,8 +47,10 @@ implemented with and without the slice convention, including what the numbers do
43
47
  `fc` and `MockDatabaseClient` are deliberate parts of the contract, not accidents: slices declare their
44
48
  invariants with `fc`, and the oracles use the mock. Both are asserted by
45
49
  `packages/synapse/test/machine-types.test.ts` ("public contract"), together with every other export this
46
- README lists. The same block is the list of what 1.0 will freeze. In 0.x a minor bump may still break;
47
- every break is in [CHANGELOG.md](../CHANGELOG.md).
50
+ README lists. The same block is the list of frozen surfaces. A minor bump may still break in 1.x;
51
+ every break is listed in [CHANGELOG.md](../../CHANGELOG.md), each with the flag that restores the
52
+ previous behaviour where one exists — start with
53
+ [`docs/migration-1.9.md`](../../docs/migration-1.9.md) if you are coming from 1.8.
48
54
 
49
55
  ## Quick start
50
56
 
@@ -126,8 +132,98 @@ bun run mcp # JSON-RPC 2.0 over stdio
126
132
 
127
133
  Tools: `synapse_get_repo_map`, `synapse_get_db_schema`, `synapse_check`, `synapse_split`,
128
134
  `synapse_run_pbt`, `synapse_scaffold_slice`, `synapse_migrate`, `synapse_rollback`, `synapse_contract`,
129
- `synapse_check_db_drift`, `synapse_diff_impact`. Slice-dependent tools fail with `NO_SLICES_DIR` (listing the paths they examined)
130
- instead of reporting an empty success.
135
+ `synapse_check_db_drift`, `synapse_diff_impact`, `synapse_test_gate`, `synapse_abort_check`,
136
+ `synapse_verify_completion`, `synapse_reasoning_effort`, `synapse_triage_error`, `synapse_route_task`, `synapse_evaluate_nudge`.
137
+ Slice-dependent tools fail with `NO_SLICES_DIR` (listing the paths they examined) instead of reporting an empty success.
138
+
139
+ ## Write-surface containment
140
+
141
+ Every surface that turns caller input into a filesystem path segment goes through the single
142
+ validator in `core/path-guard.ts`: `synapse new-slice` (`domain` and `name`), `synapse new-shared`
143
+ (`name`), the CRUD scaffold, `synapse new`'s project directory and the upload parser. It rejects
144
+ separators, parent traversal, absolute prefixes, empty segments and over-length input, and the
145
+ resolved target must stay inside the directory the operation owns before anything is written.
146
+ `--fields` names are validated as plain identifiers before they reach the generated DDL, so a crafted
147
+ name cannot end up in a migration.
148
+
149
+ The failures are machine-readable and non-zero:
150
+
151
+ | code | meaning |
152
+ |---|---|
153
+ | `INVALID_PATH_SEGMENT` | a domain, slice name, shared-module name or project directory is not a single safe path segment |
154
+ | `INVALID_FIELD_NAME` | a `--fields` name or enum value is not a plain identifier |
155
+ | `SLICE_EXISTS` | the target file already exists |
156
+ | `WRITE_FAILED` | the filesystem refused the write |
157
+ | `NO_SLICES_DIR` | no `src/slices` was found; `candidates[]` lists every path examined |
158
+ | `AMBIGUOUS_SLICES_DIR` | several workspace members own slices; set `SYNAPSE_ROOT` or run inside the target app |
159
+
160
+ Uploads apply the same name rule and additionally refuse a file whose declared extension is outside
161
+ `SYNAPSE_UPLOAD_ALLOWED_TYPES` (default: png, jpg, jpeg, gif, webp, pdf, txt, csv, json, md) or whose
162
+ bytes do not match that extension, answering `415 UNSUPPORTED_FILE_TYPE`. The framework does not serve
163
+ the upload directory — serving it is the application's responsibility.
164
+
165
+ ## Sessions and cookies
166
+
167
+ With `SYNAPSE_SESSION_SECRET` set, a bearer token must be a signed session and its claims win: role
168
+ headers are ignored and the tenant comes exclusively from the verified claims. Revocation is consulted
169
+ on every request — in-process first, then against the persisted blacklist. Without a secret, identity
170
+ headers are honoured **only** under the explicit development opt-in `SYNAPSE_DEV_HEADERS=true` (or a
171
+ `development`/`test` runtime mode); an unset, staging or production environment resolves every request
172
+ to an anonymous session, and `synapse start` refuses to run in production without a secret unless the
173
+ operator passes `--acknowledge-insecure`.
174
+
175
+ `ctx.setCookie` is secure by default: a cookie written with no options carries `HttpOnly`, `Secure`
176
+ and `SameSite=Lax`, and only an explicit `false` opts out. A cookie written by **browser** script
177
+ (`storeSession`) cannot: `document.cookie` cannot set `HttpOnly`, by construction, so a
178
+ script-written session cookie is readable by any script in the origin. A session that must be
179
+ unreachable by script is the one the server emits through `ctx.setCookie`.
180
+
181
+ ## Realtime: declared topics and a fail-closed upgrade
182
+
183
+ A topic is declared, never inferred — a name is not a permission. Declare it in the slice that owns it:
184
+
185
+ ```typescript
186
+ import { defineTopic } from 'synapsejs';
187
+
188
+ export const sliceRealtime = defineTopic({ name: 'tickets', owner: 'tickets/view-tickets', readRoles: ['support'] });
189
+ ```
190
+
191
+ `GET /_synapse/sse/tickets` then refuses an unauthenticated subscriber (`401 UNAUTHENTICATED`), an
192
+ undeclared topic (`404 TOPIC_NOT_FOUND`) and a session without a declared role
193
+ (`403 TOPIC_FORBIDDEN`). Topics resolve inside the subscriber's tenant, so two tenants using the same
194
+ name never see each other's events, and `ctx.broadcast(topic, data)` notifies zero subscribers unless
195
+ the calling slice is the declared owner. The registry and concurrent subscriptions are bounded
196
+ (`config.realtime`), and a disconnect releases the topic entry.
197
+
198
+ The WebSocket upgrade (`GET /_synapse/ws/<domain>/<name>`) is fail-closed on origin: with no
199
+ `SYNAPSE_ALLOWED_ORIGINS` configured every upgrade is refused, an absent or opaque `null` origin is
200
+ always refused, and only a loopback origin is carved out for local development. Both transports share
201
+ one rate limiter and a per-client cap on concurrent connections. Every refusal is JSON
202
+ (`{ ok: false, error, message }`) with one of `UNAUTHENTICATED`, `TOPIC_NOT_FOUND`,
203
+ `TOPIC_FORBIDDEN`, `RATE_LIMIT_EXCEEDED`, `REALTIME_LIMIT_EXCEEDED`, `ORIGIN_NOT_ALLOWED` — the same
204
+ set `synapse contract` enumerates with its statuses.
205
+
206
+ ## Per-process state, rate limiting and the image allowlist
207
+
208
+ The token bucket rate limiter, the server-render micro-cache and the realtime event hub keep their
209
+ state **in the serving process only**; there is no shared backend. A restart discards counters, cached
210
+ responses and buffered events, and N instances behind a load balancer permit up to N times the
211
+ per-instance allowance with a per-instance cache hit rate. The allowance registry is bounded
212
+ (`config.rateLimit.maxBuckets`, 10,000 by default) and a saturated registry **refuses** the excess
213
+ with `429` instead of evicting a live counter, so a rotating-identity flood cannot reset an abuser's
214
+ allowance. Forwarding headers are trusted only when `SYNAPSE_TRUST_PROXY=true` (or
215
+ `config.trustProxy`) is set; otherwise the peer address is the client identity, and every accepting
216
+ surface — RPC, uploads, webhooks, SSE, WebSocket upgrades, the image endpoint, health, metrics,
217
+ repo-map and server-rendered pages — draws from that one limiter.
218
+
219
+ `/_synapse/images/optimize` requires a session, is rate limited, caps the upstream response
220
+ (`config.imageOptimizer.maxResponseBytes`, 10 MiB by default) and the fetch duration
221
+ (`config.imageOptimizer.timeoutMs`, 10 s by default), connects to the exact address that passed
222
+ `validateExternalUrl` (so the hostname is not resolved a second time) and refuses redirects. When
223
+ `SYNAPSE_ALLOWED_IMAGE_DOMAINS` (or `config.imageOptimizer.allowedDomains`) lists hosts, only those
224
+ hosts are accepted; when nothing is configured **any publicly routable host is permitted** —
225
+ loopback, link-local, private and cloud-metadata addresses are always refused, but there is no
226
+ implicit allowlist beyond that.
131
227
 
132
228
  Client configuration for an agent host:
133
229
 
@@ -182,11 +278,13 @@ import {
182
278
 
183
279
  // Declarative UI Primitives, Hooks & i18n
184
280
  DataTable, DataForm, Button, Card, Badge, Pagination,
185
- SynapseProvider, useAction, useLoaderData, useSubscription, useSession, useSynapseContext,
281
+ SliceOutlet, SliceOutletProvider, SubSlice,
282
+ SynapseProvider, useAction, useLoaderData, useSubscription, useWebSocket, useSession, useSynapseContext,
186
283
  createTranslator,
187
284
 
188
- // Realtime SSE Gateway
285
+ // Realtime & Cache Gateways
189
286
  EventHub, getEventHub, resetEventHub,
287
+ defineSocket, defineCache,
190
288
 
191
289
  // Compiler, Scaffolding, Schemas, Drift & Diagnostics
192
290
  runSliceMigrations, rollbackSliceMigrations, runMachineVerifications,
@@ -194,7 +292,7 @@ import {
194
292
  checkSchemaDrift, analyzeImpact, nestJoinedRow,
195
293
  resolveSlicesDir, findSliceFiles, SLICE_EXTENSION,
196
294
  splitSlice, verifySplit, writeSplitArtifacts, artifactDirectory,
197
- scaffoldSlice, scaffoldCrud, parseFields, compressRepositoryAST, buildStandalone,
295
+ scaffoldSlice, scaffoldCrud, scaffoldShared, parseFields, compressRepositoryAST, buildStandalone,
198
296
 
199
297
  // MCP
200
298
  SynapseMcpServer