@cello-protocol/client 0.0.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/dist/agent-hash-queue.d.ts +206 -0
- package/dist/agent-hash-queue.d.ts.map +1 -0
- package/dist/agent-hash-queue.js +380 -0
- package/dist/agent-hash-queue.js.map +1 -0
- package/dist/backup-key-derivation.d.ts +37 -0
- package/dist/backup-key-derivation.d.ts.map +1 -0
- package/dist/backup-key-derivation.js +48 -0
- package/dist/backup-key-derivation.js.map +1 -0
- package/dist/client-backup.d.ts +144 -0
- package/dist/client-backup.d.ts.map +1 -0
- package/dist/client-backup.js +273 -0
- package/dist/client-backup.js.map +1 -0
- package/dist/client.d.ts +249 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +4664 -0
- package/dist/client.js.map +1 -0
- package/dist/connection-policy.d.ts +163 -0
- package/dist/connection-policy.d.ts.map +1 -0
- package/dist/connection-policy.js +248 -0
- package/dist/connection-policy.js.map +1 -0
- package/dist/db-key-derivation.d.ts +26 -0
- package/dist/db-key-derivation.d.ts.map +1 -0
- package/dist/db-key-derivation.js +37 -0
- package/dist/db-key-derivation.js.map +1 -0
- package/dist/encrypted-file-signing-key-provider.d.ts +92 -0
- package/dist/encrypted-file-signing-key-provider.d.ts.map +1 -0
- package/dist/encrypted-file-signing-key-provider.js +251 -0
- package/dist/encrypted-file-signing-key-provider.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server.d.ts +270 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +1155 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/network-directory-node.d.ts +85 -0
- package/dist/network-directory-node.d.ts.map +1 -0
- package/dist/network-directory-node.js +584 -0
- package/dist/network-directory-node.js.map +1 -0
- package/dist/s3-cloud-storage-provider.d.ts +54 -0
- package/dist/s3-cloud-storage-provider.d.ts.map +1 -0
- package/dist/s3-cloud-storage-provider.js +78 -0
- package/dist/s3-cloud-storage-provider.js.map +1 -0
- package/dist/sqlcipher-client-store.d.ts +68 -0
- package/dist/sqlcipher-client-store.d.ts.map +1 -0
- package/dist/sqlcipher-client-store.js +382 -0
- package/dist/sqlcipher-client-store.js.map +1 -0
- package/dist/types.d.ts +408 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3CloudStorageProvider — AWS S3-backed CloudStorageProvider for CELLO_ENV != local.
|
|
3
|
+
*
|
|
4
|
+
* PERSIST-022: Implements the CloudStorageProvider interface using AWS S3.
|
|
5
|
+
* Used for encrypted backup upload/download when BACKUP_S3_BUCKET is configured.
|
|
6
|
+
*
|
|
7
|
+
* Pseudocode:
|
|
8
|
+
* constructor({ bucket, region }):
|
|
9
|
+
* 1. Store bucket and region.
|
|
10
|
+
* 2. Create S3Client with the provided region.
|
|
11
|
+
* The S3Client uses the AWS SDK's default credential chain
|
|
12
|
+
* (IAM role in ECS, ~/.aws credentials locally, etc.).
|
|
13
|
+
*
|
|
14
|
+
* upload(key, data):
|
|
15
|
+
* 1. Send PutObjectCommand({ Bucket: bucket, Key: key, Body: data }).
|
|
16
|
+
* 2. On success: return void.
|
|
17
|
+
* 3. On error: throw (caller — ClientBackup — handles it via client.backup.upload.failed).
|
|
18
|
+
*
|
|
19
|
+
* download(key):
|
|
20
|
+
* 1. Send GetObjectCommand({ Bucket: bucket, Key: key }).
|
|
21
|
+
* 2. On success: stream Body to a Uint8Array and return it.
|
|
22
|
+
* 3. On NoSuchKey (S3 error code): return undefined (key not found is not an error).
|
|
23
|
+
* 4. On other errors: throw (caller handles it).
|
|
24
|
+
*
|
|
25
|
+
* Security notes:
|
|
26
|
+
* - This class never touches encryption keys. ClientBackup handles all crypto.
|
|
27
|
+
* - No logging in this class — ClientBackup is the observable layer.
|
|
28
|
+
* - Constructor takes a plain config object (not an S3Client) so the interface stays clean.
|
|
29
|
+
*/
|
|
30
|
+
import type { CloudStorageProvider } from "@cello-protocol/interfaces";
|
|
31
|
+
/** Configuration for S3CloudStorageProvider. Provided by the composition root. */
|
|
32
|
+
export interface S3CloudStorageConfig {
|
|
33
|
+
/** S3 bucket name. Comes from BACKUP_S3_BUCKET env var, read at composition root. */
|
|
34
|
+
bucket: string;
|
|
35
|
+
/** AWS region. Comes from CELLO_AWS_REGION env var (falls back to AWS_REGION), read at composition root. */
|
|
36
|
+
region: string;
|
|
37
|
+
}
|
|
38
|
+
export declare class S3CloudStorageProvider implements CloudStorageProvider {
|
|
39
|
+
#private;
|
|
40
|
+
constructor(config: S3CloudStorageConfig);
|
|
41
|
+
/**
|
|
42
|
+
* Upload data to the given key in S3.
|
|
43
|
+
* Overwrites if the object already exists.
|
|
44
|
+
* Throws on any S3 error — ClientBackup handles the error and logs it.
|
|
45
|
+
*/
|
|
46
|
+
upload(key: string, data: Uint8Array): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Download data from the given key in S3.
|
|
49
|
+
* Returns undefined if the object does not exist (NoSuchKey).
|
|
50
|
+
* Throws on any other S3 error.
|
|
51
|
+
*/
|
|
52
|
+
download(key: string): Promise<Uint8Array | undefined>;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=s3-cloud-storage-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-cloud-storage-provider.d.ts","sourceRoot":"","sources":["../src/s3-cloud-storage-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAEvE,kFAAkF;AAClF,MAAM,WAAW,oBAAoB;IACnC,qFAAqF;IACrF,MAAM,EAAE,MAAM,CAAC;IACf,4GAA4G;IAC5G,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,sBAAuB,YAAW,oBAAoB;;gBAIrD,MAAM,EAAE,oBAAoB;IAKxC;;;;OAIG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAU1D;;;;OAIG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;CAyB7D"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3CloudStorageProvider — AWS S3-backed CloudStorageProvider for CELLO_ENV != local.
|
|
3
|
+
*
|
|
4
|
+
* PERSIST-022: Implements the CloudStorageProvider interface using AWS S3.
|
|
5
|
+
* Used for encrypted backup upload/download when BACKUP_S3_BUCKET is configured.
|
|
6
|
+
*
|
|
7
|
+
* Pseudocode:
|
|
8
|
+
* constructor({ bucket, region }):
|
|
9
|
+
* 1. Store bucket and region.
|
|
10
|
+
* 2. Create S3Client with the provided region.
|
|
11
|
+
* The S3Client uses the AWS SDK's default credential chain
|
|
12
|
+
* (IAM role in ECS, ~/.aws credentials locally, etc.).
|
|
13
|
+
*
|
|
14
|
+
* upload(key, data):
|
|
15
|
+
* 1. Send PutObjectCommand({ Bucket: bucket, Key: key, Body: data }).
|
|
16
|
+
* 2. On success: return void.
|
|
17
|
+
* 3. On error: throw (caller — ClientBackup — handles it via client.backup.upload.failed).
|
|
18
|
+
*
|
|
19
|
+
* download(key):
|
|
20
|
+
* 1. Send GetObjectCommand({ Bucket: bucket, Key: key }).
|
|
21
|
+
* 2. On success: stream Body to a Uint8Array and return it.
|
|
22
|
+
* 3. On NoSuchKey (S3 error code): return undefined (key not found is not an error).
|
|
23
|
+
* 4. On other errors: throw (caller handles it).
|
|
24
|
+
*
|
|
25
|
+
* Security notes:
|
|
26
|
+
* - This class never touches encryption keys. ClientBackup handles all crypto.
|
|
27
|
+
* - No logging in this class — ClientBackup is the observable layer.
|
|
28
|
+
* - Constructor takes a plain config object (not an S3Client) so the interface stays clean.
|
|
29
|
+
*/
|
|
30
|
+
import { S3Client, PutObjectCommand, GetObjectCommand, NoSuchKey } from "@aws-sdk/client-s3";
|
|
31
|
+
export class S3CloudStorageProvider {
|
|
32
|
+
#client;
|
|
33
|
+
#bucket;
|
|
34
|
+
constructor(config) {
|
|
35
|
+
this.#bucket = config.bucket;
|
|
36
|
+
this.#client = new S3Client({ region: config.region });
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Upload data to the given key in S3.
|
|
40
|
+
* Overwrites if the object already exists.
|
|
41
|
+
* Throws on any S3 error — ClientBackup handles the error and logs it.
|
|
42
|
+
*/
|
|
43
|
+
async upload(key, data) {
|
|
44
|
+
await this.#client.send(new PutObjectCommand({
|
|
45
|
+
Bucket: this.#bucket,
|
|
46
|
+
Key: key,
|
|
47
|
+
Body: data,
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Download data from the given key in S3.
|
|
52
|
+
* Returns undefined if the object does not exist (NoSuchKey).
|
|
53
|
+
* Throws on any other S3 error.
|
|
54
|
+
*/
|
|
55
|
+
async download(key) {
|
|
56
|
+
try {
|
|
57
|
+
const response = await this.#client.send(new GetObjectCommand({
|
|
58
|
+
Bucket: this.#bucket,
|
|
59
|
+
Key: key,
|
|
60
|
+
}));
|
|
61
|
+
// Body is a Readable stream from the SDK; collect into a Uint8Array
|
|
62
|
+
if (!response.Body) {
|
|
63
|
+
return new Uint8Array(0);
|
|
64
|
+
}
|
|
65
|
+
// @aws-sdk/client-s3 v3: Body has transformToByteArray() in Node.js environments
|
|
66
|
+
const bytes = await response.Body.transformToByteArray();
|
|
67
|
+
return bytes;
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
// Return undefined for the key-not-found case (AC-003)
|
|
71
|
+
if (err instanceof NoSuchKey) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=s3-cloud-storage-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-cloud-storage-provider.js","sourceRoot":"","sources":["../src/s3-cloud-storage-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAW7F,MAAM,OAAO,sBAAsB;IACxB,OAAO,CAAW;IAClB,OAAO,CAAS;IAEzB,YAAY,MAA4B;QACtC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,IAAI,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACzD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,MAAM,CAAC,GAAW,EAAE,IAAgB;QACxC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CACrB,IAAI,gBAAgB,CAAC;YACnB,MAAM,EAAE,IAAI,CAAC,OAAO;YACpB,GAAG,EAAE,GAAG;YACR,IAAI,EAAE,IAAI;SACX,CAAC,CACH,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,QAAQ,CAAC,GAAW;QACxB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CACtC,IAAI,gBAAgB,CAAC;gBACnB,MAAM,EAAE,IAAI,CAAC,OAAO;gBACpB,GAAG,EAAE,GAAG;aACT,CAAC,CACH,CAAC;YAEF,oEAAoE;YACpE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACnB,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;YAC3B,CAAC;YAED,iFAAiF;YACjF,MAAM,KAAK,GAAG,MAAO,QAAQ,CAAC,IAAwD,CAAC,oBAAoB,EAAE,CAAC;YAC9G,OAAO,KAAK,CAAC;QACf,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,uDAAuD;YACvD,IAAI,GAAG,YAAY,SAAS,EAAE,CAAC;gBAC7B,OAAO,SAAS,CAAC;YACnB,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLCipherClientStore — SQLCipher-encrypted ClientStore for CELLO_ENV=dev+.
|
|
3
|
+
*
|
|
4
|
+
* PERSIST-009: Implements the ClientStore interface backed by SQLCipher-encrypted SQLite.
|
|
5
|
+
* Used only by the composition root. Never exported from packages/client/src/index.ts.
|
|
6
|
+
*
|
|
7
|
+
* Security invariants (PERSIST-009):
|
|
8
|
+
* SI-001: db_key is NEVER logged, serialized, or included in any error message.
|
|
9
|
+
* SI-002: Constructor takes db_key (Uint8Array), NOT identity_key. The caller
|
|
10
|
+
* must derive db_key via deriveDbKey() before constructing this store.
|
|
11
|
+
* SI-003: No PRAGMA cipher_* commands that would weaken AES-256-CBC defaults.
|
|
12
|
+
*
|
|
13
|
+
* Degraded behavior (DB-001):
|
|
14
|
+
* On corrupt file or wrong key, logs client.store.open.failed and throws.
|
|
15
|
+
* Never creates a new empty database over a corrupt file.
|
|
16
|
+
*/
|
|
17
|
+
import type { ClientStore, Logger } from "@cello-protocol/interfaces";
|
|
18
|
+
export interface SQLCipherClientStoreOptions {
|
|
19
|
+
/** Absolute path to the SQLite database file. */
|
|
20
|
+
dbPath: string;
|
|
21
|
+
/** Stable agent identifier — included in log events. */
|
|
22
|
+
agentId: string;
|
|
23
|
+
/** CELLO_ENV value — included in client.store.opened log event. */
|
|
24
|
+
env?: string;
|
|
25
|
+
/**
|
|
26
|
+
* @internal test seam — defaults to packages/client/db/migrations/
|
|
27
|
+
*/
|
|
28
|
+
migrationsPath?: string;
|
|
29
|
+
/** Structured logger injected at the composition root. */
|
|
30
|
+
logger: Logger;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* ClientStore backed by a SQLCipher-encrypted SQLite database.
|
|
34
|
+
*
|
|
35
|
+
* Lifecycle: construct → open() → (use) → close().
|
|
36
|
+
*
|
|
37
|
+
* IMPORTANT: only import this from the composition root. The ESLint
|
|
38
|
+
* no-restricted-imports rule enforces this at compile time.
|
|
39
|
+
*/
|
|
40
|
+
export declare class SQLCipherClientStore implements ClientStore {
|
|
41
|
+
#private;
|
|
42
|
+
/**
|
|
43
|
+
* @param dbKey - 32-byte database encryption key derived from identity_key via HKDF.
|
|
44
|
+
* NEVER pass identity_key here — only the derived db_key (SI-002).
|
|
45
|
+
* @param options - Store configuration including dbPath, agentId, and logger.
|
|
46
|
+
*/
|
|
47
|
+
constructor(dbKey: Uint8Array, options: SQLCipherClientStoreOptions);
|
|
48
|
+
/**
|
|
49
|
+
* Open the encrypted database, verify the key, and apply pending migrations.
|
|
50
|
+
* Must be called before any read/write operations.
|
|
51
|
+
*
|
|
52
|
+
* Throws on:
|
|
53
|
+
* - corrupt file (file exists but is not a valid SQLCipher database)
|
|
54
|
+
* - wrong key (PRAGMA key does not decrypt the file)
|
|
55
|
+
* - migration failure
|
|
56
|
+
*
|
|
57
|
+
* On any throw, logs client.store.open.failed and does NOT create a new empty
|
|
58
|
+
* database over the corrupt file (DB-001).
|
|
59
|
+
*/
|
|
60
|
+
open(): Promise<void>;
|
|
61
|
+
/** Close the database. Idempotent — safe to call multiple times. */
|
|
62
|
+
close(): Promise<void>;
|
|
63
|
+
set(key: string, value: Uint8Array): Promise<void>;
|
|
64
|
+
get(key: string): Promise<Uint8Array | undefined>;
|
|
65
|
+
delete(key: string): Promise<void>;
|
|
66
|
+
has(key: string): Promise<boolean>;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=sqlcipher-client-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlcipher-client-store.d.ts","sourceRoot":"","sources":["../src/sqlcipher-client-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAkCtE,MAAM,WAAW,2BAA2B;IAC1C,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;CAChB;AAID;;;;;;;GAOG;AACH,qBAAa,oBAAqB,YAAW,WAAW;;IAWtD;;;;OAIG;gBACS,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,2BAA2B;IAkBnE;;;;;;;;;;;OAWG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmD3B,oEAAoE;IAC9D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IActB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBlD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAsBjD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWlC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAwNzC"}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLCipherClientStore — SQLCipher-encrypted ClientStore for CELLO_ENV=dev+.
|
|
3
|
+
*
|
|
4
|
+
* PERSIST-009: Implements the ClientStore interface backed by SQLCipher-encrypted SQLite.
|
|
5
|
+
* Used only by the composition root. Never exported from packages/client/src/index.ts.
|
|
6
|
+
*
|
|
7
|
+
* Security invariants (PERSIST-009):
|
|
8
|
+
* SI-001: db_key is NEVER logged, serialized, or included in any error message.
|
|
9
|
+
* SI-002: Constructor takes db_key (Uint8Array), NOT identity_key. The caller
|
|
10
|
+
* must derive db_key via deriveDbKey() before constructing this store.
|
|
11
|
+
* SI-003: No PRAGMA cipher_* commands that would weaken AES-256-CBC defaults.
|
|
12
|
+
*
|
|
13
|
+
* Degraded behavior (DB-001):
|
|
14
|
+
* On corrupt file or wrong key, logs client.store.open.failed and throws.
|
|
15
|
+
* Never creates a new empty database over a corrupt file.
|
|
16
|
+
*/
|
|
17
|
+
import { createRequire } from "node:module";
|
|
18
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
19
|
+
import { resolve, dirname } from "node:path";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
// ─── SQLCipherClientStore ────────────────────────────────────────────────────
|
|
22
|
+
/**
|
|
23
|
+
* ClientStore backed by a SQLCipher-encrypted SQLite database.
|
|
24
|
+
*
|
|
25
|
+
* Lifecycle: construct → open() → (use) → close().
|
|
26
|
+
*
|
|
27
|
+
* IMPORTANT: only import this from the composition root. The ESLint
|
|
28
|
+
* no-restricted-imports rule enforces this at compile time.
|
|
29
|
+
*/
|
|
30
|
+
export class SQLCipherClientStore {
|
|
31
|
+
#dbKey;
|
|
32
|
+
#dbPath;
|
|
33
|
+
#agentId;
|
|
34
|
+
#env;
|
|
35
|
+
#migrationsPath;
|
|
36
|
+
#logger;
|
|
37
|
+
// Mutable state set during open()
|
|
38
|
+
#db = null;
|
|
39
|
+
/**
|
|
40
|
+
* @param dbKey - 32-byte database encryption key derived from identity_key via HKDF.
|
|
41
|
+
* NEVER pass identity_key here — only the derived db_key (SI-002).
|
|
42
|
+
* @param options - Store configuration including dbPath, agentId, and logger.
|
|
43
|
+
*/
|
|
44
|
+
constructor(dbKey, options) {
|
|
45
|
+
this.#dbKey = dbKey;
|
|
46
|
+
this.#dbPath = options.dbPath;
|
|
47
|
+
this.#agentId = options.agentId;
|
|
48
|
+
this.#env = options.env ?? "unknown";
|
|
49
|
+
this.#logger = options.logger;
|
|
50
|
+
if (options.migrationsPath !== undefined) {
|
|
51
|
+
this.#migrationsPath = options.migrationsPath;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// Resolve default migrations path relative to this module file.
|
|
55
|
+
// Works for both the TypeScript source (packages/client/src/) and
|
|
56
|
+
// the compiled output (packages/client/dist/), since db/ is a sibling of both.
|
|
57
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
58
|
+
this.#migrationsPath = resolve(moduleDir, "../db/migrations");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Open the encrypted database, verify the key, and apply pending migrations.
|
|
63
|
+
* Must be called before any read/write operations.
|
|
64
|
+
*
|
|
65
|
+
* Throws on:
|
|
66
|
+
* - corrupt file (file exists but is not a valid SQLCipher database)
|
|
67
|
+
* - wrong key (PRAGMA key does not decrypt the file)
|
|
68
|
+
* - migration failure
|
|
69
|
+
*
|
|
70
|
+
* On any throw, logs client.store.open.failed and does NOT create a new empty
|
|
71
|
+
* database over the corrupt file (DB-001).
|
|
72
|
+
*/
|
|
73
|
+
async open() {
|
|
74
|
+
// Load the native module. Using createRequire because @journeyapps/sqlcipher
|
|
75
|
+
// is a CJS module and this file is ESM.
|
|
76
|
+
const require = createRequire(import.meta.url);
|
|
77
|
+
let sqlite3Module;
|
|
78
|
+
try {
|
|
79
|
+
sqlite3Module = require("@journeyapps/sqlcipher");
|
|
80
|
+
}
|
|
81
|
+
catch (loadErr) {
|
|
82
|
+
const reason = loadErr instanceof Error ? loadErr.message : String(loadErr);
|
|
83
|
+
this.#logger.error("client.store.open.failed", { reason: `SQLCipher native module unavailable: ${reason}`, agentId: this.#agentId });
|
|
84
|
+
throw new Error(`SQLCipher native module unavailable: ${reason}`);
|
|
85
|
+
}
|
|
86
|
+
const db = await this.#openDatabase(sqlite3Module);
|
|
87
|
+
// NOTE: do not assign this.#db yet — #applyKey and #verifyKey must succeed first.
|
|
88
|
+
try {
|
|
89
|
+
// Apply PRAGMA key (SI-003: only the key PRAGMA — no weakening cipher settings)
|
|
90
|
+
await this.#applyKey(db);
|
|
91
|
+
// Verify the key is correct by reading the sqlite_master table.
|
|
92
|
+
// If the key is wrong or the file is corrupt, this will fail.
|
|
93
|
+
await this.#verifyKey(db);
|
|
94
|
+
// Key is verified — safe to make the database accessible to other methods.
|
|
95
|
+
this.#db = db;
|
|
96
|
+
// Run migration runner — applies pending V{n}__*.sql files
|
|
97
|
+
await this.#runMigrations(db);
|
|
98
|
+
this.#logger.info("client.store.opened", {
|
|
99
|
+
env: this.#env,
|
|
100
|
+
dbPath: this.#dbPath,
|
|
101
|
+
agentId: this.#agentId,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
106
|
+
// SI-001: never include key material in the error log
|
|
107
|
+
this.#logger.error("client.store.open.failed", {
|
|
108
|
+
reason,
|
|
109
|
+
agentId: this.#agentId,
|
|
110
|
+
});
|
|
111
|
+
// Close the DB to release the file handle
|
|
112
|
+
await new Promise((resolve) => {
|
|
113
|
+
db.close(() => resolve());
|
|
114
|
+
});
|
|
115
|
+
this.#db = null;
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** Close the database. Idempotent — safe to call multiple times. */
|
|
120
|
+
async close() {
|
|
121
|
+
if (this.#db === null)
|
|
122
|
+
return;
|
|
123
|
+
const db = this.#db;
|
|
124
|
+
this.#db = null;
|
|
125
|
+
await new Promise((resolve, reject) => {
|
|
126
|
+
db.close((err) => {
|
|
127
|
+
if (err)
|
|
128
|
+
reject(err);
|
|
129
|
+
else
|
|
130
|
+
resolve();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// ─── ClientStore interface ─────────────────────────────────────────────────
|
|
135
|
+
async set(key, value) {
|
|
136
|
+
this.#assertOpen();
|
|
137
|
+
const db = this.#db;
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
db.run(`INSERT INTO client_store (key, value, updated_at)
|
|
140
|
+
VALUES (?, ?, datetime('now'))
|
|
141
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`, [key, Buffer.from(value)], function (err) {
|
|
142
|
+
if (err)
|
|
143
|
+
reject(err);
|
|
144
|
+
else
|
|
145
|
+
resolve();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async get(key) {
|
|
150
|
+
this.#assertOpen();
|
|
151
|
+
const db = this.#db;
|
|
152
|
+
return new Promise((resolve, reject) => {
|
|
153
|
+
db.get(`SELECT value FROM client_store WHERE key = ?`, [key], (err, row) => {
|
|
154
|
+
if (err)
|
|
155
|
+
return reject(err);
|
|
156
|
+
if (row === undefined)
|
|
157
|
+
return resolve(undefined);
|
|
158
|
+
// SQLite BLOB comes back as a Node.js Buffer.
|
|
159
|
+
// Buffer is a Uint8Array subclass, but callers expect a plain Uint8Array.
|
|
160
|
+
// Use the copy constructor `new Uint8Array(raw)` — NOT the ArrayBuffer view
|
|
161
|
+
// form `new Uint8Array(raw.buffer, byteOffset, byteLength)` which shares the
|
|
162
|
+
// underlying memory. A copy is required so callers cannot mutate stored data.
|
|
163
|
+
const raw = row.value;
|
|
164
|
+
resolve(new Uint8Array(raw));
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
async delete(key) {
|
|
169
|
+
this.#assertOpen();
|
|
170
|
+
const db = this.#db;
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
db.run(`DELETE FROM client_store WHERE key = ?`, [key], function (err) {
|
|
173
|
+
if (err)
|
|
174
|
+
reject(err);
|
|
175
|
+
else
|
|
176
|
+
resolve();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
async has(key) {
|
|
181
|
+
this.#assertOpen();
|
|
182
|
+
const db = this.#db;
|
|
183
|
+
return new Promise((resolve, reject) => {
|
|
184
|
+
db.get(`SELECT 1 as exists_flag FROM client_store WHERE key = ? LIMIT 1`, [key], (err, row) => {
|
|
185
|
+
if (err)
|
|
186
|
+
return reject(err);
|
|
187
|
+
resolve(row !== undefined);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
// ─── Private helpers ───────────────────────────────────────────────────────
|
|
192
|
+
#assertOpen() {
|
|
193
|
+
if (this.#db === null) {
|
|
194
|
+
throw new Error("SQLCipherClientStore: database is not open. Call open() first.");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/** Open the SQLite database file. Returns the Database instance on success. */
|
|
198
|
+
async #openDatabase(sqlite3Module) {
|
|
199
|
+
return new Promise((resolve, reject) => {
|
|
200
|
+
// OPEN_CREATE creates the file if absent (first run). On an existing corrupt file,
|
|
201
|
+
// SQLite opens it without error at the OS level; #verifyKey detects the corruption
|
|
202
|
+
// and throws without writing to the file — satisfying DB-001 (never overwrite a
|
|
203
|
+
// corrupt database).
|
|
204
|
+
const mode = sqlite3Module.OPEN_READWRITE | sqlite3Module.OPEN_CREATE;
|
|
205
|
+
const db = new sqlite3Module.Database(this.#dbPath, mode, (err) => {
|
|
206
|
+
if (err)
|
|
207
|
+
reject(err);
|
|
208
|
+
else
|
|
209
|
+
resolve(db);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Apply the SQLCipher PRAGMA key using the raw hex format.
|
|
215
|
+
* The key is passed as x'<hex>' which is the SQLCipher raw key format.
|
|
216
|
+
* SI-001: The key hex is used only in the PRAGMA string and never logged.
|
|
217
|
+
* SI-003: Only the `key` PRAGMA is set — no cipher_* weakening PRAGMAs.
|
|
218
|
+
*/
|
|
219
|
+
async #applyKey(db) {
|
|
220
|
+
const keyHex = Buffer.from(this.#dbKey).toString("hex");
|
|
221
|
+
const pragmaSql = `PRAGMA key = "x'${keyHex}'"`;
|
|
222
|
+
// SI-001: keyHex is used only in the PRAGMA string and is never logged.
|
|
223
|
+
// SI-003: only the `key` PRAGMA is issued — no cipher_* weakening PRAGMAs.
|
|
224
|
+
return new Promise((resolve, reject) => {
|
|
225
|
+
db.run(pragmaSql, [], function (err) {
|
|
226
|
+
if (err)
|
|
227
|
+
reject(err);
|
|
228
|
+
else
|
|
229
|
+
resolve();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Verify the PRAGMA key is correct by executing a test query.
|
|
235
|
+
* If the key is wrong or the file is corrupt, this will throw with
|
|
236
|
+
* an error like "SQLITE_NOTADB: file is not a database".
|
|
237
|
+
*
|
|
238
|
+
* DB-001: We do NOT catch and recover here — the error propagates to open()
|
|
239
|
+
* which logs client.store.open.failed and throws, halting the client.
|
|
240
|
+
*/
|
|
241
|
+
async #verifyKey(db) {
|
|
242
|
+
return new Promise((resolve, reject) => {
|
|
243
|
+
db.get(`SELECT count(*) as c FROM sqlite_master`, [], (err, _row) => {
|
|
244
|
+
if (err)
|
|
245
|
+
reject(err);
|
|
246
|
+
else
|
|
247
|
+
resolve();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Run the custom migration runner.
|
|
253
|
+
*
|
|
254
|
+
* Algorithm:
|
|
255
|
+
* 1. Ensure schema_migrations table exists.
|
|
256
|
+
* 2. Read migration files from migrationsPath matching V{n}__*.sql.
|
|
257
|
+
* 3. Sort by version number.
|
|
258
|
+
* 4. For each file not yet in schema_migrations, run it in a transaction.
|
|
259
|
+
* 5. Record the version in schema_migrations.
|
|
260
|
+
* 6. Log client.store.migration.applied for each applied migration.
|
|
261
|
+
*/
|
|
262
|
+
async #runMigrations(db) {
|
|
263
|
+
// Bootstrap the migrations table before any migration runs — this is not a migration
|
|
264
|
+
// itself and must not be inside a migration transaction.
|
|
265
|
+
// Step 1: bootstrap the schema_migrations table itself
|
|
266
|
+
await this.#execSql(db, `CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
267
|
+
version TEXT PRIMARY KEY,
|
|
268
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
269
|
+
)`);
|
|
270
|
+
// Step 2: read migration files
|
|
271
|
+
const migrationFiles = this.#readMigrationFiles();
|
|
272
|
+
// Step 3: find already-applied migrations
|
|
273
|
+
const appliedVersions = await this.#queryAppliedVersions(db);
|
|
274
|
+
// Step 4-6: apply pending migrations in order
|
|
275
|
+
for (const { filename, version, description } of migrationFiles) {
|
|
276
|
+
if (appliedVersions.has(version))
|
|
277
|
+
continue;
|
|
278
|
+
const sql = readFileSync(filename, "utf-8");
|
|
279
|
+
const startTime = Date.now();
|
|
280
|
+
try {
|
|
281
|
+
await this.#applyMigration(db, sql, version);
|
|
282
|
+
const executionTime = Date.now() - startTime;
|
|
283
|
+
this.#logger.info("client.store.migration.applied", {
|
|
284
|
+
version,
|
|
285
|
+
description,
|
|
286
|
+
executionTime,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
291
|
+
this.#logger.error("client.store.migration.failed", {
|
|
292
|
+
version,
|
|
293
|
+
description,
|
|
294
|
+
reason,
|
|
295
|
+
});
|
|
296
|
+
throw err;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/** Read and sort migration files from the migrations directory. */
|
|
301
|
+
#readMigrationFiles() {
|
|
302
|
+
let files;
|
|
303
|
+
try {
|
|
304
|
+
files = readdirSync(this.#migrationsPath);
|
|
305
|
+
}
|
|
306
|
+
catch (err) {
|
|
307
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
308
|
+
throw new Error(`Cannot read migrations directory '${this.#migrationsPath}': ${reason}`);
|
|
309
|
+
}
|
|
310
|
+
const migrations = files
|
|
311
|
+
.filter((f) => /^V\d+__.*\.sql$/.test(f))
|
|
312
|
+
.map((f) => {
|
|
313
|
+
const match = /^(V(\d+)__(.+))\.sql$/.exec(f);
|
|
314
|
+
if (!match)
|
|
315
|
+
throw new Error(`Unexpected migration filename: ${f}`);
|
|
316
|
+
return {
|
|
317
|
+
filename: resolve(this.#migrationsPath, f),
|
|
318
|
+
version: match[1], // e.g. "V1__client_schema"
|
|
319
|
+
versionNum: parseInt(match[2], 10),
|
|
320
|
+
description: match[3].replaceAll("_", " "), // e.g. "client schema"
|
|
321
|
+
};
|
|
322
|
+
})
|
|
323
|
+
.sort((a, b) => a.versionNum - b.versionNum);
|
|
324
|
+
return migrations;
|
|
325
|
+
}
|
|
326
|
+
/** Query the schema_migrations table and return the set of applied version strings. */
|
|
327
|
+
async #queryAppliedVersions(db) {
|
|
328
|
+
return new Promise((resolve, reject) => {
|
|
329
|
+
db.all(`SELECT version FROM schema_migrations`, [], (err, rows) => {
|
|
330
|
+
if (err)
|
|
331
|
+
return reject(err);
|
|
332
|
+
const versions = new Set((rows ?? []).map((r) => String(r.version)));
|
|
333
|
+
resolve(versions);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Apply a single migration SQL within a transaction and record it in schema_migrations.
|
|
339
|
+
*
|
|
340
|
+
* Implementation note: db.serialize() enqueues all db.run/db.exec calls synchronously
|
|
341
|
+
* before any callbacks fire, so error recovery inside serialize() is broken — the INSERT
|
|
342
|
+
* and COMMIT are already queued when a ROLLBACK fires. This method uses a linear await
|
|
343
|
+
* chain instead, which gives correct sequencing with real error propagation.
|
|
344
|
+
*/
|
|
345
|
+
async #applyMigration(db, sql, version) {
|
|
346
|
+
const run = (stmt, params = []) => new Promise((res, rej) => db.run(stmt, params, function (err) {
|
|
347
|
+
if (err)
|
|
348
|
+
rej(err);
|
|
349
|
+
else
|
|
350
|
+
res();
|
|
351
|
+
}));
|
|
352
|
+
const exec = (stmt) => new Promise((res, rej) => db.exec(stmt, (err) => {
|
|
353
|
+
if (err)
|
|
354
|
+
rej(err);
|
|
355
|
+
else
|
|
356
|
+
res();
|
|
357
|
+
}));
|
|
358
|
+
await run("BEGIN");
|
|
359
|
+
try {
|
|
360
|
+
await exec(sql);
|
|
361
|
+
await run(`INSERT OR IGNORE INTO schema_migrations (version, applied_at) VALUES (?, datetime('now'))`, [version]);
|
|
362
|
+
await run("COMMIT");
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
// Best-effort rollback — if ROLLBACK itself fails we still re-throw the original error.
|
|
366
|
+
await run("ROLLBACK").catch(() => { });
|
|
367
|
+
throw err;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/** Execute a SQL statement with no parameters. */
|
|
371
|
+
async #execSql(db, sql) {
|
|
372
|
+
return new Promise((resolve, reject) => {
|
|
373
|
+
db.exec(sql, (err) => {
|
|
374
|
+
if (err)
|
|
375
|
+
reject(err);
|
|
376
|
+
else
|
|
377
|
+
resolve();
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
//# sourceMappingURL=sqlcipher-client-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlcipher-client-store.js","sourceRoot":"","sources":["../src/sqlcipher-client-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAkDzC,gFAAgF;AAEhF;;;;;;;GAOG;AACH,MAAM,OAAO,oBAAoB;IACtB,MAAM,CAAa;IACnB,OAAO,CAAS;IAChB,QAAQ,CAAS;IACjB,IAAI,CAAS;IACb,eAAe,CAAS;IACxB,OAAO,CAAS;IAEzB,kCAAkC;IAClC,GAAG,GAA6B,IAAI,CAAC;IAErC;;;;OAIG;IACH,YAAY,KAAiB,EAAE,OAAoC;QACjE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;QAC9B,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,GAAG,IAAI,SAAS,CAAC;QACrC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;QAE9B,IAAI,OAAO,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;YACzC,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;QAChD,CAAC;aAAM,CAAC;YACN,gEAAgE;YAChE,kEAAkE;YAClE,+EAA+E;YAC/E,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1D,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,IAAI;QACR,6EAA6E;QAC7E,wCAAwC;QACxC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/C,IAAI,aAA8B,CAAC;QACnC,IAAI,CAAC;YACH,aAAa,GAAG,OAAO,CAAC,wBAAwB,CAAoB,CAAC;QACvE,CAAC;QAAC,OAAO,OAAgB,EAAE,CAAC;YAC1B,MAAM,MAAM,GAAG,OAAO,YAAY,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC5E,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,EAAE,MAAM,EAAE,wCAAwC,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YACrI,MAAM,IAAI,KAAK,CAAC,wCAAwC,MAAM,EAAE,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;QACnD,kFAAkF;QAElF,IAAI,CAAC;YACH,gFAAgF;YAChF,MAAM,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YAEzB,gEAAgE;YAChE,8DAA8D;YAC9D,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YAE1B,2EAA2E;YAC3E,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;YAEd,2DAA2D;YAC3D,MAAM,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;YAE9B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,qBAAqB,EAAE;gBACvC,GAAG,EAAE,IAAI,CAAC,IAAI;gBACd,MAAM,EAAE,IAAI,CAAC,OAAO;gBACpB,OAAO,EAAE,IAAI,CAAC,QAAQ;aACvB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAChE,sDAAsD;YACtD,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE;gBAC7C,MAAM;gBACN,OAAO,EAAE,IAAI,CAAC,QAAQ;aACvB,CAAC,CAAC;YACH,0CAA0C;YAC1C,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBAClC,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5B,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC;YAChB,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAED,oEAAoE;IACpE,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,GAAG,KAAK,IAAI;YAAE,OAAO;QAC9B,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC;QACpB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC;QAChB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBACf,IAAI,GAAG;oBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;oBAChB,OAAO,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAE9E,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAiB;QACtC,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,MAAM,EAAE,GAAG,IAAI,CAAC,GAAI,CAAC;QACrB,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC3C,EAAE,CAAC,GAAG,CACJ;;iGAEyF,EACzF,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EACzB,UAAU,GAAG;gBACX,IAAI,GAAG;oBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;oBAChB,OAAO,EAAE,CAAC;YACjB,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,MAAM,EAAE,GAAG,IAAI,CAAC,GAAI,CAAC;QACrB,OAAO,IAAI,OAAO,CAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC7D,EAAE,CAAC,GAAG,CACJ,8CAA8C,EAC9C,CAAC,GAAG,CAAC,EACL,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;gBACX,IAAI,GAAG;oBAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC5B,IAAI,GAAG,KAAK,SAAS;oBAAE,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC;gBACjD,8CAA8C;gBAC9C,0EAA0E;gBAC1E,4EAA4E;gBAC5E,6EAA6E;gBAC7E,8EAA8E;gBAC9E,MAAM,GAAG,GAAI,GAAsC,CAAC,KAAK,CAAC;gBAC1D,OAAO,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;YAC/B,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,MAAM,EAAE,GAAG,IAAI,CAAC,GAAI,CAAC;QACrB,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC3C,EAAE,CAAC,GAAG,CAAC,wCAAwC,EAAE,CAAC,GAAG,CAAC,EAAE,UAAU,GAAG;gBACnE,IAAI,GAAG;oBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;oBAChB,OAAO,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,MAAM,EAAE,GAAG,IAAI,CAAC,GAAI,CAAC;QACrB,OAAO,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC9C,EAAE,CAAC,GAAG,CACJ,iEAAiE,EACjE,CAAC,GAAG,CAAC,EACL,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;gBACX,IAAI,GAAG;oBAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC5B,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC;YAC7B,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAE9E,WAAW;QACT,IAAI,IAAI,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;QACpF,CAAC;IACH,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,aAAa,CAAC,aAA8B;QAChD,OAAO,IAAI,OAAO,CAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACxD,mFAAmF;YACnF,mFAAmF;YACnF,gFAAgF;YAChF,qBAAqB;YACrB,MAAM,IAAI,GAAG,aAAa,CAAC,cAAc,GAAG,aAAa,CAAC,WAAW,CAAC;YACtE,MAAM,EAAE,GAAG,IAAI,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE;gBAChE,IAAI,GAAG;oBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;oBAChB,OAAO,CAAC,EAAE,CAAC,CAAC;YACnB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,SAAS,CAAC,EAAqB;QACnC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACxD,MAAM,SAAS,GAAG,mBAAmB,MAAM,IAAI,CAAC;QAChD,wEAAwE;QACxE,2EAA2E;QAE3E,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC3C,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,EAAE,UAAU,GAAG;gBACjC,IAAI,GAAG;oBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;oBAChB,OAAO,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,UAAU,CAAC,EAAqB;QACpC,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC3C,EAAE,CAAC,GAAG,CAAC,yCAAyC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;gBAClE,IAAI,GAAG;oBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;oBAChB,OAAO,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,cAAc,CAAC,EAAqB;QACxC,qFAAqF;QACrF,yDAAyD;QACzD,uDAAuD;QACvD,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,EACpB;;;SAGG,CACJ,CAAC;QAEF,+BAA+B;QAC/B,MAAM,cAAc,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAElD,0CAA0C;QAC1C,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC;QAE7D,8CAA8C;QAC9C,KAAK,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,cAAc,EAAE,CAAC;YAChE,IAAI,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,SAAS;YAE3C,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAE7B,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,eAAe,CAAC,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;gBAC7C,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;gBAC7C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,gCAAgC,EAAE;oBAClD,OAAO;oBACP,WAAW;oBACX,aAAa;iBACd,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC;gBACtB,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAChE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE;oBAClD,OAAO;oBACP,WAAW;oBACX,MAAM;iBACP,CAAC,CAAC;gBACH,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC;IACH,CAAC;IAED,mEAAmE;IACnE,mBAAmB;QACjB,IAAI,KAAe,CAAC;QACpB,IAAI,CAAC;YACH,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAChE,MAAM,IAAI,KAAK,CAAC,qCAAqC,IAAI,CAAC,eAAe,MAAM,MAAM,EAAE,CAAC,CAAC;QAC3F,CAAC;QAED,MAAM,UAAU,GAAG,KAAK;aACrB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;aACxC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACT,MAAM,KAAK,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC9C,IAAI,CAAC,KAAK;gBAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,EAAE,CAAC,CAAC;YACnE,OAAO;gBACL,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC;gBAC1C,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAW,2BAA2B;gBACvD,UAAU,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;gBAClC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,uBAAuB;aACpE,CAAC;QACJ,CAAC,CAAC;aACD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC;QAE/C,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,uFAAuF;IACvF,KAAK,CAAC,qBAAqB,CAAC,EAAqB;QAC/C,OAAO,IAAI,OAAO,CAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAClD,EAAE,CAAC,GAAG,CAAC,uCAAuC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;gBAChE,IAAI,GAAG;oBAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC5B,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAE,CAAyB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBACtG,OAAO,CAAC,QAAQ,CAAC,CAAC;YACpB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,eAAe,CAAC,EAAqB,EAAE,GAAW,EAAE,OAAe;QACvE,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,SAAoB,EAAE,EAAiB,EAAE,CAClE,IAAI,OAAO,CAAO,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAC7B,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,GAAG;YAChC,IAAI,GAAG;gBAAE,GAAG,CAAC,GAAG,CAAC,CAAC;;gBACb,GAAG,EAAE,CAAC;QACb,CAAC,CAAC,CACH,CAAC;QACJ,MAAM,IAAI,GAAG,CAAC,IAAY,EAAiB,EAAE,CAC3C,IAAI,OAAO,CAAO,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAC7B,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE;YACpB,IAAI,GAAG;gBAAE,GAAG,CAAC,GAAG,CAAC,CAAC;;gBACb,GAAG,EAAE,CAAC;QACb,CAAC,CAAC,CACH,CAAC;QAEJ,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;YAChB,MAAM,GAAG,CACP,2FAA2F,EAC3F,CAAC,OAAO,CAAC,CACV,CAAC;YACF,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC;QACtB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,wFAAwF;YACxF,MAAM,GAAG,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACtC,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAED,kDAAkD;IAClD,KAAK,CAAC,QAAQ,CAAC,EAAqB,EAAE,GAAW;QAC/C,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC3C,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE;gBACnB,IAAI,GAAG;oBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;oBAChB,OAAO,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|