@feltdb/core 0.6.9 → 0.6.11

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 (122) hide show
  1. package/dist/cli/application-lifecycle.js +112 -0
  2. package/dist/cli/application.js +10 -0
  3. package/dist/cli/commands.js +407 -119
  4. package/dist/cli/development-session.js +54 -0
  5. package/dist/cli/index.js +1 -1
  6. package/dist/cli/workspace-integration.js +146 -20
  7. package/dist/collection.d.ts +110 -2
  8. package/dist/collection.d.ts.map +1 -1
  9. package/dist/collection.js +228 -3
  10. package/dist/create/cli.js +4 -2
  11. package/dist/create/create.js +42 -6
  12. package/dist/create/frameworks.js +18 -0
  13. package/dist/create/package-versions.js +1 -1
  14. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +408 -178
  15. package/dist/create/server-source/crates/feltdb/src/lib.rs +49 -5
  16. package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +659 -0
  17. package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +285 -0
  18. package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +582 -0
  19. package/dist/create/server-source/crates/feltdb-server/src/main.rs +141 -0
  20. package/dist/create/workspace-initialization.js +4 -5
  21. package/dist/db.d.ts +63 -1
  22. package/dist/db.d.ts.map +1 -1
  23. package/dist/db.js +145 -4
  24. package/dist/development-runtime-bridge.d.ts +60 -0
  25. package/dist/development-runtime-bridge.d.ts.map +1 -0
  26. package/dist/development-runtime-bridge.js +171 -0
  27. package/dist/embedded-transaction.d.ts +77 -0
  28. package/dist/embedded-transaction.d.ts.map +1 -0
  29. package/dist/embedded-transaction.js +102 -0
  30. package/dist/feltdb.d.ts +49 -1
  31. package/dist/feltdb.d.ts.map +1 -1
  32. package/dist/file-db.d.ts +33 -0
  33. package/dist/file-db.d.ts.map +1 -1
  34. package/dist/file-db.js +128 -4
  35. package/dist/freshness.d.ts +119 -0
  36. package/dist/freshness.d.ts.map +1 -0
  37. package/dist/freshness.js +115 -0
  38. package/dist/http-db.d.ts +48 -0
  39. package/dist/http-db.d.ts.map +1 -1
  40. package/dist/http-db.js +77 -1
  41. package/dist/index-backend.d.ts +8 -0
  42. package/dist/index-backend.d.ts.map +1 -1
  43. package/dist/index-backend.js +13 -0
  44. package/dist/index-core.d.ts +80 -0
  45. package/dist/index-core.d.ts.map +1 -0
  46. package/dist/index-core.js +74 -0
  47. package/dist/index-manager.d.ts +11 -0
  48. package/dist/index-manager.d.ts.map +1 -1
  49. package/dist/index-manager.js +30 -13
  50. package/dist/index-types.d.ts +17 -0
  51. package/dist/index-types.d.ts.map +1 -1
  52. package/dist/index.browser.d.ts +14 -0
  53. package/dist/index.browser.d.ts.map +1 -0
  54. package/dist/index.browser.js +13 -0
  55. package/dist/index.d.ts +8 -59
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +11 -54
  58. package/dist/indexeddb-db.d.ts +32 -0
  59. package/dist/indexeddb-db.d.ts.map +1 -1
  60. package/dist/indexeddb-db.js +151 -1
  61. package/dist/memory-db.d.ts +26 -0
  62. package/dist/memory-db.d.ts.map +1 -1
  63. package/dist/memory-db.js +86 -0
  64. package/dist/runtime-registry.d.ts +32 -0
  65. package/dist/runtime-registry.d.ts.map +1 -0
  66. package/dist/runtime-registry.js +38 -0
  67. package/dist/studio/app.d.ts +2 -1
  68. package/dist/studio/app.d.ts.map +1 -1
  69. package/dist/studio/components/StateExplorer.d.ts +2 -1
  70. package/dist/studio/components/StateExplorer.d.ts.map +1 -1
  71. package/dist/studio/components/index.js +1 -1
  72. package/dist/studio/{components-9kDSWiGL.js → components-C5p2TfIU.js} +25 -14
  73. package/dist/studio/index.js +234 -160
  74. package/dist/studio-app/assets/{feltdb_wasm-B4wq4mqp.js → feltdb_wasm-CJv3wHzi.js} +1 -1
  75. package/dist/studio-app/assets/feltdb_wasm_bg-C8TG8r2n.wasm +0 -0
  76. package/dist/studio-app/assets/index-DospFFYE.js +28 -0
  77. package/dist/studio-app/index.html +1 -1
  78. package/dist/transaction.d.ts +98 -0
  79. package/dist/transaction.d.ts.map +1 -0
  80. package/dist/transaction.js +86 -0
  81. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  82. package/dist/workspace/browser.d.ts +22 -0
  83. package/dist/workspace/browser.d.ts.map +1 -0
  84. package/dist/workspace/browser.js +17 -0
  85. package/dist/workspace/development-node.d.ts +16 -0
  86. package/dist/workspace/development-node.d.ts.map +1 -1
  87. package/dist/workspace/development-node.js +85 -1
  88. package/dist/workspace/git-identity.d.ts +35 -0
  89. package/dist/workspace/git-identity.d.ts.map +1 -0
  90. package/dist/workspace/git-identity.js +81 -0
  91. package/dist/workspace/index.d.ts +12 -2
  92. package/dist/workspace/index.d.ts.map +1 -1
  93. package/dist/workspace/index.js +7 -1
  94. package/dist/workspace/investigation-analysis.d.ts +47 -0
  95. package/dist/workspace/investigation-analysis.d.ts.map +1 -0
  96. package/dist/workspace/investigation-analysis.js +181 -0
  97. package/dist/workspace/investigation-lifecycle-manager.d.ts +88 -0
  98. package/dist/workspace/investigation-lifecycle-manager.d.ts.map +1 -0
  99. package/dist/workspace/investigation-lifecycle-manager.js +259 -0
  100. package/dist/workspace/investigation-supervisor.d.ts +100 -0
  101. package/dist/workspace/investigation-supervisor.d.ts.map +1 -0
  102. package/dist/workspace/investigation-supervisor.js +238 -0
  103. package/dist/workspace/runtime-investigation.d.ts +5 -1
  104. package/dist/workspace/runtime-investigation.d.ts.map +1 -1
  105. package/dist/workspace/runtime-investigation.js +50 -21
  106. package/dist/workspace/runtime-observation.d.ts +82 -0
  107. package/dist/workspace/runtime-observation.d.ts.map +1 -0
  108. package/dist/workspace/runtime-observation.js +170 -0
  109. package/dist/workspace/runtime-observer.d.ts +57 -0
  110. package/dist/workspace/runtime-observer.d.ts.map +1 -0
  111. package/dist/workspace/runtime-observer.js +272 -0
  112. package/dist/workspace/workspace-connection.d.ts.map +1 -1
  113. package/dist/workspace/workspace-connection.js +14 -5
  114. package/dist/workspace/workspace-identity.d.ts +1 -1
  115. package/dist/workspace/workspace-identity.d.ts.map +1 -1
  116. package/dist/workspace/workspace-identity.js +4 -4
  117. package/dist/workspace/workspace-types.d.ts +208 -0
  118. package/dist/workspace/workspace-types.d.ts.map +1 -1
  119. package/package.json +12 -4
  120. package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +0 -523
  121. package/dist/studio-app/assets/feltdb_wasm_bg-Ceyi7l21.wasm +0 -0
  122. package/dist/studio-app/assets/index-BF45M757.js +0 -28
@@ -0,0 +1,54 @@
1
+ const TRANSITIONS = {
2
+ CREATING: ['STARTING', 'FAILED'],
3
+ STARTING: ['READY', 'STOPPING', 'FAILED'],
4
+ READY: ['RUNNING', 'STOPPING', 'FAILED'],
5
+ RUNNING: ['STOPPING', 'FAILED'],
6
+ STOPPING: ['STOPPED'],
7
+ STOPPED: [],
8
+ FAILED: ['STOPPING', 'STOPPED'],
9
+ };
10
+ export function createDevelopmentSession(input) {
11
+ for (const key of ['sessionId', 'workspaceId', 'projectId', 'namespace', 'runtime', 'storage', 'authorityUrl', 'studioUrl', 'pairingUrl']) {
12
+ if (!input[key])
13
+ throw new Error(`Cannot create development session without ${key}`);
14
+ }
15
+ return { ...input, state: 'CREATING' };
16
+ }
17
+ export function transitionDevelopmentSession(session, state) {
18
+ if (session.state === state)
19
+ return;
20
+ if (!TRANSITIONS[session.state].includes(state))
21
+ throw new Error(`Invalid development session transition ${session.state} -> ${state}`);
22
+ session.state = state;
23
+ }
24
+ export function developmentSessionEnvironment(session) {
25
+ return {
26
+ FELTDB_WORKSPACE_ID: session.workspaceId,
27
+ FELTDB_DEV_SESSION_ID: session.sessionId,
28
+ FELTDB_DEV_BRIDGE_URL: session.pairingUrl,
29
+ FELTDB_RUNTIME: session.runtime,
30
+ FELTDB_AUTHORITY_URL: session.authorityUrl,
31
+ FELTDB_APPLICATION_URL: session.applicationUrl,
32
+ FELTDB_WORKSPACE_ENDPOINT: session.authorityUrl,
33
+ FELTDB_NAMESPACE: session.namespace,
34
+ VITE_FELTDB_WORKSPACE_ID: session.workspaceId,
35
+ VITE_FELTDB_DEV_SESSION_ID: session.sessionId,
36
+ VITE_FELTDB_DEV_BRIDGE_URL: session.pairingUrl,
37
+ VITE_FELTDB_RUNTIME: session.runtime,
38
+ VITE_FELTDB_AUTHORITY_URL: session.authorityUrl,
39
+ VITE_FELTDB_APPLICATION_URL: session.applicationUrl,
40
+ VITE_FELTDB_WORKSPACE_ENDPOINT: session.authorityUrl,
41
+ VITE_FELTDB_NAMESPACE: session.namespace,
42
+ };
43
+ }
44
+ export function developmentSessionSummary(session) {
45
+ const rows = [
46
+ ['Session', session.sessionId], ['Workspace', session.workspaceId], ['Project', session.projectId], ['Namespace', session.namespace],
47
+ ['Runtime', session.runtime], ['Storage', session.storage], ['Lifecycle', session.lifecycle],
48
+ ['Application', session.applicationUrl], ['Authority', session.authorityUrl],
49
+ ['Studio', session.studioUrl], ['Pairing', session.pairingUrl],
50
+ ].filter((row) => Boolean(row[1]));
51
+ return ['FeltDB Development Session', ...rows.map(([label, value]) => `${label.padEnd(12)} ${value}`),
52
+ '✓ Authority', '✓ Pairing', '✓ Studio', ...(session.applicationUrl ? ['✓ Application'] : []),
53
+ 'FeltDB development environment ready.'].join('\n');
54
+ }
package/dist/cli/index.js CHANGED
@@ -23,7 +23,7 @@ import * as path from 'path';
23
23
  import * as readline from 'readline';
24
24
  import { getClient } from './api-client.js';
25
25
  import { loadFeltDBConfig, createDefaultConfig, validateModel, } from './config.js';
26
- const VERSION = '0.6.9';
26
+ const VERSION = '0.6.11';
27
27
  function prompt(question) {
28
28
  const rl = readline.createInterface({
29
29
  input: process.stdin,
@@ -12,6 +12,7 @@ import path from 'path';
12
12
  import { randomBytes } from 'crypto';
13
13
  import http from 'http';
14
14
  import { spawnSync } from 'child_process';
15
+ export const DEVELOPMENT_OBSERVATION_HISTORY_LIMIT = 1000;
15
16
  const FELTDB_IGNORE_PATTERN = /^\/?\.feltdb\/?$/;
16
17
  /**
17
18
  * Reserve .feltdb/ for local FeltDB runtime state before any state is written.
@@ -42,15 +43,26 @@ export function ensureWorkspaceGitIgnored(projectDir) {
42
43
  }
43
44
  return { added, tracked };
44
45
  }
45
- export function startPairingDiscoveryServer(token, port = 7799, host = '127.0.0.1') {
46
+ export function startPairingDiscoveryServer(token, port = 7799, host = '127.0.0.1', runtimeSession, runtimeTtlMs = 5000) {
46
47
  const authorityEndpoint = token.authorityEndpoint || token.endpoint;
47
48
  if (!token.workspaceId || !authorityEndpoint || !token.namespace) {
48
49
  throw new Error('Pairing discovery requires workspaceId, endpoint, and namespace');
49
50
  }
50
51
  const route = `/api/v1/development/pairing/${encodeURIComponent(token.token)}`;
51
- const server = http.createServer((request, response) => {
52
+ const registrations = new Map();
53
+ const observationHistory = new Map();
54
+ const droppedObservations = new Map();
55
+ let observationCursor = 0;
56
+ const activeRegistrations = () => {
57
+ const now = Date.now();
58
+ for (const [id, registration] of registrations)
59
+ if (registration.expiresAt <= now)
60
+ registrations.delete(id);
61
+ return [...registrations.values()];
62
+ };
63
+ const server = http.createServer(async (request, response) => {
52
64
  response.setHeader('Access-Control-Allow-Origin', '*');
53
- response.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
65
+ response.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
54
66
  response.setHeader('Access-Control-Allow-Headers', 'Accept, Content-Type');
55
67
  response.setHeader('Cache-Control', 'no-store');
56
68
  if (request.method === 'OPTIONS') {
@@ -58,26 +70,142 @@ export function startPairingDiscoveryServer(token, port = 7799, host = '127.0.0.
58
70
  response.end();
59
71
  return;
60
72
  }
61
- const pathname = new URL(request.url || '/', `http://${host}`).pathname;
62
- if (request.method !== 'GET' || pathname !== route || Date.now() >= token.expiresAt) {
63
- response.statusCode = 404;
73
+ try {
74
+ const pathname = new URL(request.url || '/', `http://${host}`).pathname;
75
+ if (runtimeSession && request.method === 'POST' && pathname === '/api/v1/development/runtime/register') {
76
+ const body = JSON.parse(await readRequestBody(request) || '{}');
77
+ if (body.sessionId !== runtimeSession.sessionId || body.workspaceId !== runtimeSession.workspaceId) {
78
+ runtimeJson(response, 403, { error: 'RUNTIME_SESSION_MISMATCH' });
79
+ return;
80
+ }
81
+ if (body.namespace !== runtimeSession.namespace || body.runtime !== runtimeSession.runtime || body.authorityUrl !== runtimeSession.authorityUrl) {
82
+ runtimeJson(response, 409, { error: 'RUNTIME_IDENTITY_MISMATCH' });
83
+ return;
84
+ }
85
+ if (!body.runtimeInstanceId || !body.applicationId) {
86
+ runtimeJson(response, 400, { error: 'RUNTIME_IDENTITY_REQUIRED' });
87
+ return;
88
+ }
89
+ const now = Date.now();
90
+ const previous = registrations.get(body.runtimeInstanceId);
91
+ const registration = {
92
+ runtimeInstanceId: body.runtimeInstanceId,
93
+ applicationId: body.applicationId,
94
+ workspaceId: runtimeSession.workspaceId,
95
+ sessionId: runtimeSession.sessionId,
96
+ namespace: runtimeSession.namespace,
97
+ runtime: runtimeSession.runtime,
98
+ authorityUrl: runtimeSession.authorityUrl,
99
+ applicationUrl: body.applicationUrl || runtimeSession.applicationUrl,
100
+ registeredAt: previous?.registeredAt ?? now,
101
+ lastSeenAt: now,
102
+ expiresAt: now + runtimeTtlMs,
103
+ };
104
+ for (const [id, active] of registrations) {
105
+ if (id !== registration.runtimeInstanceId && active.applicationId === registration.applicationId)
106
+ registrations.delete(id);
107
+ }
108
+ registrations.set(registration.runtimeInstanceId, registration);
109
+ runtimeJson(response, previous ? 200 : 201, registration);
110
+ return;
111
+ }
112
+ if (runtimeSession && request.method === 'GET' && pathname === '/api/v1/development/runtimes') {
113
+ const sessionId = new URL(request.url || '/', `http://${host}`).searchParams.get('sessionId');
114
+ if (sessionId !== runtimeSession.sessionId) {
115
+ runtimeJson(response, 403, { error: 'RUNTIME_SESSION_MISMATCH' });
116
+ return;
117
+ }
118
+ runtimeJson(response, 200, { sessionId, workspaceId: runtimeSession.workspaceId, runtimes: activeRegistrations() });
119
+ return;
120
+ }
121
+ const observationMatch = pathname.match(/^\/api\/v1\/development\/runtime\/([^/]+)\/observations$/);
122
+ if (runtimeSession && observationMatch) {
123
+ const runtimeInstanceId = decodeURIComponent(observationMatch[1]);
124
+ const url = new URL(request.url || '/', `http://${host}`);
125
+ const sessionId = request.method === 'POST'
126
+ ? undefined
127
+ : url.searchParams.get('sessionId');
128
+ if (request.method === 'POST') {
129
+ const body = JSON.parse(await readRequestBody(request) || '{}');
130
+ if (body.sessionId !== runtimeSession.sessionId) {
131
+ runtimeJson(response, 403, { error: 'RUNTIME_SESSION_MISMATCH' });
132
+ return;
133
+ }
134
+ if (!registrations.has(runtimeInstanceId)) {
135
+ runtimeJson(response, 404, { error: 'RUNTIME_NOT_REGISTERED' });
136
+ return;
137
+ }
138
+ const valid = (body.observations || []).slice(0, 64).filter((event) => event && event.runtimeInstanceId === runtimeInstanceId && typeof event.eventId === 'string'
139
+ && typeof event.correlationId === 'string' && ['started', 'completed', 'failed'].includes(event.status));
140
+ const history = observationHistory.get(runtimeInstanceId) || [];
141
+ history.push(...valid.map(event => ({ ...event, cursor: ++observationCursor })));
142
+ if (history.length > DEVELOPMENT_OBSERVATION_HISTORY_LIMIT)
143
+ history.splice(0, history.length - DEVELOPMENT_OBSERVATION_HISTORY_LIMIT);
144
+ observationHistory.set(runtimeInstanceId, history);
145
+ droppedObservations.set(runtimeInstanceId, (droppedObservations.get(runtimeInstanceId) || 0) + Math.max(0, Number(body.dropped) || 0));
146
+ runtimeJson(response, 202, { accepted: valid.length });
147
+ return;
148
+ }
149
+ if (request.method === 'GET') {
150
+ if (sessionId !== runtimeSession.sessionId) {
151
+ runtimeJson(response, 403, { error: 'RUNTIME_SESSION_MISMATCH' });
152
+ return;
153
+ }
154
+ const cursor = Math.max(0, Number(url.searchParams.get('cursor')) || 0);
155
+ const limit = Math.min(200, Math.max(1, Number(url.searchParams.get('limit')) || 100));
156
+ const observations = (observationHistory.get(runtimeInstanceId) || []).filter(event => event.cursor > cursor).slice(0, limit);
157
+ runtimeJson(response, 200, { runtimeInstanceId, observations, nextCursor: observations[observations.length - 1]?.cursor ?? cursor, dropped: droppedObservations.get(runtimeInstanceId) || 0 });
158
+ return;
159
+ }
160
+ }
161
+ if (runtimeSession && request.method === 'DELETE' && pathname.startsWith('/api/v1/development/runtime/')) {
162
+ const sessionId = new URL(request.url || '/', `http://${host}`).searchParams.get('sessionId');
163
+ if (sessionId !== runtimeSession.sessionId) {
164
+ runtimeJson(response, 403, { error: 'RUNTIME_SESSION_MISMATCH' });
165
+ return;
166
+ }
167
+ registrations.delete(decodeURIComponent(pathname.slice('/api/v1/development/runtime/'.length)));
168
+ response.statusCode = 204;
169
+ response.end();
170
+ return;
171
+ }
172
+ if (request.method !== 'GET' || pathname !== route || Date.now() >= token.expiresAt) {
173
+ response.statusCode = 404;
174
+ response.setHeader('Content-Type', 'application/json; charset=utf-8');
175
+ response.end(JSON.stringify({ error: 'PAIRING_CODE_NOT_FOUND' }));
176
+ return;
177
+ }
64
178
  response.setHeader('Content-Type', 'application/json; charset=utf-8');
65
- response.end(JSON.stringify({ error: 'PAIRING_CODE_NOT_FOUND' }));
66
- return;
179
+ response.end(JSON.stringify({
180
+ workspaceId: token.workspaceId,
181
+ endpoint: authorityEndpoint,
182
+ expiresAt: token.expiresAt,
183
+ namespace: token.namespace,
184
+ }));
185
+ }
186
+ catch (error) {
187
+ runtimeJson(response, 400, { error: error instanceof Error ? error.message : String(error) });
67
188
  }
68
- response.setHeader('Content-Type', 'application/json; charset=utf-8');
69
- response.end(JSON.stringify({
70
- workspaceId: token.workspaceId,
71
- endpoint: authorityEndpoint,
72
- expiresAt: token.expiresAt,
73
- namespace: token.namespace,
74
- }));
75
189
  });
76
190
  return new Promise((resolve, reject) => {
77
191
  server.once('error', reject);
78
192
  server.listen(port, host, () => resolve(server));
79
193
  });
80
194
  }
195
+ function readRequestBody(request) {
196
+ return new Promise((resolve, reject) => {
197
+ let body = '';
198
+ request.setEncoding('utf8');
199
+ request.on('data', chunk => { body += chunk; });
200
+ request.once('end', () => resolve(body));
201
+ request.once('error', reject);
202
+ });
203
+ }
204
+ function runtimeJson(response, status, value) {
205
+ response.statusCode = status;
206
+ response.setHeader('Content-Type', 'application/json; charset=utf-8');
207
+ response.end(JSON.stringify(value));
208
+ }
81
209
  /**
82
210
  * Discover workspace identity from .feltdb/workspace.json
83
211
  * Returns null if workspace is not initialized
@@ -143,12 +271,10 @@ export function readPairingToken(projectDir) {
143
271
  }
144
272
  /**
145
273
  * Generate a unique workspace ID
146
- * Format: ws_<projectId>_<timestamp>_<random>
274
+ * Opaque format: ws_<random>. Project identity is stored separately.
147
275
  */
148
- export function generateWorkspaceId(projectId) {
149
- const timestamp = Date.now();
150
- const random = Math.random().toString(36).substring(2, 9);
151
- return `ws_${projectId}_${timestamp}_${random}`;
276
+ export function generateWorkspaceId(_projectId) {
277
+ return `ws_${randomBytes(16).toString('hex')}`;
152
278
  }
153
279
  /**
154
280
  * Initialize development workspace for a project
@@ -8,9 +8,27 @@
8
8
  * for efficient, real-time state propagation.
9
9
  */
10
10
  import type { JsDb } from './feltdb.js';
11
- import type { IndexConfig } from './index-types.js';
11
+ import { type FreshnessCapability, type Revision } from './freshness.js';
12
+ import type { IndexConfig, CollectionQueryPlan } from './index-types.js';
12
13
  export type Predicate<T> = (item: T) => boolean;
13
14
  export type Subscriber<T> = (items: T[]) => void;
15
+ /**
16
+ * How a refresh decided whether the local cache could be reused.
17
+ *
18
+ * Reporting only, and deliberately explicit about *why* the runtime was
19
+ * queried, so a benchmark or a caller can tell a validated cache hit from a
20
+ * fallback that happens to be fast.
21
+ */
22
+ export interface FreshnessCheck {
23
+ /** `revision` when a revision decided it; `refresh` when the runtime was asked for the data. */
24
+ validation: 'revision' | 'refresh';
25
+ /** True only when the runtime was not queried for the collection at all. */
26
+ servedFromCache: boolean;
27
+ /** The revision the cache now corresponds to, when there is one. */
28
+ revision?: Revision;
29
+ /** Why a revision was not used. */
30
+ reason?: string;
31
+ }
14
32
  /**
15
33
  * Result of an atomic version-checked update operation.
16
34
  * Represents either a successful commit or a version conflict.
@@ -49,6 +67,26 @@ export declare class Collection<T> {
49
67
  private indexBackend;
50
68
  private indexStore;
51
69
  private indexesLoaded;
70
+ /** Incremented whenever `cache` is replaced, so the index can tell it is stale. */
71
+ private cacheGeneration;
72
+ /** The cache generation the index entries currently reflect, or -1 for none. */
73
+ private indexedGeneration;
74
+ private lastPlan;
75
+ /** Cache records addressed by id, valid for `indexedGeneration`. */
76
+ private recordsById;
77
+ /** Raw payload the cache was parsed from, used to detect that nothing changed. */
78
+ private lastRawSnapshot;
79
+ /**
80
+ * The runtime revision the cache was built from, when the runtime offers one.
81
+ *
82
+ * Captured *before* the query that produced the cache, never after. A write
83
+ * landing between the two then makes this older than the data it labels,
84
+ * which costs one unnecessary refresh. Capturing it after would make it
85
+ * newer than the data, and the cache would look current forever.
86
+ */
87
+ private cachedRevision;
88
+ /** How the last refresh decided the cache was usable. Reporting only. */
89
+ private lastFreshness;
52
90
  constructor(db: JsDb, name: string, predicate?: Predicate<T>, parent?: Collection<T>, loadIndexes?: boolean);
53
91
  /**
54
92
  * Load indexes that were persisted from a previous session.
@@ -59,8 +97,46 @@ export declare class Collection<T> {
59
97
  * Returns cached results (live-updated).
60
98
  */
61
99
  all(): Promise<T[]>;
62
- /** Find records whose fields match the supplied query. */
100
+ /**
101
+ * Find records whose fields match the supplied query.
102
+ *
103
+ * When a hash index covers one of the queried fields, the index supplies the
104
+ * candidate records and the remaining fields are checked on those candidates
105
+ * only. Otherwise every record is examined.
106
+ *
107
+ * The result is identical either way. The index is a projection of the same
108
+ * cached array a scan would walk, rebuilt whenever that array is replaced,
109
+ * so an indexed lookup cannot return a record a scan would miss or resolve
110
+ * an id the cache no longer holds.
111
+ *
112
+ * `lastQueryPlan()` reports which path ran.
113
+ */
63
114
  find(query?: Partial<T>): Promise<T[]>;
115
+ /**
116
+ * How the most recent `find` was answered.
117
+ *
118
+ * Exposed so an application — and the benchmark — can confirm that the index
119
+ * was actually consulted rather than inferring it from timing.
120
+ */
121
+ lastQueryPlan(): CollectionQueryPlan | null;
122
+ /** A hash index covering one of the queried fields, if there is one. */
123
+ private indexFor;
124
+ /**
125
+ * Candidate records from an index, or null when the index cannot be trusted.
126
+ *
127
+ * Returning null makes the caller fall back to a scan, which is always
128
+ * correct. That is the safe direction: a wrong answer is far worse than a
129
+ * slow one.
130
+ */
131
+ private indexCandidates;
132
+ /**
133
+ * Rebuild the index from the current cache when the cache has moved on.
134
+ *
135
+ * The index is only ever a projection of `this.cache`. Deriving it from the
136
+ * same array a scan walks is what makes the two paths agree by construction,
137
+ * rather than by hoping every write path remembered to update the index.
138
+ */
139
+ private ensureIndexReflectsCache;
64
140
  /**
65
141
  * Create an index on this collection for faster queries.
66
142
  * @example
@@ -159,6 +235,38 @@ export declare class Collection<T> {
159
235
  subscribe(subscriber: Subscriber<T>, _pollInterval?: number): () => void;
160
236
  /** Release runtime subscriptions owned by this live collection. */
161
237
  close(): void;
238
+ /**
239
+ * What this collection's runtime can tell it about staleness.
240
+ *
241
+ * This is a *validation mechanism*, not a guarantee that the cache is fresh.
242
+ * `validation: 'revision'` means a cheap staleness check exists; it says
243
+ * nothing about whether this collection's cache currently passes it.
244
+ *
245
+ * @example
246
+ * const capability = await people.freshness();
247
+ * // 'revision' -> a cheap staleness check exists
248
+ * // 'refresh' -> re-read is the only honest answer, and `reason` says why
249
+ */
250
+ freshness(): Promise<FreshnessCapability>;
251
+ /**
252
+ * How the last refresh decided the cache was usable.
253
+ *
254
+ * `servedFromCache` is true only when the runtime was never asked for this
255
+ * collection's data. Reporting only; it exists so a benchmark can tell a
256
+ * validated cache hit from a fallback that merely happened to be fast.
257
+ */
258
+ lastFreshnessCheck(): FreshnessCheck | null;
259
+ /**
260
+ * Read the runtime's current revision, or explain why there isn't one.
261
+ *
262
+ * The capability is re-checked on every call rather than remembered, because
263
+ * a deployment can change underneath a live collection: switching on
264
+ * replication revokes revision authority, and a collection holding a cached
265
+ * capability would keep trusting a number that stopped meaning anything.
266
+ * Any refusal — no capability, a topology change, a transport failure —
267
+ * lands here as a reason, and the caller falls back to a full refresh.
268
+ */
269
+ private currentRevision;
162
270
  /**
163
271
  * Refresh data from the database and notify subscribers.
164
272
  */
@@ -1 +1 @@
1
- {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}