@noorm/broccolidb 2.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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -0
  3. package/dist/TokenCompressionService.d.ts +59 -0
  4. package/dist/TokenCompressionService.d.ts.map +1 -0
  5. package/dist/TokenCompressionService.js +179 -0
  6. package/dist/TokenCompressionService.js.map +1 -0
  7. package/dist/broccolidb-aggregation.d.ts +14 -0
  8. package/dist/broccolidb-aggregation.d.ts.map +1 -0
  9. package/dist/broccolidb-aggregation.js +158 -0
  10. package/dist/broccolidb-aggregation.js.map +1 -0
  11. package/dist/broccolidb-cas.d.ts +56 -0
  12. package/dist/broccolidb-cas.d.ts.map +1 -0
  13. package/dist/broccolidb-cas.js +285 -0
  14. package/dist/broccolidb-cas.js.map +1 -0
  15. package/dist/broccolidb-kernel.d.ts +63 -0
  16. package/dist/broccolidb-kernel.d.ts.map +1 -0
  17. package/dist/broccolidb-kernel.js +287 -0
  18. package/dist/broccolidb-kernel.js.map +1 -0
  19. package/dist/broccolidb-mutex.d.ts +37 -0
  20. package/dist/broccolidb-mutex.d.ts.map +1 -0
  21. package/dist/broccolidb-mutex.js +121 -0
  22. package/dist/broccolidb-mutex.js.map +1 -0
  23. package/dist/broccolidb-natural-query.d.ts +13 -0
  24. package/dist/broccolidb-natural-query.d.ts.map +1 -0
  25. package/dist/broccolidb-natural-query.js +188 -0
  26. package/dist/broccolidb-natural-query.js.map +1 -0
  27. package/dist/broccolidb-table.d.ts +62 -0
  28. package/dist/broccolidb-table.d.ts.map +1 -0
  29. package/dist/broccolidb-table.js +893 -0
  30. package/dist/broccolidb-table.js.map +1 -0
  31. package/dist/broccolidb-wal.d.ts +49 -0
  32. package/dist/broccolidb-wal.d.ts.map +1 -0
  33. package/dist/broccolidb-wal.js +168 -0
  34. package/dist/broccolidb-wal.js.map +1 -0
  35. package/dist/broccolidb.contracts.d.ts +232 -0
  36. package/dist/broccolidb.contracts.d.ts.map +1 -0
  37. package/dist/broccolidb.contracts.js +7 -0
  38. package/dist/broccolidb.contracts.js.map +1 -0
  39. package/dist/index.d.ts +17 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +17 -0
  42. package/dist/index.js.map +1 -0
  43. package/docs/API.md +208 -0
  44. package/docs/ARCHITECTURE.md +160 -0
  45. package/docs/BRIEF.md +56 -0
  46. package/docs/CONTRIBUTING.md +82 -0
  47. package/docs/GLOSSARY.md +23 -0
  48. package/docs/OPERATIONS.md +176 -0
  49. package/docs/PHILOSOPHY.md +91 -0
  50. package/docs/README.md +116 -0
  51. package/docs/RELEASE_NOTES.md +34 -0
  52. package/docs/TROUBLESHOOTING.md +145 -0
  53. package/docs/adr/ADR-001-portable-inmemory-kernel.md +74 -0
  54. package/docs/adr/README.md +38 -0
  55. package/docs/adr/TEMPLATE.md +35 -0
  56. package/package.json +43 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BroccoliDB contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,219 @@
1
+ # @noorm/broccolidb
2
+
3
+ Portable, dependency-free in-memory tables with explicit file-backed durability.
4
+
5
+ BroccoliDB is an embeddable TypeScript database kernel for applications that
6
+ want a fast table hot path without SQLite, Kysely, a native addon, or a server
7
+ process. Records live in memory for reads and indexes; the kernel persists
8
+ mutations through a micro-batched, checksum-linked write-ahead log (WAL),
9
+ periodic JSON checkpoints, and an optional content-addressable storage (CAS)
10
+ vault for large blobs.
11
+
12
+ > BroccoliDB is a table-first embedded library, not a SQL engine or a remote
13
+ > database service. Its public contracts are TypeScript interfaces and
14
+ > deterministic file formats.
15
+
16
+ [![Node.js](https://img.shields.io/badge/Node.js-18%2B-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
17
+ [![TypeScript](https://img.shields.io/badge/TypeScript-ES2022-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
18
+ [![Runtime dependencies](https://img.shields.io/badge/runtime%20dependencies-0-2ea44f)](#portability-and-boundaries)
19
+ [![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
20
+
21
+ ## Table of contents
22
+
23
+ - [At a glance](#at-a-glance)
24
+ - [Quick start](#quick-start)
25
+ - [How durability works](#how-durability-works)
26
+ - [Public surface](#public-surface)
27
+ - [Storage layout](#storage-layout)
28
+ - [Portability and boundaries](#portability-and-boundaries)
29
+ - [Documentation](#documentation)
30
+ - [Development and verification](#development-and-verification)
31
+ - [Compatibility policy](#compatibility-policy)
32
+ - [License](#license)
33
+
34
+ ## At a glance
35
+
36
+ | Capability | What it provides |
37
+ |---|---|
38
+ | In-memory tables | Typed records, CRUD, bulk writes, TTL expiration, snapshots, and reactive change events |
39
+ | Indexes | Equality, sorted/range, composite, and prefix indexes |
40
+ | Queries | Operator filters, boolean clauses, ordering, pagination, a fluent builder, and deterministic natural-language parsing |
41
+ | Aggregation | `sum`, `avg`, `min`, `max`, `count`, `stddev`, grouping, `having`, and grand totals |
42
+ | Durability | Micro-batched WAL frames, checksum validation, checkpoint rotation, replay, and rollback |
43
+ | Blob storage | SHA-256 addressed files, optional Brotli compression, read verification, quarantine, and garbage collection |
44
+ | Coordination | Re-entrant async mutex and kernel-level transaction boundary |
45
+ | Portability | ESM package, Node built-ins only at runtime, no SQLite or native ABI |
46
+
47
+ ## Quick start
48
+
49
+ ```bash
50
+ npm install @noorm/broccolidb
51
+ ```
52
+
53
+ ```ts
54
+ import { BroccoliDatabaseKernel } from "@noorm/broccolidb"
55
+
56
+ type User = {
57
+ id: string
58
+ name: string
59
+ team: string
60
+ active: boolean
61
+ }
62
+
63
+ const db = new BroccoliDatabaseKernel({ workspaceRoot: "./state" })
64
+ await db.start()
65
+
66
+ const users = db.getTable<User>("users")
67
+ users.createIndex("team")
68
+ users.createIndex("active")
69
+
70
+ await db.transaction(async () => {
71
+ users.put("user-1", { id: "user-1", name: "Ada", team: "platform", active: true })
72
+ users.put("user-2", { id: "user-2", name: "Grace", team: "platform", active: false })
73
+ })
74
+
75
+ const activePlatformUsers = users.query({
76
+ where: { team: "platform", active: true },
77
+ })
78
+
79
+ console.log(activePlatformUsers)
80
+ console.log(await db.health())
81
+
82
+ await db.checkpoint("after-initial-users")
83
+ await db.stop()
84
+ ```
85
+
86
+ The package is ESM-only. The `workspaceRoot` directory is created on demand;
87
+ the kernel stores its durable state below `workspaceRoot/.broccolidb/`.
88
+
89
+ ## How durability works
90
+
91
+ ```mermaid
92
+ flowchart LR
93
+ A[Table mutation] --> B[In-memory table and indexes]
94
+ B --> C[WAL frame]
95
+ C --> D[Micro-batched flush]
96
+ D --> E[.broccolidb/wal.log]
97
+ B --> F[Checkpoint]
98
+ F --> G[checkpoint.db]
99
+ F --> H[checkpoints/id.json]
100
+ E --> I[Startup replay]
101
+ G --> I
102
+ I --> B
103
+ ```
104
+
105
+ A mutation updates the in-memory table immediately and schedules a WAL frame.
106
+ Call `flush()`, use `transaction()`, call `checkpoint()`, or shut down with
107
+ `stop()` when the application needs the buffered frames written before it
108
+ continues. On the next `start()`, BroccoliDB loads the latest base checkpoint
109
+ and replays the remaining WAL frames.
110
+
111
+ For a stable restore point, use:
112
+
113
+ ```ts
114
+ const checkpoint = await db.checkpoint("before-import")
115
+ // ... perform work ...
116
+ await db.rollback(checkpoint.checkpointId)
117
+ ```
118
+
119
+ The checkpoint record contains a timestamp, frame index, record counts, and a
120
+ SHA-256 hash of the serialized table snapshot. See the [operations guide](docs/OPERATIONS.md)
121
+ for backup, corruption, and multi-process guidance.
122
+
123
+ ## Public surface
124
+
125
+ The package entry point re-exports the contracts and implementations needed by
126
+ an embedding application:
127
+
128
+ | Area | Primary exports | Source |
129
+ |---|---|---|
130
+ | Kernel | `BroccoliDatabaseKernel`, `broccolidb`, `DatabaseKernelOptions` | `src/broccolidb-kernel.ts` |
131
+ | Tables and contracts | `IDbTable`, `DbQueryOptions`, `DbPutOptions`, index/query/change types | `src/broccolidb.contracts.ts` |
132
+ | Queries | `BroccoliNaturalQueryParser`, `IFluentQueryBuilder` | `src/broccolidb-natural-query.ts` and contracts |
133
+ | Aggregation | `DbAggregateQuery`, `DbAggregateResult`, aggregate metrics | `src/broccolidb-aggregation.ts` and contracts |
134
+ | WAL | `BroccoliWriteAheadLog`, `WalIntegrityError`, `WalFrame` | `src/broccolidb-wal.ts` |
135
+ | CAS | `BroccoliCASStorageService`, `StorageIntegrityError` | `src/broccolidb-cas.ts` |
136
+ | Locking | `ReentrantAsyncMutex`, `DatabaseLockError`, `DeadlockTimeoutError` | `src/broccolidb-mutex.ts` |
137
+ | Prompt compression | `TokenCompressionService`, `tokenCompressionService` | `src/TokenCompressionService.ts` |
138
+
139
+ The detailed signatures and examples live in the [API reference](docs/API.md).
140
+
141
+ ## Storage layout
142
+
143
+ ```text
144
+ <workspaceRoot>/
145
+ └── .broccolidb/
146
+ ├── wal.log # append-only JSONL mutation journal
147
+ ├── wal.log.old # previous journal after checkpoint rotation
148
+ ├── checkpoint.db # latest atomic base snapshot
149
+ ├── checkpoints/<id>.json # named checkpoint history
150
+ └── cas/
151
+ ├── blobs/<00-ff>/<sha> # content-addressed payloads
152
+ └── corrupt/ # quarantined payloads and manifest.jsonl
153
+ ```
154
+
155
+ Treat this directory as application state. Back it up only while the kernel is
156
+ stopped or after an explicit `flush()`/`checkpoint()`. Do not edit WAL or
157
+ checkpoint files by hand; a malformed WAL frame raises `WalIntegrityError`.
158
+
159
+ ## Portability and boundaries
160
+
161
+ BroccoliDB intentionally has:
162
+
163
+ - zero production dependencies in `package.json`;
164
+ - no SQLite, `better-sqlite3`, Kysely, native modules, or Electron ABI coupling;
165
+ - no network service, daemon, or background process;
166
+ - ordinary JSON, JSONL, SHA-256, Brotli, and filesystem primitives;
167
+ - a Node.js `>=18` engine requirement.
168
+
169
+ BroccoliDB does not provide SQL parsing, migrations, a cross-process lock, a
170
+ replicated log, or provider-specific billing/token accounting. The prompt token
171
+ compressor uses a four-characters-per-token estimate as a budget signal, not as
172
+ an API provider billing measurement.
173
+
174
+ ## Documentation
175
+
176
+ The documentation follows a layered path: **Concepts → How it works →
177
+ Reference → Operations → Decisions**.
178
+
179
+ - [Documentation map](docs/README.md) — choose a reading path by role.
180
+ - [Brief](docs/BRIEF.md) — problem, solution, guarantees, and fit.
181
+ - [Architecture](docs/ARCHITECTURE.md) — layers, lifecycle, files, and failure model.
182
+ - [API reference](docs/API.md) — public types, methods, and query examples.
183
+ - [Operations guide](docs/OPERATIONS.md) — durability, backup, recovery, health, and GC.
184
+ - [Troubleshooting](docs/TROUBLESHOOTING.md) — symptom → cause → action runbooks.
185
+ - [Glossary](docs/GLOSSARY.md) — canonical vocabulary.
186
+ - [Design philosophy](docs/PHILOSOPHY.md) — principles and rejected alternatives.
187
+ - [Architecture decisions](docs/adr/README.md) — durable decisions and their consequences.
188
+ - [Contributing](docs/CONTRIBUTING.md) — source map, workflow, and contract checklist.
189
+ - [Release notes](docs/RELEASE_NOTES.md) — supported package history.
190
+
191
+ ## Development and verification
192
+
193
+ ```bash
194
+ npm install
195
+ npm run build
196
+ npm test
197
+ npm run docs:check
198
+ npm run check
199
+ npm pack --dry-run
200
+ ```
201
+
202
+ `npm test` builds the package and runs the TypeScript tests under `test/`.
203
+ `npm run docs:check` verifies the documentation map and required relative
204
+ links. `npm run check` is the release-oriented local gate.
205
+
206
+ ## Compatibility policy
207
+
208
+ The standalone package is the supported BroccoliDB implementation. The removed
209
+ in-repository SQLite implementation is not a compatibility target. New code
210
+ should import `@noorm/broccolidb` from the package entry point and should not
211
+ recreate a private table/WAL implementation inside an application.
212
+
213
+ Changes to exported types, on-disk formats, WAL replay, checkpoint structure, or
214
+ CAS integrity behavior require an API/operations documentation update and an
215
+ entry in the ADR or release notes.
216
+
217
+ ## License
218
+
219
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Minimal message shape accepted by the prompt compressor.
3
+ * The generic keeps provider-specific message metadata intact while the
4
+ * compressor only rewrites textual content.
5
+ */
6
+ export interface TokenCompressionMessage {
7
+ role?: string;
8
+ content?: unknown;
9
+ }
10
+ export interface TokenCompressionRequest<T extends TokenCompressionMessage = TokenCompressionMessage> {
11
+ systemPrompt?: string;
12
+ messages: readonly T[];
13
+ requestedModel?: string;
14
+ }
15
+ export interface TokenCompressionResult<T extends TokenCompressionMessage = TokenCompressionMessage> {
16
+ compactedSystemPrompt?: string;
17
+ compactedMessages: T[];
18
+ originalEstimatedTokens: number;
19
+ compactedEstimatedTokens: number;
20
+ tokensSaved: number;
21
+ compressionRatioPct: number;
22
+ promptSha256: string;
23
+ /** The requested model is echoed without routing or model substitution. */
24
+ recommendedModel: string;
25
+ cachedL1: boolean;
26
+ }
27
+ /**
28
+ * BroccoliDB implementation of GALXAI's deterministic prompt compression
29
+ * strategy. It is deliberately lossless at the semantic level: only line
30
+ * endings, repeated whitespace, and empty-line runs are normalized. Structured
31
+ * provider blocks remain structured so tool calls, images, and signatures are
32
+ * never serialized into plain text by the optimization pass.
33
+ */
34
+ export declare class TokenCompressionService {
35
+ private static instance;
36
+ private static readonly MAX_L1_ENTRIES;
37
+ private readonly l1Cache;
38
+ private constructor();
39
+ static getInstance(): TokenCompressionService;
40
+ /**
41
+ * Estimates tokens with the same fast four-characters-per-token heuristic as
42
+ * the source strategy. This is a budget signal, not provider billing data.
43
+ */
44
+ static estimateTokens(text: string): number;
45
+ /**
46
+ * Compacts a prompt at the request boundary and reuses exact L1 results by
47
+ * SHA-256 of the unmodified prompt payload.
48
+ */
49
+ compactPrompt<T extends TokenCompressionMessage>(input: TokenCompressionRequest<T>): TokenCompressionResult<T>;
50
+ /** Clears the process-local cache. Intended for lifecycle cleanup and tests. */
51
+ clear(): void;
52
+ private estimatePromptTokens;
53
+ private serializeContent;
54
+ private normalizeContent;
55
+ private normalizeText;
56
+ private copyMessages;
57
+ }
58
+ export declare const tokenCompressionService: TokenCompressionService;
59
+ //# sourceMappingURL=TokenCompressionService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokenCompressionService.d.ts","sourceRoot":"","sources":["../src/TokenCompressionService.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB,CAAC,CAAC,SAAS,uBAAuB,GAAG,uBAAuB;IAClG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,uBAAuB,GAAG,uBAAuB;IACjG,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,iBAAiB,EAAE,CAAC,EAAE,CAAC;IACvB,uBAAuB,EAAE,MAAM,CAAC;IAChC,wBAAwB,EAAE,MAAM,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAID;;;;;;GAMG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAsC;IAC7D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAS;IAE/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8C;IAEtE,OAAO;WAEO,WAAW,IAAI,uBAAuB;IAOpD;;;OAGG;WACW,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAKlD;;;OAGG;IACI,aAAa,CAAC,CAAC,SAAS,uBAAuB,EACpD,KAAK,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAChC,sBAAsB,CAAC,CAAC,CAAC;IAwD5B,gFAAgF;IACzE,KAAK,IAAI,IAAI;IAIpB,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,gBAAgB;IAoDxB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,YAAY;CAarB;AAED,eAAO,MAAM,uBAAuB,yBAAwC,CAAC"}
@@ -0,0 +1,179 @@
1
+ // [LAYER: CORE]
2
+ // @classification PURE
3
+ import { createHash } from 'node:crypto';
4
+ /**
5
+ * BroccoliDB implementation of GALXAI's deterministic prompt compression
6
+ * strategy. It is deliberately lossless at the semantic level: only line
7
+ * endings, repeated whitespace, and empty-line runs are normalized. Structured
8
+ * provider blocks remain structured so tool calls, images, and signatures are
9
+ * never serialized into plain text by the optimization pass.
10
+ */
11
+ export class TokenCompressionService {
12
+ static instance;
13
+ static MAX_L1_ENTRIES = 1_000;
14
+ l1Cache = new Map();
15
+ constructor() { }
16
+ static getInstance() {
17
+ if (!TokenCompressionService.instance) {
18
+ TokenCompressionService.instance = new TokenCompressionService();
19
+ }
20
+ return TokenCompressionService.instance;
21
+ }
22
+ /**
23
+ * Estimates tokens with the same fast four-characters-per-token heuristic as
24
+ * the source strategy. This is a budget signal, not provider billing data.
25
+ */
26
+ static estimateTokens(text) {
27
+ if (!text)
28
+ return 0;
29
+ return Math.max(1, Math.ceil(text.length / 4));
30
+ }
31
+ /**
32
+ * Compacts a prompt at the request boundary and reuses exact L1 results by
33
+ * SHA-256 of the unmodified prompt payload.
34
+ */
35
+ compactPrompt(input) {
36
+ const rawPayload = JSON.stringify({
37
+ systemPrompt: input.systemPrompt ?? null,
38
+ messages: input.messages,
39
+ });
40
+ const promptSha256 = createHash('sha256').update(rawPayload ?? '').digest('hex');
41
+ const cached = this.l1Cache.get(promptSha256);
42
+ if (cached) {
43
+ return {
44
+ ...cached,
45
+ compactedMessages: this.copyMessages(cached.compactedMessages),
46
+ recommendedModel: input.requestedModel ?? 'unknown',
47
+ cachedL1: true,
48
+ };
49
+ }
50
+ const compactedSystemPrompt = input.systemPrompt === undefined
51
+ ? undefined
52
+ : this.normalizeText(input.systemPrompt, 'system');
53
+ const compactedMessages = input.messages.map((message) => ({
54
+ ...message,
55
+ content: this.normalizeContent(message.content, message.role),
56
+ }));
57
+ const originalEstimatedTokens = this.estimatePromptTokens(input.systemPrompt, input.messages);
58
+ const compactedEstimatedTokens = this.estimatePromptTokens(compactedSystemPrompt, compactedMessages);
59
+ const tokensSaved = Math.max(0, originalEstimatedTokens - compactedEstimatedTokens);
60
+ const compressionRatioPct = originalEstimatedTokens > 0
61
+ ? Number(((tokensSaved / originalEstimatedTokens) * 100).toFixed(2))
62
+ : 0;
63
+ const result = {
64
+ compactedSystemPrompt,
65
+ compactedMessages,
66
+ originalEstimatedTokens,
67
+ compactedEstimatedTokens,
68
+ tokensSaved,
69
+ compressionRatioPct,
70
+ promptSha256,
71
+ recommendedModel: input.requestedModel ?? 'unknown',
72
+ cachedL1: false,
73
+ };
74
+ this.l1Cache.set(promptSha256, {
75
+ ...result,
76
+ compactedMessages: this.copyMessages(compactedMessages),
77
+ });
78
+ if (this.l1Cache.size > TokenCompressionService.MAX_L1_ENTRIES) {
79
+ const firstKey = this.l1Cache.keys().next().value;
80
+ if (firstKey)
81
+ this.l1Cache.delete(firstKey);
82
+ }
83
+ return result;
84
+ }
85
+ /** Clears the process-local cache. Intended for lifecycle cleanup and tests. */
86
+ clear() {
87
+ this.l1Cache.clear();
88
+ }
89
+ estimatePromptTokens(systemPrompt, messages) {
90
+ let total = TokenCompressionService.estimateTokens(systemPrompt ?? '');
91
+ for (const message of messages) {
92
+ total += TokenCompressionService.estimateTokens(this.serializeContent(message.content));
93
+ }
94
+ return total;
95
+ }
96
+ serializeContent(content) {
97
+ if (typeof content === 'string')
98
+ return content;
99
+ if (content === undefined || content === null)
100
+ return '';
101
+ try {
102
+ return JSON.stringify(content) ?? '';
103
+ }
104
+ catch {
105
+ return String(content);
106
+ }
107
+ }
108
+ normalizeContent(content, role) {
109
+ if (typeof content === 'string') {
110
+ return this.normalizeText(content, role);
111
+ }
112
+ if (!Array.isArray(content))
113
+ return content;
114
+ let changed = false;
115
+ const normalized = content.map((block) => {
116
+ if (!block || typeof block !== 'object')
117
+ return block;
118
+ const record = block;
119
+ if (record.type === 'text' && typeof record.text === 'string') {
120
+ const text = this.normalizeText(record.text, role);
121
+ if (text !== record.text)
122
+ changed = true;
123
+ return text === record.text ? block : { ...record, text };
124
+ }
125
+ if (record.type === 'thinking' && typeof record.thinking === 'string') {
126
+ // Provider thought signatures cover the reasoning payload. Rewriting a
127
+ // signed block would leave the signature attached to different bytes
128
+ // and can make the next request invalid. Unsigned blocks are safe to
129
+ // normalize because they do not carry that provider contract.
130
+ if (record.signature !== undefined)
131
+ return block;
132
+ const thinking = this.normalizeText(record.thinking, role);
133
+ if (thinking !== record.thinking)
134
+ changed = true;
135
+ return thinking === record.thinking ? block : { ...record, thinking };
136
+ }
137
+ if (record.type === 'tool_result') {
138
+ if (typeof record.content === 'string') {
139
+ const text = this.normalizeText(record.content, role);
140
+ if (text !== record.content)
141
+ changed = true;
142
+ return text === record.content ? block : { ...record, content: text };
143
+ }
144
+ if (Array.isArray(record.content)) {
145
+ const nested = this.normalizeContent(record.content, role);
146
+ if (nested !== record.content) {
147
+ changed = true;
148
+ return { ...record, content: nested };
149
+ }
150
+ }
151
+ }
152
+ return block;
153
+ });
154
+ return changed ? normalized : content;
155
+ }
156
+ normalizeText(text, role) {
157
+ const normalized = text.replace(/\r\n/g, '\n');
158
+ if (role === 'system') {
159
+ return normalized.replace(/[ \t]+/g, ' ').trim();
160
+ }
161
+ return normalized
162
+ .replace(/\n{3,}/g, '\n\n')
163
+ .replace(/[ \t]{2,}/g, ' ')
164
+ .trim();
165
+ }
166
+ copyMessages(messages) {
167
+ return messages.map((message) => {
168
+ const content = message.content;
169
+ if (!Array.isArray(content))
170
+ return { ...message };
171
+ return {
172
+ ...message,
173
+ content: content.map((block) => (block && typeof block === 'object' ? { ...block } : block)),
174
+ };
175
+ });
176
+ }
177
+ }
178
+ export const tokenCompressionService = TokenCompressionService.getInstance();
179
+ //# sourceMappingURL=TokenCompressionService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokenCompressionService.js","sourceRoot":"","sources":["../src/TokenCompressionService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * GALXAI: BroccoliDB Statistical Aggregation & Group-By Engine (Zenith Tier)
3
+ * Single-pass streaming grouping, statistical accumulators (SUM, AVG, MIN, MAX, COUNT, STDDEV),
4
+ * and HAVING predicate filters over BroccoliDbTable records.
5
+ */
6
+ import type { DbAggregateQuery, DbAggregateResult } from "./broccolidb.contracts.js";
7
+ export declare class BroccoliAggregateEngine {
8
+ /**
9
+ * Executes an aggregation query across candidate records.
10
+ */
11
+ static execute<T extends Record<string, unknown>>(tableName: string, records: readonly T[], query: DbAggregateQuery): DbAggregateResult;
12
+ private static finalizeMetric;
13
+ }
14
+ //# sourceMappingURL=broccolidb-aggregation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broccolidb-aggregation.d.ts","sourceRoot":"","sources":["../src/broccolidb-aggregation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAEV,gBAAgB,EAChB,iBAAiB,EAElB,MAAM,2BAA2B,CAAC;AAkBnC,qBAAa,uBAAuB;IAClC;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9C,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,SAAS,CAAC,EAAE,EACrB,KAAK,EAAE,gBAAgB,GACtB,iBAAiB;IA6IpB,OAAO,CAAC,MAAM,CAAC,cAAc;CA6B9B"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * GALXAI: BroccoliDB Statistical Aggregation & Group-By Engine (Zenith Tier)
3
+ * Single-pass streaming grouping, statistical accumulators (SUM, AVG, MIN, MAX, COUNT, STDDEV),
4
+ * and HAVING predicate filters over BroccoliDbTable records.
5
+ */
6
+ export class BroccoliAggregateEngine {
7
+ /**
8
+ * Executes an aggregation query across candidate records.
9
+ */
10
+ static execute(tableName, records, query) {
11
+ const startTime = performance.now();
12
+ const groupMap = new Map();
13
+ const grandTotalsAccumulator = {};
14
+ for (const [metricKey, metricDef] of Object.entries(query.metrics)) {
15
+ grandTotalsAccumulator[metricKey] = {
16
+ metric: metricDef.metric,
17
+ field: metricDef.field,
18
+ sum: 0,
19
+ min: Number.POSITIVE_INFINITY,
20
+ max: Number.NEGATIVE_INFINITY,
21
+ values: [],
22
+ };
23
+ }
24
+ for (const record of records) {
25
+ const groupKeyObj = {};
26
+ let compositeKey = "";
27
+ if (query.groupBy && query.groupBy.length > 0) {
28
+ for (const f of query.groupBy) {
29
+ const v = record[f];
30
+ groupKeyObj[f] = v;
31
+ compositeKey += `${f}:${JSON.stringify(v)}|`;
32
+ }
33
+ }
34
+ else {
35
+ compositeKey = "__root__";
36
+ }
37
+ let group = groupMap.get(compositeKey);
38
+ if (!group) {
39
+ group = {
40
+ keys: groupKeyObj,
41
+ count: 0,
42
+ metricData: {},
43
+ };
44
+ for (const [metricKey, metricDef] of Object.entries(query.metrics)) {
45
+ group.metricData[metricKey] = {
46
+ metric: metricDef.metric,
47
+ field: metricDef.field,
48
+ sum: 0,
49
+ min: Number.POSITIVE_INFINITY,
50
+ max: Number.NEGATIVE_INFINITY,
51
+ values: [],
52
+ };
53
+ }
54
+ groupMap.set(compositeKey, group);
55
+ }
56
+ group.count++;
57
+ for (const [metricKey, metricDef] of Object.entries(query.metrics)) {
58
+ const acc = group.metricData[metricKey];
59
+ const grandAcc = grandTotalsAccumulator[metricKey];
60
+ const val = metricDef.field ? Number(record[metricDef.field]) : 1;
61
+ const isValidNum = typeof val === "number" && !Number.isNaN(val);
62
+ if (isValidNum) {
63
+ acc.sum += val;
64
+ grandAcc.sum += val;
65
+ if (val < acc.min)
66
+ acc.min = val;
67
+ if (val > acc.max)
68
+ acc.max = val;
69
+ if (val < grandAcc.min)
70
+ grandAcc.min = val;
71
+ if (val > grandAcc.max)
72
+ grandAcc.max = val;
73
+ if (metricDef.metric === "stddev") {
74
+ acc.values.push(val);
75
+ grandAcc.values.push(val);
76
+ }
77
+ }
78
+ }
79
+ }
80
+ let groups = [];
81
+ for (const group of groupMap.values()) {
82
+ const finalizedMetrics = {};
83
+ for (const [metricKey, acc] of Object.entries(group.metricData)) {
84
+ finalizedMetrics[metricKey] = this.finalizeMetric(acc.metric, acc.sum, acc.min, acc.max, acc.values, group.count);
85
+ }
86
+ groups.push({
87
+ keys: group.keys,
88
+ metrics: finalizedMetrics,
89
+ recordCount: group.count,
90
+ });
91
+ }
92
+ if (query.having) {
93
+ groups = groups.filter((g) => {
94
+ for (const [k, expected] of Object.entries(query.having)) {
95
+ const val = k === "count" || k === "_count" ? g.recordCount : g.metrics[k];
96
+ if (expected !== null && typeof expected === "object") {
97
+ const expObj = expected;
98
+ if (expObj.$gt !== undefined && (val === undefined || val <= expObj.$gt))
99
+ return false;
100
+ if (expObj.$gte !== undefined && (val === undefined || val < expObj.$gte))
101
+ return false;
102
+ if (expObj.$lt !== undefined && (val === undefined || val >= expObj.$lt))
103
+ return false;
104
+ if (expObj.$lte !== undefined && (val === undefined || val > expObj.$lte))
105
+ return false;
106
+ if (expObj.$eq !== undefined && val !== expObj.$eq)
107
+ return false;
108
+ if (expObj.$ne !== undefined && val === expObj.$ne)
109
+ return false;
110
+ }
111
+ else if (val !== expected) {
112
+ return false;
113
+ }
114
+ }
115
+ return true;
116
+ });
117
+ }
118
+ if (query.limit !== undefined && query.limit > 0) {
119
+ groups = groups.slice(0, query.limit);
120
+ }
121
+ const grandTotals = {};
122
+ for (const [metricKey, acc] of Object.entries(grandTotalsAccumulator)) {
123
+ grandTotals[metricKey] = this.finalizeMetric(acc.metric, acc.sum, acc.min, acc.max, acc.values, records.length);
124
+ }
125
+ const executionTimeMicros = Math.round((performance.now() - startTime) * 1000);
126
+ return {
127
+ table: tableName,
128
+ totalRecordsEvaluated: records.length,
129
+ groups,
130
+ grandTotals,
131
+ executionTimeMicros,
132
+ };
133
+ }
134
+ static finalizeMetric(metric, sum, min, max, values, count) {
135
+ switch (metric) {
136
+ case "count":
137
+ return count;
138
+ case "sum":
139
+ return sum;
140
+ case "avg":
141
+ return count > 0 ? Math.round((sum / count) * 10000) / 10000 : 0;
142
+ case "min":
143
+ return min !== Number.POSITIVE_INFINITY ? min : 0;
144
+ case "max":
145
+ return max !== Number.NEGATIVE_INFINITY ? max : 0;
146
+ case "stddev": {
147
+ if (values.length <= 1)
148
+ return 0;
149
+ const mean = sum / values.length;
150
+ const variance = values.reduce((acc, v) => acc + Math.pow(v - mean, 2), 0) / values.length;
151
+ return Math.round(Math.sqrt(variance) * 10000) / 10000;
152
+ }
153
+ default:
154
+ return sum;
155
+ }
156
+ }
157
+ }
158
+ //# sourceMappingURL=broccolidb-aggregation.js.map