@peac/capture-core 0.10.7

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.
@@ -0,0 +1,244 @@
1
+ "use strict";
2
+ /**
3
+ * @peac/capture-core - Capture Session
4
+ *
5
+ * Stateful capture pipeline that orchestrates hashing, deduplication,
6
+ * and spool storage.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.DefaultCaptureSession = void 0;
10
+ exports.createCaptureSession = createCaptureSession;
11
+ // =============================================================================
12
+ // Capture Session Implementation
13
+ // =============================================================================
14
+ /**
15
+ * Default capture session implementation.
16
+ *
17
+ * Orchestrates:
18
+ * 1. Deduplication check
19
+ * 2. Payload hashing (input/output)
20
+ * 3. Entry creation with chain linking
21
+ * 4. Spool storage
22
+ * 5. Dedupe index update
23
+ */
24
+ class DefaultCaptureSession {
25
+ store;
26
+ dedupe;
27
+ hasher;
28
+ closed = false;
29
+ // Concurrency serialization: queue captures to prevent race conditions
30
+ // on sequence numbers and chain linkage
31
+ captureQueue = Promise.resolve();
32
+ constructor(config) {
33
+ this.store = config.store;
34
+ this.dedupe = config.dedupe;
35
+ this.hasher = config.hasher;
36
+ }
37
+ /**
38
+ * Capture an action.
39
+ *
40
+ * Process:
41
+ * 1. Validate action
42
+ * 2. Serialize via queue (prevents race conditions)
43
+ * 3. Check for duplicate (by action.id)
44
+ * 4. Hash input/output payloads
45
+ * 5. Create spool entry with chain link
46
+ * 6. Append to spool
47
+ * 7. Update dedupe index
48
+ *
49
+ * Concurrency: Capture calls are serialized to prevent race conditions
50
+ * on sequence numbers and chain linkage.
51
+ *
52
+ * GUARANTEE: This method NEVER throws. All failures are returned as
53
+ * CaptureResult with success=false. This ensures the capture queue
54
+ * remains healthy and subsequent captures can proceed.
55
+ */
56
+ async capture(action) {
57
+ try {
58
+ // Check session state (convert throw to result)
59
+ if (this.closed) {
60
+ return {
61
+ success: false,
62
+ code: 'E_CAPTURE_SESSION_CLOSED',
63
+ message: 'CaptureSession is closed',
64
+ };
65
+ }
66
+ // 1. Validate action (can run before serialization)
67
+ const validationError = this.validateAction(action);
68
+ if (validationError) {
69
+ return {
70
+ success: false,
71
+ code: 'E_CAPTURE_INVALID_ACTION',
72
+ message: validationError,
73
+ };
74
+ }
75
+ // 2. Serialize capture operations to prevent race conditions
76
+ let result;
77
+ const capturePromise = this.captureQueue.then(async () => {
78
+ result = await this.captureInternal(action);
79
+ });
80
+ // Keep queue alive regardless of outcome
81
+ this.captureQueue = capturePromise.catch(() => {
82
+ // Swallow errors to keep queue alive for subsequent captures
83
+ });
84
+ // Await result, but catch any unexpected throws
85
+ await capturePromise;
86
+ return result;
87
+ }
88
+ catch (error) {
89
+ // Last-resort catch: convert ANY unexpected throw to CaptureResult
90
+ // This should never happen if captureInternal is correct, but provides safety
91
+ const message = error instanceof Error ? error.message : String(error);
92
+ return {
93
+ success: false,
94
+ code: 'E_CAPTURE_INTERNAL',
95
+ message: `Internal capture error: ${message}`,
96
+ };
97
+ }
98
+ }
99
+ /**
100
+ * Internal capture logic (runs serialized).
101
+ *
102
+ * IMPORTANT: This method must NEVER throw - it always returns CaptureResult.
103
+ * This is critical for queue safety: if this throws, the queue chain would
104
+ * propagate the rejection to the caller while the queue itself stays healthy.
105
+ */
106
+ async captureInternal(action) {
107
+ try {
108
+ // 3. Check for duplicate (inside try/catch for queue safety)
109
+ if (await this.dedupe.has(action.id)) {
110
+ return {
111
+ success: false,
112
+ code: 'E_CAPTURE_DUPLICATE',
113
+ message: `Action ${action.id} already captured`,
114
+ };
115
+ }
116
+ // 4. Hash payloads
117
+ const inputDigest = action.input_bytes
118
+ ? await this.hasher.digest(action.input_bytes)
119
+ : undefined;
120
+ const outputDigest = action.output_bytes
121
+ ? await this.hasher.digest(action.output_bytes)
122
+ : undefined;
123
+ // 5. Get current chain state
124
+ const prevDigest = await this.store.getHeadDigest();
125
+ const sequence = (await this.store.getSequence()) + 1;
126
+ // 6. Build entry (without entry_digest)
127
+ // DETERMINISM: Derive captured_at from action timestamps, not wall clock.
128
+ // This ensures the same action stream produces identical chain digests.
129
+ const capturedAt = action.completed_at ?? action.started_at;
130
+ const partialEntry = {
131
+ captured_at: capturedAt,
132
+ action: this.stripPayloadBytes(action),
133
+ input_digest: inputDigest,
134
+ output_digest: outputDigest,
135
+ prev_entry_digest: prevDigest,
136
+ sequence,
137
+ };
138
+ // 7. Compute entry digest
139
+ const entryDigest = await this.hasher.digestEntry(partialEntry);
140
+ // 8. Complete entry
141
+ const entry = {
142
+ ...partialEntry,
143
+ entry_digest: entryDigest,
144
+ };
145
+ // 9. Append to spool
146
+ await this.store.append(entry);
147
+ // 10. Update dedupe index
148
+ await this.dedupe.set(action.id, {
149
+ sequence,
150
+ entry_digest: entryDigest,
151
+ captured_at: capturedAt,
152
+ emitted: false,
153
+ });
154
+ return { success: true, entry };
155
+ }
156
+ catch (error) {
157
+ // Determine error type
158
+ const message = error instanceof Error ? error.message : String(error);
159
+ if (message.includes('hash') || message.includes('digest')) {
160
+ return {
161
+ success: false,
162
+ code: 'E_CAPTURE_HASH_FAILED',
163
+ message: `Hash failed: ${message}`,
164
+ };
165
+ }
166
+ return {
167
+ success: false,
168
+ code: 'E_CAPTURE_STORE_FAILED',
169
+ message: `Store failed: ${message}`,
170
+ };
171
+ }
172
+ }
173
+ /**
174
+ * Commit pending writes to durable storage.
175
+ */
176
+ async commit() {
177
+ this.assertNotClosed();
178
+ await this.store.commit();
179
+ }
180
+ /**
181
+ * Get the current spool head digest.
182
+ */
183
+ async getHeadDigest() {
184
+ this.assertNotClosed();
185
+ return this.store.getHeadDigest();
186
+ }
187
+ /**
188
+ * Close the session and release resources.
189
+ */
190
+ async close() {
191
+ if (!this.closed) {
192
+ await this.store.close();
193
+ this.closed = true;
194
+ }
195
+ }
196
+ // =============================================================================
197
+ // Private Helpers
198
+ // =============================================================================
199
+ /**
200
+ * Validate action has required fields.
201
+ */
202
+ validateAction(action) {
203
+ if (!action.id || action.id.trim() === '') {
204
+ return 'Missing action.id';
205
+ }
206
+ if (!action.kind || action.kind.trim() === '') {
207
+ return 'Missing action.kind';
208
+ }
209
+ if (!action.platform || action.platform.trim() === '') {
210
+ return 'Missing action.platform';
211
+ }
212
+ if (!action.started_at || action.started_at.trim() === '') {
213
+ return 'Missing action.started_at';
214
+ }
215
+ return null;
216
+ }
217
+ /**
218
+ * Strip payload bytes from action for storage.
219
+ */
220
+ stripPayloadBytes(action) {
221
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
222
+ const { input_bytes, output_bytes, ...rest } = action;
223
+ return rest;
224
+ }
225
+ /**
226
+ * Assert session is not closed.
227
+ */
228
+ assertNotClosed() {
229
+ if (this.closed) {
230
+ throw new Error('CaptureSession is closed');
231
+ }
232
+ }
233
+ }
234
+ exports.DefaultCaptureSession = DefaultCaptureSession;
235
+ // =============================================================================
236
+ // Factory Function
237
+ // =============================================================================
238
+ /**
239
+ * Create a capture session.
240
+ */
241
+ function createCaptureSession(config) {
242
+ return new DefaultCaptureSession(config);
243
+ }
244
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @peac/capture-core/testkit
3
+ *
4
+ * In-memory implementations for testing.
5
+ * NOT for production use - use @peac/capture-node for durable storage.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import {
10
+ * createInMemorySpoolStore,
11
+ * createInMemoryDedupeIndex,
12
+ * } from '@peac/capture-core/testkit';
13
+ * ```
14
+ */
15
+ export { InMemorySpoolStore, InMemoryDedupeIndex, createInMemorySpoolStore, createInMemoryDedupeIndex, } from './memory';
16
+ //# sourceMappingURL=testkit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testkit.d.ts","sourceRoot":"","sources":["../src/testkit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,UAAU,CAAC"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ /**
3
+ * @peac/capture-core/testkit
4
+ *
5
+ * In-memory implementations for testing.
6
+ * NOT for production use - use @peac/capture-node for durable storage.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import {
11
+ * createInMemorySpoolStore,
12
+ * createInMemoryDedupeIndex,
13
+ * } from '@peac/capture-core/testkit';
14
+ * ```
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.createInMemoryDedupeIndex = exports.createInMemorySpoolStore = exports.InMemoryDedupeIndex = exports.InMemorySpoolStore = void 0;
18
+ var memory_1 = require("./memory");
19
+ Object.defineProperty(exports, "InMemorySpoolStore", { enumerable: true, get: function () { return memory_1.InMemorySpoolStore; } });
20
+ Object.defineProperty(exports, "InMemoryDedupeIndex", { enumerable: true, get: function () { return memory_1.InMemoryDedupeIndex; } });
21
+ Object.defineProperty(exports, "createInMemorySpoolStore", { enumerable: true, get: function () { return memory_1.createInMemorySpoolStore; } });
22
+ Object.defineProperty(exports, "createInMemoryDedupeIndex", { enumerable: true, get: function () { return memory_1.createInMemoryDedupeIndex; } });
23
+ //# sourceMappingURL=testkit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testkit.js","sourceRoot":"","sources":["../src/testkit.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;AAEH,mCAKkB;AAJhB,4GAAA,kBAAkB,OAAA;AAClB,6GAAA,mBAAmB,OAAA;AACnB,kHAAA,wBAAwB,OAAA;AACxB,mHAAA,yBAAyB,OAAA"}
@@ -0,0 +1,282 @@
1
+ /**
2
+ * @peac/capture-core - Type Definitions
3
+ *
4
+ * Runtime-neutral types for the capture pipeline.
5
+ * NO Node.js APIs, NO fs, NO path - pure types and interfaces only.
6
+ *
7
+ * Filesystem implementations belong in @peac/capture-node.
8
+ */
9
+ import type { Digest } from '@peac/schema';
10
+ /**
11
+ * Execution status of a captured action.
12
+ */
13
+ export type ActionStatus = 'ok' | 'error' | 'timeout' | 'canceled';
14
+ /**
15
+ * Policy snapshot at capture time.
16
+ */
17
+ export interface PolicySnapshot {
18
+ /** Policy decision that allowed/denied the action */
19
+ decision: 'allow' | 'deny' | 'constrained';
20
+ /** Whether sandbox mode was enabled */
21
+ sandbox_enabled?: boolean;
22
+ /** Whether elevated permissions were granted */
23
+ elevated?: boolean;
24
+ /** Hash of the effective policy document (64 lowercase hex) */
25
+ policy_digest?: string;
26
+ }
27
+ /**
28
+ * Runtime-neutral captured action.
29
+ *
30
+ * This is the input to the capture pipeline, before any hashing or
31
+ * transformation. Platform-specific adapters convert their events
32
+ * to this common format.
33
+ *
34
+ * IMPORTANT: Timestamps are ISO 8601 strings for deterministic serialization.
35
+ */
36
+ export interface CapturedAction {
37
+ /** Stable ID for idempotency/dedupe (REQUIRED) */
38
+ id: string;
39
+ /** Event kind - "tool.call", "http.request", etc. */
40
+ kind: string;
41
+ /** Platform identifier - "openclaw", "mcp", "a2a", "claude-code" */
42
+ platform: string;
43
+ /** Platform version (optional) */
44
+ platform_version?: string;
45
+ /** Plugin that captured this (optional) */
46
+ plugin_id?: string;
47
+ /** Tool name (for tool.call kind) */
48
+ tool_name?: string;
49
+ /** Tool provider (optional) */
50
+ tool_provider?: string;
51
+ /** Resource URI (for http/fs kinds) */
52
+ resource_uri?: string;
53
+ /** HTTP method (for http.request kind) */
54
+ resource_method?: string;
55
+ /** Raw input bytes (will be hashed, then discarded) */
56
+ input_bytes?: Uint8Array;
57
+ /** Raw output bytes (will be hashed, then discarded) */
58
+ output_bytes?: Uint8Array;
59
+ /** Start time (ISO 8601 string for determinism) */
60
+ started_at: string;
61
+ /** Completion time (ISO 8601 string) */
62
+ completed_at?: string;
63
+ /** Duration in milliseconds from monotonic clock */
64
+ duration_ms?: number;
65
+ /** Execution status */
66
+ status?: ActionStatus;
67
+ /** Error code if status is 'error' */
68
+ error_code?: string;
69
+ /** Whether the error is retryable */
70
+ retryable?: boolean;
71
+ /** Policy snapshot at execution time */
72
+ policy?: PolicySnapshot;
73
+ /** Platform-specific metadata (will be stored in extensions) */
74
+ metadata?: Record<string, unknown>;
75
+ }
76
+ /**
77
+ * Spool entry - the post-hashing record that can be serialized.
78
+ *
79
+ * This contains computed digests but NOT raw payload bytes (privacy-preserving).
80
+ * The format is deterministic for tamper-evident chaining.
81
+ */
82
+ export interface SpoolEntry {
83
+ /** When this entry was captured (RFC 3339) */
84
+ captured_at: string;
85
+ /** The captured action (without raw bytes) */
86
+ action: Omit<CapturedAction, 'input_bytes' | 'output_bytes'>;
87
+ /** Input payload digest (computed inline) */
88
+ input_digest?: Digest;
89
+ /** Output payload digest (computed inline) */
90
+ output_digest?: Digest;
91
+ /** Digest of previous entry in spool (chain link) */
92
+ prev_entry_digest: string;
93
+ /** Digest of this entry (for next entry's prev) */
94
+ entry_digest: string;
95
+ /** Sequence number in the spool (monotonic) */
96
+ sequence: number;
97
+ }
98
+ /**
99
+ * Abstract spool storage interface.
100
+ *
101
+ * Implementations handle the actual storage mechanism:
102
+ * - InMemorySpoolStore (for tests, in this package)
103
+ * - FsSpoolStore (in @peac/capture-node)
104
+ * - CloudSpoolStore (future, for serverless)
105
+ */
106
+ export interface SpoolStore {
107
+ /**
108
+ * Append an entry to the spool.
109
+ * Returns the assigned sequence number.
110
+ */
111
+ append(entry: SpoolEntry): Promise<number>;
112
+ /**
113
+ * Commit/sync the spool to durable storage.
114
+ * No-op for in-memory stores.
115
+ */
116
+ commit(): Promise<void>;
117
+ /**
118
+ * Read entries starting from a sequence number.
119
+ * Returns entries in order.
120
+ */
121
+ read(fromSequence: number, limit?: number): Promise<SpoolEntry[]>;
122
+ /**
123
+ * Get the current head digest (last entry's digest).
124
+ * Returns genesis digest if spool is empty.
125
+ */
126
+ getHeadDigest(): Promise<string>;
127
+ /**
128
+ * Get the current sequence number (last entry's sequence).
129
+ * Returns 0 if spool is empty.
130
+ */
131
+ getSequence(): Promise<number>;
132
+ /**
133
+ * Close the store and release resources.
134
+ */
135
+ close(): Promise<void>;
136
+ }
137
+ /**
138
+ * Dedupe entry - tracks captured actions to prevent duplicates.
139
+ */
140
+ export interface DedupeEntry {
141
+ /** Sequence number in spool */
142
+ sequence: number;
143
+ /** Entry digest for verification */
144
+ entry_digest: string;
145
+ /** When the action was captured */
146
+ captured_at: string;
147
+ /** Whether a receipt has been emitted */
148
+ emitted: boolean;
149
+ }
150
+ /**
151
+ * Abstract dedupe index interface.
152
+ *
153
+ * All methods are async to support durable backends (sqlite, kv, etc.)
154
+ * without forcing implementers to use sync filesystem calls.
155
+ *
156
+ * Implementations handle the actual storage:
157
+ * - InMemoryDedupeIndex (for tests, in this package)
158
+ * - PersistentDedupeIndex (in @peac/capture-node)
159
+ */
160
+ export interface DedupeIndex {
161
+ /** Get entry by action ID */
162
+ get(actionId: string): Promise<DedupeEntry | undefined>;
163
+ /** Set entry for action ID */
164
+ set(actionId: string, entry: DedupeEntry): Promise<void>;
165
+ /** Check if action ID exists */
166
+ has(actionId: string): Promise<boolean>;
167
+ /** Mark an entry as emitted */
168
+ markEmitted(actionId: string): Promise<boolean>;
169
+ /** Delete entry (for cleanup) */
170
+ delete(actionId: string): Promise<boolean>;
171
+ /** Get count of entries */
172
+ size(): Promise<number>;
173
+ /** Clear all entries */
174
+ clear(): Promise<void>;
175
+ }
176
+ /**
177
+ * Hasher configuration.
178
+ */
179
+ export interface HasherConfig {
180
+ /** Maximum bytes to hash before truncating (default: 1MB) */
181
+ truncateThreshold?: number;
182
+ }
183
+ /**
184
+ * Hasher interface for computing payload digests.
185
+ */
186
+ export interface Hasher {
187
+ /**
188
+ * Compute digest for payload bytes.
189
+ * Automatically truncates if payload exceeds threshold.
190
+ */
191
+ digest(payload: Uint8Array): Promise<Digest>;
192
+ /**
193
+ * Compute digest for a spool entry (for chaining).
194
+ * Uses deterministic serialization (JCS).
195
+ */
196
+ digestEntry(entry: Omit<SpoolEntry, 'entry_digest'>): Promise<string>;
197
+ }
198
+ /**
199
+ * Capture session configuration.
200
+ */
201
+ export interface CaptureSessionConfig {
202
+ /** Spool store implementation */
203
+ store: SpoolStore;
204
+ /** Dedupe index implementation */
205
+ dedupe: DedupeIndex;
206
+ /** Hasher implementation */
207
+ hasher: Hasher;
208
+ }
209
+ /**
210
+ * Capture result for a single action.
211
+ */
212
+ export type CaptureResult = {
213
+ success: true;
214
+ entry: SpoolEntry;
215
+ } | {
216
+ success: false;
217
+ code: CaptureErrorCode;
218
+ message: string;
219
+ };
220
+ /**
221
+ * Capture error codes.
222
+ *
223
+ * Layer-separated error codes:
224
+ * - E_CAPTURE_* codes are for capture pipeline failures, NOT schema validation
225
+ * - E_INTERACTION_* codes (in @peac/schema) are for receipt/profile validation
226
+ */
227
+ export type CaptureErrorCode = 'E_CAPTURE_DUPLICATE' | 'E_CAPTURE_HASH_FAILED' | 'E_CAPTURE_STORE_FAILED' | 'E_CAPTURE_INVALID_ACTION' | 'E_CAPTURE_SESSION_CLOSED' | 'E_CAPTURE_INTERNAL';
228
+ /**
229
+ * Capture session - stateful capture pipeline instance.
230
+ */
231
+ export interface CaptureSession {
232
+ /**
233
+ * Capture an action.
234
+ * Returns success with entry, or failure with error code.
235
+ */
236
+ capture(action: CapturedAction): Promise<CaptureResult>;
237
+ /**
238
+ * Commit any pending writes to durable storage.
239
+ */
240
+ commit(): Promise<void>;
241
+ /**
242
+ * Get the current spool head digest.
243
+ */
244
+ getHeadDigest(): Promise<string>;
245
+ /**
246
+ * Close the session and release resources.
247
+ */
248
+ close(): Promise<void>;
249
+ }
250
+ /**
251
+ * Spool anchor extension data.
252
+ *
253
+ * When included in a receipt, this allows external verifiers to
254
+ * check the spool chain without access to the full spool file.
255
+ */
256
+ export interface SpoolAnchor {
257
+ /** Current head digest of the spool chain */
258
+ spool_head_digest: string;
259
+ /** Sequence number in the spool */
260
+ sequence: number;
261
+ /** Timestamp of the anchor */
262
+ anchored_at: string;
263
+ }
264
+ /**
265
+ * Genesis digest - the "prev" digest for the first entry in a spool.
266
+ * 64 zeros represents "no previous entry".
267
+ */
268
+ export declare const GENESIS_DIGEST: string;
269
+ /**
270
+ * Default truncation threshold: 1MB (1024 * 1024 bytes).
271
+ */
272
+ export declare const DEFAULT_TRUNCATE_THRESHOLD: number;
273
+ /**
274
+ * Size constants for truncation algorithms.
275
+ */
276
+ export declare const SIZE_CONSTANTS: {
277
+ readonly K: 1024;
278
+ readonly M: number;
279
+ readonly TRUNC_64K: number;
280
+ readonly TRUNC_1M: number;
281
+ };
282
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ /**
3
+ * @peac/capture-core - Type Definitions
4
+ *
5
+ * Runtime-neutral types for the capture pipeline.
6
+ * NO Node.js APIs, NO fs, NO path - pure types and interfaces only.
7
+ *
8
+ * Filesystem implementations belong in @peac/capture-node.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.SIZE_CONSTANTS = exports.DEFAULT_TRUNCATE_THRESHOLD = exports.GENESIS_DIGEST = void 0;
12
+ // =============================================================================
13
+ // Constants
14
+ // =============================================================================
15
+ /**
16
+ * Genesis digest - the "prev" digest for the first entry in a spool.
17
+ * 64 zeros represents "no previous entry".
18
+ */
19
+ exports.GENESIS_DIGEST = '0'.repeat(64);
20
+ /**
21
+ * Default truncation threshold: 1MB (1024 * 1024 bytes).
22
+ */
23
+ exports.DEFAULT_TRUNCATE_THRESHOLD = 1024 * 1024;
24
+ /**
25
+ * Size constants for truncation algorithms.
26
+ */
27
+ exports.SIZE_CONSTANTS = {
28
+ K: 1024, // 1 KB
29
+ M: 1024 * 1024, // 1 MB
30
+ TRUNC_64K: 64 * 1024, // 64 KB
31
+ TRUNC_1M: 1024 * 1024, // 1 MB
32
+ };
33
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AA6VH,gFAAgF;AAChF,YAAY;AACZ,gFAAgF;AAEhF;;;GAGG;AACU,QAAA,cAAc,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAE7C;;GAEG;AACU,QAAA,0BAA0B,GAAG,IAAI,GAAG,IAAI,CAAC;AAEtD;;GAEG;AACU,QAAA,cAAc,GAAG;IAC5B,CAAC,EAAE,IAAI,EAAE,OAAO;IAChB,CAAC,EAAE,IAAI,GAAG,IAAI,EAAE,OAAO;IACvB,SAAS,EAAE,EAAE,GAAG,IAAI,EAAE,QAAQ;IAC9B,QAAQ,EAAE,IAAI,GAAG,IAAI,EAAE,OAAO;CACtB,CAAC"}