bunsane 0.3.1 → 0.3.2
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/.claude/scheduled_tasks.lock +1 -0
- package/CHANGELOG.md +52 -0
- package/config/cache.config.ts +35 -1
- package/core/App.ts +24 -1064
- package/core/ArcheType.ts +78 -2110
- package/core/Entity.ts +10 -33
- package/core/RequestContext.ts +85 -36
- package/core/RequestLoaders.ts +89 -31
- package/core/app/bootstrap.ts +133 -0
- package/core/app/cors.ts +94 -0
- package/core/app/graphqlSetup.ts +56 -0
- package/core/app/healthEndpoints.ts +31 -0
- package/core/app/metricsCollector.ts +27 -0
- package/core/app/preparedStatementWarmup.ts +55 -0
- package/core/app/processHandlers.ts +43 -0
- package/core/app/requestRouter.ts +309 -0
- package/core/app/restRegistry.ts +72 -0
- package/core/app/shutdown.ts +97 -0
- package/core/app/studioRouter.ts +83 -0
- package/core/archetype/customTypes.ts +100 -0
- package/core/archetype/decorators.ts +171 -0
- package/core/archetype/fieldResolvers.ts +621 -0
- package/core/archetype/helpers.ts +29 -0
- package/core/archetype/relationLoader.ts +118 -0
- package/core/archetype/schemaBuilder.ts +141 -0
- package/core/archetype/weaver.ts +218 -0
- package/core/archetype/zodSchemaBuilder.ts +527 -0
- package/core/cache/CacheManager.ts +126 -9
- package/core/middleware/AccessLog.ts +8 -1
- package/database/PreparedStatementCache.ts +12 -3
- package/database/cancellable.ts +22 -0
- package/database/instrumentedDb.ts +141 -0
- package/docs/RFC_APP_REFACTOR.md +248 -0
- package/docs/RFC_REFACTOR_TARGETS.md +251 -0
- package/package.json +1 -1
- package/query/Query.ts +53 -20
- package/tests/integration/loaders/RequestLoaders.abort.test.ts +82 -0
- package/tests/integration/query/Query.abort.test.ts +66 -0
- package/tests/unit/cache/CacheManager.test.ts +132 -1
- package/tests/unit/database/cancellable.test.ts +81 -0
- package/tests/unit/database/instrumentedDb.test.ts +160 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"sessionId":"302022ef-825d-48c8-8ef6-656f1cd141e0","pid":60520,"procStart":"639139204827053470","acquiredAt":1778489386099}
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,58 @@ All notable changes to bunsane are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
### Added (v0.3.2 — AbortSignal propagation + DB observability)
|
|
8
|
+
|
|
9
|
+
- **AbortSignal threading into `Query.exec` + DataLoaders.** Resolvers
|
|
10
|
+
invoked from a GraphQL request now receive the request's `AbortSignal`
|
|
11
|
+
via the request-context plugin. When the framework's 30s wall-clock
|
|
12
|
+
fires (`core/app/requestRouter.ts`), in-flight `db.unsafe()` queries
|
|
13
|
+
are cancelled through Bun's `SQL.Query.cancel()`. Without this an
|
|
14
|
+
aborted request leaked its backend connection into
|
|
15
|
+
`idle in transaction` under pgbouncer transaction-mode pooling,
|
|
16
|
+
cascading into pool starvation under sustained timeout pressure.
|
|
17
|
+
Public surface: `Query.exec({ signal })`, `Query.count({ signal })`,
|
|
18
|
+
`Query.estimatedCount(component, { signal })`,
|
|
19
|
+
`Query.findOneById(id, { signal })`,
|
|
20
|
+
`Query.explainAnalyze(buffers, { signal })`,
|
|
21
|
+
`createRequestLoaders(db, cache?, signal?, perRequest?)`.
|
|
22
|
+
Reuses helper `runWithSignal` extracted to `database/cancellable.ts`
|
|
23
|
+
and shared with the existing `Entity.doSave` / `Entity.doDelete`
|
|
24
|
+
abort paths.
|
|
25
|
+
|
|
26
|
+
- **DB roundtrip observability (`database/instrumentedDb.ts`).** Every
|
|
27
|
+
`db.unsafe()` callsite in `Query.ts`, `RequestLoaders.ts` and the
|
|
28
|
+
shared `PreparedStatementCache.execute` now routes through
|
|
29
|
+
`timedUnsafe`. Tracks `totalCount`, `totalMs`, `maxMs`, `avgMs`,
|
|
30
|
+
`slowCount`, `abortedCount`, `inFlightMax`, plus per-DataLoader-kind
|
|
31
|
+
counters. Exposed at `/metrics` under the new `db` key. Calls over
|
|
32
|
+
`BUNSANE_DB_SLOW_MS` (default 500ms, set 0 to disable warn) log a
|
|
33
|
+
structured `Slow DB call` warning with a SQL snippet.
|
|
34
|
+
|
|
35
|
+
- **Per-request stats on access + timeout logs.** GraphQL request
|
|
36
|
+
context now captures `operationName`, `dataLoaderCalls`
|
|
37
|
+
(entity / component / relation), and `dbQueryCount`. These attach to
|
|
38
|
+
the underlying `Request` via `__bunsaneStats` so the HTTP router's
|
|
39
|
+
catch block and `AccessLog` middleware can include them in every
|
|
40
|
+
log line. The previous `Request failed after 30004ms: POST /graphql`
|
|
41
|
+
log now carries enough fields to identify the offending operation
|
|
42
|
+
without re-running production with a debug build. Timeout warn log
|
|
43
|
+
also includes operation name when reachable.
|
|
44
|
+
|
|
45
|
+
### Env vars added
|
|
46
|
+
|
|
47
|
+
- `BUNSANE_DB_SLOW_MS` (default `500`) — per-call DB threshold for
|
|
48
|
+
slow log + `slowCount` metric. Set `0` to suppress the warn (stats
|
|
49
|
+
still accumulate).
|
|
50
|
+
|
|
51
|
+
### Backward compatibility
|
|
52
|
+
|
|
53
|
+
All additions are opt-in. Existing apps see no behavior change:
|
|
54
|
+
`Query.exec()`, `Query.count()`, `createRequestLoaders(db, cache)`,
|
|
55
|
+
and `preparedStatementCache.execute(s, p, db)` retain their pre-0.3.2
|
|
56
|
+
signatures. `/metrics` gains a `db` key (pure addition). Log lines
|
|
57
|
+
gain fields but preserve existing ones.
|
|
58
|
+
|
|
7
59
|
### Added (HR-Screening ticket batch — BUNSANE-002..006)
|
|
8
60
|
|
|
9
61
|
- **`@ScheduledTask` allows entity-less time-based tasks.** Previously
|
package/config/cache.config.ts
CHANGED
|
@@ -31,6 +31,29 @@ export interface CacheConfig {
|
|
|
31
31
|
component?: {
|
|
32
32
|
enabled: boolean;
|
|
33
33
|
ttl: number;
|
|
34
|
+
/**
|
|
35
|
+
* Cache "absent" component lookups (negative cache) so repeated reads
|
|
36
|
+
* of optional components on the same entity do not re-hit the DB.
|
|
37
|
+
* Default false (opt-in) to preserve prior behavior.
|
|
38
|
+
*/
|
|
39
|
+
negativeCacheEnabled?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* TTL in ms for tombstone entries written for absent components.
|
|
42
|
+
* Defaults to min(component.ttl, 60_000). Keep ≤ ttl so a created
|
|
43
|
+
* row supersedes the tombstone within bounded staleness.
|
|
44
|
+
*/
|
|
45
|
+
negativeCacheTtl?: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Negative-only cache for relation lookups (e.g. @HasMany returning []).
|
|
50
|
+
* Empty results are cached with a short TTL; positive results are not
|
|
51
|
+
* cached here (cross-entity invalidation is non-trivial — see RFC
|
|
52
|
+
* H-CACHE-NEG). Bounded staleness window equal to negativeCacheTtl.
|
|
53
|
+
*/
|
|
54
|
+
relation?: {
|
|
55
|
+
negativeCacheEnabled?: boolean;
|
|
56
|
+
negativeCacheTtl?: number;
|
|
34
57
|
};
|
|
35
58
|
|
|
36
59
|
query?: {
|
|
@@ -76,7 +99,18 @@ export const defaultCacheConfig: CacheConfig = {
|
|
|
76
99
|
|
|
77
100
|
component: {
|
|
78
101
|
enabled: process.env.CACHE_COMPONENT_ENABLED !== 'false', // Default true
|
|
79
|
-
ttl: parseInt(process.env.CACHE_COMPONENT_TTL || '1800000') // 30 minutes
|
|
102
|
+
ttl: parseInt(process.env.CACHE_COMPONENT_TTL || '1800000'), // 30 minutes
|
|
103
|
+
negativeCacheEnabled: process.env.CACHE_COMPONENT_NEGATIVE_ENABLED === 'true',
|
|
104
|
+
negativeCacheTtl: process.env.CACHE_COMPONENT_NEGATIVE_TTL
|
|
105
|
+
? parseInt(process.env.CACHE_COMPONENT_NEGATIVE_TTL)
|
|
106
|
+
: undefined
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
relation: {
|
|
110
|
+
negativeCacheEnabled: process.env.CACHE_RELATION_NEGATIVE_ENABLED === 'true',
|
|
111
|
+
negativeCacheTtl: process.env.CACHE_RELATION_NEGATIVE_TTL
|
|
112
|
+
? parseInt(process.env.CACHE_RELATION_NEGATIVE_TTL)
|
|
113
|
+
: 60_000
|
|
80
114
|
},
|
|
81
115
|
|
|
82
116
|
query: {
|