@party-stack/node-runtime 0.3.2 → 0.4.0
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/README.md +32 -0
- package/lib/NodeFileSystemBlobBytesStore.d.ts +7 -6
- package/lib/NodeFileSystemBlobBytesStore.d.ts.map +1 -1
- package/lib/NodeFileSystemBlobBytesStore.js +23 -8
- package/lib/NodeFileSystemBlobBytesStore.js.map +1 -1
- package/lib/NodeKeyMaterialStore.d.ts +14 -0
- package/lib/NodeKeyMaterialStore.d.ts.map +1 -0
- package/lib/NodeKeyMaterialStore.js +29 -0
- package/lib/NodeKeyMaterialStore.js.map +1 -0
- package/lib/createNodeBrowserAuthentication.d.ts.map +1 -1
- package/lib/createNodeBrowserAuthentication.js +102 -79
- package/lib/createNodeBrowserAuthentication.js.map +1 -1
- package/lib/createNodeRuntime.d.ts +5 -4
- package/lib/createNodeRuntime.d.ts.map +1 -1
- package/lib/createNodeRuntime.js +70 -31
- package/lib/createNodeRuntime.js.map +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.js +1 -1
- package/package.json +8 -7
- package/lib/NodeSecretStore.d.ts +0 -14
- package/lib/NodeSecretStore.d.ts.map +0 -1
- package/lib/NodeSecretStore.js +0 -46
- package/lib/NodeSecretStore.js.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Node runtime
|
|
2
|
+
|
|
3
|
+
`make`, `layer`, and `createNodeRuntime(owner, namespace)` acquire a scoped Effect
|
|
4
|
+
`Runtime` backed by SQLite, filesystem bytes, browser authentication, and the native
|
|
5
|
+
OS keyring. `createNodeRuntimeWithOptions({ dataDirectory })` returns the same
|
|
6
|
+
Effect-based provider with a configured data directory.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import * as NodeRuntime from "@party-stack/node-runtime";
|
|
10
|
+
import { Runtime } from "@party-stack/runtime";
|
|
11
|
+
import { Effect } from "effect";
|
|
12
|
+
|
|
13
|
+
const program = Effect.gen(function* () {
|
|
14
|
+
const runtime = yield* Runtime;
|
|
15
|
+
yield* runtime.bytes.write("example", new Blob(["hello"]));
|
|
16
|
+
yield* Effect.never;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
Effect.runFork(program.pipe(Effect.provide(NodeRuntime.layer({ owner: "user", namespace: "app" }))));
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Defining a Layer opens nothing. Building it opens SQLite; completion, failure or
|
|
23
|
+
interruption closes the database. Platform runtimes have no Promise factory or
|
|
24
|
+
manual cleanup method. An application's outer Effect or ManagedRuntime owns them.
|
|
25
|
+
Installations accept these Effect-based providers directly.
|
|
26
|
+
|
|
27
|
+
Native credentials and device encryption keys use `@party-stack/effect-node-keyring`.
|
|
28
|
+
The native module is loaded lazily on first credential/key access. Keys stay outside
|
|
29
|
+
SQLite, and cached decrypted property values remain encrypted on device. The native
|
|
30
|
+
keyring must be available; there is no CLI, plaintext-file or volatile fallback.
|
|
31
|
+
No biometric/session-unlock policy is implied. See the keyring package for Linux
|
|
32
|
+
Secret Service requirements.
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BlobBytesService, ByteInput, ByteSource } from "@party-stack/runtime";
|
|
2
|
+
import { Effect } from "effect";
|
|
2
3
|
//#region src/NodeFileSystemBlobBytesStore.d.ts
|
|
3
4
|
export interface NodeFileSystemBlobBytesStoreOptions {
|
|
4
5
|
directory: string;
|
|
5
6
|
}
|
|
6
|
-
export declare class NodeFileSystemBlobBytesStore implements
|
|
7
|
+
export declare class NodeFileSystemBlobBytesStore implements BlobBytesService {
|
|
7
8
|
#private;
|
|
9
|
+
readonly write: (id: string, value: ByteInput) => Effect.Effect<void, unknown, never>;
|
|
10
|
+
readonly read: (id: string) => Effect.Effect<ByteSource | undefined, import("effect/Cause").UnknownError, never>;
|
|
11
|
+
readonly delete: (id: string) => Effect.Effect<void, import("effect/Cause").UnknownError, never>;
|
|
12
|
+
readonly clear: () => Effect.Effect<void, import("effect/Cause").UnknownError, never>;
|
|
8
13
|
constructor(options: NodeFileSystemBlobBytesStoreOptions);
|
|
9
|
-
write(id: string, blob: Blob): Promise<void>;
|
|
10
|
-
read(id: string): Promise<Blob>;
|
|
11
|
-
delete(id: string): Promise<void>;
|
|
12
|
-
clear(): Promise<void>;
|
|
13
14
|
}
|
|
14
15
|
//#endregion
|
|
15
16
|
//# sourceMappingURL=NodeFileSystemBlobBytesStore.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeFileSystemBlobBytesStore.d.ts","names":[],"sources":["../src/NodeFileSystemBlobBytesStore.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"NodeFileSystemBlobBytesStore.d.ts","names":[],"sources":["../src/NodeFileSystemBlobBytesStore.ts"],"mappings":";;;iBAeiB;EACb;;qBAOS,wCAAwC;;WAGxC,QAAK,YAAc,OAAS,cAAS,OAAA;WACrC,OAAI,eAAc,OAAA,OAAA,+CAAA;WAIlB,SAAM,eAAc,OAAA,oCAAA;WACpB,aAAK,OAAA,oCAAA;EAEd,YAAY,SAAS"}
|
|
@@ -1,38 +1,53 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { createReadStream, createWriteStream } from "node:fs";
|
|
3
|
+
import { mkdir, rename, rm, stat } from "node:fs/promises";
|
|
3
4
|
import { join } from "node:path";
|
|
5
|
+
import { Readable } from "node:stream";
|
|
6
|
+
import { pipeline } from "node:stream/promises";
|
|
7
|
+
import { byteIO, byteSource } from "@party-stack/runtime";
|
|
8
|
+
import { Effect, Stream } from "effect";
|
|
4
9
|
//#region src/NodeFileSystemBlobBytesStore.ts
|
|
5
10
|
function fileName(id) {
|
|
6
11
|
return createHash("sha256").update(id).digest("hex");
|
|
7
12
|
}
|
|
8
13
|
var NodeFileSystemBlobBytesStore = class {
|
|
9
14
|
#directory;
|
|
15
|
+
write = (id, value) => byteIO((signal) => this.#write(id, value, signal));
|
|
16
|
+
read = (id) => Effect.tryPromise(() => this.#read(id)).pipe(Effect.map((value) => value === void 0 ? void 0 : byteSource(value)));
|
|
17
|
+
delete = (id) => Effect.tryPromise(() => this.#delete(id));
|
|
18
|
+
clear = () => Effect.tryPromise(() => this.#clear());
|
|
10
19
|
constructor(options) {
|
|
11
20
|
this.#directory = options.directory;
|
|
12
21
|
}
|
|
13
|
-
async write(id,
|
|
22
|
+
async #write(id, input, signal) {
|
|
14
23
|
await mkdir(this.#directory, { recursive: true });
|
|
15
24
|
const target = this.#path(id);
|
|
16
25
|
const temporary = join(this.#directory, `.${fileName(id)}.${randomUUID()}.tmp`);
|
|
17
26
|
try {
|
|
18
|
-
await
|
|
27
|
+
await pipeline(Readable.from(Stream.toAsyncIterable(byteSource(input).stream)), createWriteStream(temporary), { signal });
|
|
28
|
+
signal?.throwIfAborted();
|
|
19
29
|
await rename(temporary, target);
|
|
20
30
|
} finally {
|
|
21
31
|
await rm(temporary, { force: true });
|
|
22
32
|
}
|
|
23
33
|
}
|
|
24
|
-
async read(id) {
|
|
34
|
+
async #read(id) {
|
|
25
35
|
try {
|
|
26
|
-
|
|
36
|
+
const path = this.#path(id);
|
|
37
|
+
return {
|
|
38
|
+
size: (await stat(path)).size,
|
|
39
|
+
type: "application/octet-stream",
|
|
40
|
+
stream: Stream.suspend(() => Stream.fromAsyncIterable(createReadStream(path), (cause) => cause))
|
|
41
|
+
};
|
|
27
42
|
} catch (error) {
|
|
28
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
43
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return;
|
|
29
44
|
throw error;
|
|
30
45
|
}
|
|
31
46
|
}
|
|
32
|
-
async delete(id) {
|
|
47
|
+
async #delete(id) {
|
|
33
48
|
await rm(this.#path(id), { force: true });
|
|
34
49
|
}
|
|
35
|
-
async clear() {
|
|
50
|
+
async #clear() {
|
|
36
51
|
await rm(this.#directory, {
|
|
37
52
|
recursive: true,
|
|
38
53
|
force: true
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeFileSystemBlobBytesStore.js","names":[],"sources":["../src/NodeFileSystemBlobBytesStore.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { mkdir,
|
|
1
|
+
{"version":3,"file":"NodeFileSystemBlobBytesStore.js","names":[],"sources":["../src/NodeFileSystemBlobBytesStore.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { createReadStream, createWriteStream } from \"node:fs\";\nimport { mkdir, stat, rename, rm } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport {\n byteSource,\n type ByteInput,\n type ByteSource,\n byteIO,\n type BlobBytesService,\n} from \"@party-stack/runtime\";\nimport { Effect, Stream } from \"effect\";\n\nexport interface NodeFileSystemBlobBytesStoreOptions {\n directory: string;\n}\n\nfunction fileName(id: string): string {\n return createHash(\"sha256\").update(id).digest(\"hex\");\n}\n\nexport class NodeFileSystemBlobBytesStore implements BlobBytesService {\n readonly #directory: string;\n\n readonly write = (id: string, value: ByteInput) => byteIO((signal) => this.#write(id, value, signal));\n readonly read = (id: string) =>\n Effect.tryPromise(() => this.#read(id)).pipe(\n Effect.map((value) => (value === undefined ? undefined : byteSource(value)))\n );\n readonly delete = (id: string) => Effect.tryPromise(() => this.#delete(id));\n readonly clear = () => Effect.tryPromise(() => this.#clear());\n\n constructor(options: NodeFileSystemBlobBytesStoreOptions) {\n this.#directory = options.directory;\n }\n\n async #write(id: string, input: ByteInput, signal?: AbortSignal): Promise<void> {\n await mkdir(this.#directory, { recursive: true });\n const target = this.#path(id);\n const temporary = join(this.#directory, `.${fileName(id)}.${randomUUID()}.tmp`);\n try {\n await pipeline(\n Readable.from(Stream.toAsyncIterable(byteSource(input).stream)),\n createWriteStream(temporary),\n { signal }\n );\n signal?.throwIfAborted();\n await rename(temporary, target);\n } finally {\n await rm(temporary, { force: true });\n }\n }\n\n async #read(id: string): Promise<ByteSource | undefined> {\n try {\n const path = this.#path(id);\n const info = await stat(path);\n return {\n size: info.size,\n type: \"application/octet-stream\",\n stream: Stream.suspend(() =>\n Stream.fromAsyncIterable(createReadStream(path), (cause) => cause)\n ),\n };\n } catch (error) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n return undefined;\n }\n throw error;\n }\n }\n\n async #delete(id: string): Promise<void> {\n await rm(this.#path(id), { force: true });\n }\n\n async #clear(): Promise<void> {\n await rm(this.#directory, { recursive: true, force: true });\n }\n\n #path(id: string): string {\n return join(this.#directory, fileName(id));\n }\n}\n"],"mappings":";;;;;;;;;AAmBA,SAAS,SAAS,IAAoB;CAClC,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,KAAK;AACvD;AAEA,IAAa,+BAAb,MAAsE;CAClE;CAEA,SAAkB,IAAY,UAAqB,QAAQ,WAAW,KAAK,OAAO,IAAI,OAAO,MAAM,CAAC;CACpG,QAAiB,OACb,OAAO,iBAAiB,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,KACpC,OAAO,KAAK,UAAW,UAAU,KAAA,IAAY,KAAA,IAAY,WAAW,KAAK,CAAE,CAC/E;CACJ,UAAmB,OAAe,OAAO,iBAAiB,KAAK,QAAQ,EAAE,CAAC;CAC1E,cAAuB,OAAO,iBAAiB,KAAK,OAAO,CAAC;CAE5D,YAAY,SAA8C;EACtD,KAAK,aAAa,QAAQ;CAC9B;CAEA,MAAM,OAAO,IAAY,OAAkB,QAAqC;EAC5E,MAAM,MAAM,KAAK,YAAY,EAAE,WAAW,KAAK,CAAC;EAChD,MAAM,SAAS,KAAK,MAAM,EAAE;EAC5B,MAAM,YAAY,KAAK,KAAK,YAAY,IAAI,SAAS,EAAE,EAAE,GAAG,WAAW,EAAE,KAAK;EAC9E,IAAI;GACA,MAAM,SACF,SAAS,KAAK,OAAO,gBAAgB,WAAW,KAAK,CAAC,CAAC,MAAM,CAAC,GAC9D,kBAAkB,SAAS,GAC3B,EAAE,OAAO,CACb;GACA,QAAQ,eAAe;GACvB,MAAM,OAAO,WAAW,MAAM;EAClC,UAAU;GACN,MAAM,GAAG,WAAW,EAAE,OAAO,KAAK,CAAC;EACvC;CACJ;CAEA,MAAM,MAAM,IAA6C;EACrD,IAAI;GACA,MAAM,OAAO,KAAK,MAAM,EAAE;GAE1B,OAAO;IACH,OAAM,MAFS,KAAK,IAAI,EAAA,CAEb;IACX,MAAM;IACN,QAAQ,OAAO,cACX,OAAO,kBAAkB,iBAAiB,IAAI,IAAI,UAAU,KAAK,CACrE;GACJ;EACJ,SAAS,OAAO;GACZ,IAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAC5D;GAEJ,MAAM;EACV;CACJ;CAEA,MAAM,QAAQ,IAA2B;EACrC,MAAM,GAAG,KAAK,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,CAAC;CAC5C;CAEA,MAAM,SAAwB;EAC1B,MAAM,GAAG,KAAK,YAAY;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;CAC9D;CAEA,MAAM,IAAoB;EACtB,OAAO,KAAK,KAAK,YAAY,SAAS,EAAE,CAAC;CAC7C;AACJ"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { RuntimeError } from "@party-stack/runtime";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
//#region src/NodeKeyMaterialStore.d.ts
|
|
4
|
+
export interface NodeKeyMaterialStoreOptions {
|
|
5
|
+
service: string;
|
|
6
|
+
}
|
|
7
|
+
/** The native service is loaded on first use, once per scoped runtime. */
|
|
8
|
+
export declare const makeKeyMaterialStore: (options: NodeKeyMaterialStoreOptions) => Effect.Effect<{
|
|
9
|
+
get: (key: string) => Effect.Effect<string | undefined, RuntimeError, never>;
|
|
10
|
+
set: (key: string, value: string) => Effect.Effect<void, RuntimeError, never>;
|
|
11
|
+
delete: (key: string) => Effect.Effect<void, RuntimeError, never>;
|
|
12
|
+
}, never, never>;
|
|
13
|
+
//#endregion
|
|
14
|
+
//# sourceMappingURL=NodeKeyMaterialStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeKeyMaterialStore.d.ts","names":[],"sources":["../src/NodeKeyMaterialStore.ts"],"mappings":";;;iBAKiB;EACb;;;qBAOS,uBAAoB,SAAA,gCAAA,OAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { RuntimeError } from "@party-stack/runtime";
|
|
3
|
+
import { Effect, Redacted } from "effect";
|
|
4
|
+
import { make } from "@party-stack/effect-node-keyring";
|
|
5
|
+
//#region src/NodeKeyMaterialStore.ts
|
|
6
|
+
function encodeIdentifier(value) {
|
|
7
|
+
return `party-stack.${createHash("sha256").update(value).digest("hex")}`;
|
|
8
|
+
}
|
|
9
|
+
/** The native service is loaded on first use, once per scoped runtime. */
|
|
10
|
+
const makeKeyMaterialStore = Effect.fnUntraced(function* (options) {
|
|
11
|
+
const service = encodeIdentifier(options.service);
|
|
12
|
+
const keyring = yield* Effect.cached(make.pipe(Effect.mapError(() => new RuntimeError({
|
|
13
|
+
operation: "secrets.open",
|
|
14
|
+
cause: /* @__PURE__ */ new Error("Native keyring unavailable.")
|
|
15
|
+
}))));
|
|
16
|
+
const operation = (effect) => effect.pipe(Effect.mapError(() => new RuntimeError({
|
|
17
|
+
operation: "secrets",
|
|
18
|
+
cause: /* @__PURE__ */ new Error("Native credential operation failed.")
|
|
19
|
+
})));
|
|
20
|
+
return {
|
|
21
|
+
get: (key) => operation(Effect.flatMap(keyring, (native) => native.getPassword(service, encodeIdentifier(key)))).pipe(Effect.map((value) => value === void 0 ? void 0 : Redacted.value(value))),
|
|
22
|
+
set: (key, value) => operation(Effect.flatMap(keyring, (native) => native.setPassword(service, encodeIdentifier(key), Redacted.make(value)))),
|
|
23
|
+
delete: (key) => operation(Effect.flatMap(keyring, (native) => native.deleteCredential(service, encodeIdentifier(key)))).pipe(Effect.asVoid)
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
//#endregion
|
|
27
|
+
export { makeKeyMaterialStore };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=NodeKeyMaterialStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeKeyMaterialStore.js","names":["makeNodeKeyring"],"sources":["../src/NodeKeyMaterialStore.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { make as makeNodeKeyring } from \"@party-stack/effect-node-keyring\";\nimport { RuntimeError, type KeyMaterialStore } from \"@party-stack/runtime\";\nimport { Effect, Redacted } from \"effect\";\n\nexport interface NodeKeyMaterialStoreOptions {\n service: string;\n}\nfunction encodeIdentifier(value: string): string {\n return `party-stack.${createHash(\"sha256\").update(value).digest(\"hex\")}`;\n}\n\n/** The native service is loaded on first use, once per scoped runtime. */\nexport const makeKeyMaterialStore = Effect.fnUntraced(function* (options: NodeKeyMaterialStoreOptions) {\n const service = encodeIdentifier(options.service);\n const keyring = yield* Effect.cached(\n makeNodeKeyring.pipe(\n Effect.mapError(\n () =>\n new RuntimeError({\n operation: \"secrets.open\",\n cause: new Error(\"Native keyring unavailable.\"),\n })\n )\n )\n );\n const operation = <A>(effect: Effect.Effect<A, unknown>) =>\n effect.pipe(\n Effect.mapError(\n () =>\n new RuntimeError({\n operation: \"secrets\",\n cause: new Error(\"Native credential operation failed.\"),\n })\n )\n );\n return {\n get: (key) =>\n operation(\n Effect.flatMap(keyring, (native) => native.getPassword(service, encodeIdentifier(key)))\n ).pipe(Effect.map((value) => (value === undefined ? undefined : Redacted.value(value)))),\n set: (key, value) =>\n operation(\n Effect.flatMap(keyring, (native) =>\n native.setPassword(service, encodeIdentifier(key), Redacted.make(value))\n )\n ),\n delete: (key) =>\n operation(\n Effect.flatMap(keyring, (native) => native.deleteCredential(service, encodeIdentifier(key)))\n ).pipe(Effect.asVoid),\n } satisfies KeyMaterialStore;\n});\n"],"mappings":";;;;;AAQA,SAAS,iBAAiB,OAAuB;CAC7C,OAAO,eAAe,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK;AACzE;;AAGA,MAAa,uBAAuB,OAAO,WAAW,WAAW,SAAsC;CACnG,MAAM,UAAU,iBAAiB,QAAQ,OAAO;CAChD,MAAM,UAAU,OAAO,OAAO,OAC1BA,KAAgB,KACZ,OAAO,eAEC,IAAI,aAAa;EACb,WAAW;EACX,uBAAO,IAAI,MAAM,6BAA6B;CAClD,CAAC,CACT,CACJ,CACJ;CACA,MAAM,aAAgB,WAClB,OAAO,KACH,OAAO,eAEC,IAAI,aAAa;EACb,WAAW;EACX,uBAAO,IAAI,MAAM,qCAAqC;CAC1D,CAAC,CACT,CACJ;CACJ,OAAO;EACH,MAAM,QACF,UACI,OAAO,QAAQ,UAAU,WAAW,OAAO,YAAY,SAAS,iBAAiB,GAAG,CAAC,CAAC,CAC1F,CAAC,CAAC,KAAK,OAAO,KAAK,UAAW,UAAU,KAAA,IAAY,KAAA,IAAY,SAAS,MAAM,KAAK,CAAE,CAAC;EAC3F,MAAM,KAAK,UACP,UACI,OAAO,QAAQ,UAAU,WACrB,OAAO,YAAY,SAAS,iBAAiB,GAAG,GAAG,SAAS,KAAK,KAAK,CAAC,CAC3E,CACJ;EACJ,SAAS,QACL,UACI,OAAO,QAAQ,UAAU,WAAW,OAAO,iBAAiB,SAAS,iBAAiB,GAAG,CAAC,CAAC,CAC/F,CAAC,CAAC,KAAK,OAAO,MAAM;CAC5B;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createNodeBrowserAuthentication.d.ts","names":[],"sources":["../src/createNodeBrowserAuthentication.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"createNodeBrowserAuthentication.d.ts","names":[],"sources":["../src/createNodeBrowserAuthentication.ts"],"mappings":";;iBAQiB;EACb,WAAW,gBAAgB;EAC3B;;wBAGY,gCACZ,UAAS,yCACV"}
|
|
@@ -1,105 +1,128 @@
|
|
|
1
|
+
import { BrowserAuthenticationError } from "@party-stack/runtime";
|
|
2
|
+
import { Effect } from "effect";
|
|
1
3
|
import { serve } from "@hono/node-server";
|
|
2
|
-
import { BrowserAuthenticationCancelledError } from "@party-stack/runtime";
|
|
3
4
|
import { Hono } from "hono";
|
|
4
5
|
import open from "open";
|
|
5
6
|
//#region src/createNodeBrowserAuthentication.ts
|
|
6
|
-
const DEFAULT_TIMEOUT_MS = 3e5;
|
|
7
7
|
const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
|
|
8
8
|
"localhost",
|
|
9
9
|
"127.0.0.1",
|
|
10
10
|
"::1",
|
|
11
11
|
"[::1]"
|
|
12
12
|
]);
|
|
13
|
-
function closeServer(server) {
|
|
14
|
-
if (!server?.listening) return Promise.resolve();
|
|
15
|
-
return new Promise((resolve, reject) => {
|
|
16
|
-
server.close((error) => {
|
|
17
|
-
if (error) reject(error);
|
|
18
|
-
else resolve();
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
function validateRedirectUrl(redirectUrl) {
|
|
23
|
-
const url = new URL(redirectUrl);
|
|
24
|
-
if (url.protocol !== "http:" || !LOOPBACK_HOSTS.has(url.hostname) || !url.port) throw new Error("Node browser authentication requires an http:// loopback redirect URL with an explicit port.");
|
|
25
|
-
return url;
|
|
26
|
-
}
|
|
27
13
|
function createNodeBrowserAuthentication(options = {}) {
|
|
28
14
|
const openUrl = options.openUrl ?? ((url) => open(url));
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
15
|
+
return { start: Effect.fnUntraced(function* ({ redirectUrl }) {
|
|
16
|
+
const redirect = yield* Effect.try({
|
|
17
|
+
try: () => {
|
|
18
|
+
const url = new URL(redirectUrl);
|
|
19
|
+
if (url.protocol !== "http:" || !LOOPBACK_HOSTS.has(url.hostname) || !url.port) throw new Error("Node browser authentication requires an http:// loopback redirect URL with an explicit port.");
|
|
20
|
+
return url;
|
|
21
|
+
},
|
|
22
|
+
catch: (cause) => new BrowserAuthenticationError({
|
|
23
|
+
reason: "Configuration",
|
|
24
|
+
message: cause instanceof Error ? cause.message : "Invalid redirect URL.",
|
|
25
|
+
cause
|
|
26
|
+
})
|
|
27
|
+
});
|
|
32
28
|
let server;
|
|
33
|
-
let
|
|
34
|
-
let
|
|
35
|
-
let
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
29
|
+
let closing;
|
|
30
|
+
let listenFailed = false;
|
|
31
|
+
let cancel;
|
|
32
|
+
let used = false;
|
|
33
|
+
let closed = false;
|
|
34
|
+
const closeServer = () => closing ??= new Promise((resolve, reject) => {
|
|
35
|
+
if (!server || listenFailed) {
|
|
36
|
+
resolve();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const active = server;
|
|
40
|
+
const stop = () => {
|
|
41
|
+
active.removeListener("error", failed);
|
|
42
|
+
active.close((error) => error ? reject(error) : resolve());
|
|
43
|
+
active.closeAllConnections();
|
|
44
|
+
};
|
|
45
|
+
const failed = () => {
|
|
46
|
+
active.removeListener("listening", stop);
|
|
47
|
+
resolve();
|
|
48
|
+
};
|
|
49
|
+
if (active.listening) stop();
|
|
50
|
+
else {
|
|
51
|
+
active.once("listening", stop);
|
|
52
|
+
active.once("error", failed);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => {
|
|
56
|
+
closed = true;
|
|
57
|
+
cancel?.();
|
|
58
|
+
}).pipe(Effect.andThen(Effect.promise(closeServer))));
|
|
59
|
+
return { open: (authorizationUrl) => Effect.suspend(() => {
|
|
60
|
+
if (used || closed) return Effect.fail(new BrowserAuthenticationError({
|
|
61
|
+
reason: "Configuration",
|
|
62
|
+
message: "This authentication session has already been used or closed."
|
|
63
|
+
}));
|
|
64
|
+
used = true;
|
|
65
|
+
return Effect.callback((resume) => {
|
|
66
|
+
let settled = false;
|
|
67
|
+
const finish = (result) => {
|
|
68
|
+
if (settled) return;
|
|
69
|
+
settled = true;
|
|
70
|
+
cancel = void 0;
|
|
71
|
+
resume(result);
|
|
72
|
+
};
|
|
73
|
+
cancel = () => finish(Effect.fail(new BrowserAuthenticationError({
|
|
74
|
+
reason: "Cancelled",
|
|
75
|
+
message: "Browser authentication was cancelled."
|
|
76
|
+
})));
|
|
77
|
+
const app = new Hono();
|
|
78
|
+
app.get(redirect.pathname, (context) => {
|
|
79
|
+
if (settled) return context.text("Authentication session is already complete.", 409);
|
|
80
|
+
const callbackUrl = context.req.url;
|
|
81
|
+
setImmediate(() => finish(Effect.succeed({ callbackUrl })));
|
|
82
|
+
return context.html("<!doctype html><html lang=\"en\"><head><title>Authentication complete</title></head><body><p>Authentication complete. Returning to the terminal…</p><script>window.close()<\/script></body></html>");
|
|
83
|
+
});
|
|
84
|
+
try {
|
|
65
85
|
server = serve({
|
|
66
86
|
fetch: app.fetch,
|
|
67
87
|
hostname: redirect.hostname.replace(/^\[|\]$/g, ""),
|
|
68
88
|
port: Number(redirect.port)
|
|
69
89
|
});
|
|
70
|
-
server.once("error", (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
90
|
+
server.once("error", (cause) => {
|
|
91
|
+
listenFailed = true;
|
|
92
|
+
finish(Effect.fail(new BrowserAuthenticationError({
|
|
93
|
+
reason: "Platform",
|
|
94
|
+
message: "Could not start the authentication callback server.",
|
|
95
|
+
cause
|
|
96
|
+
})));
|
|
74
97
|
});
|
|
75
98
|
server.once("listening", () => {
|
|
76
99
|
if (settled) return;
|
|
77
|
-
Promise.resolve().then(() => openUrl(authorizationUrl)).catch((
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
});
|
|
100
|
+
Promise.resolve().then(() => openUrl(authorizationUrl)).catch((cause) => finish(Effect.fail(new BrowserAuthenticationError({
|
|
101
|
+
reason: "Platform",
|
|
102
|
+
message: "Could not open browser authorization.",
|
|
103
|
+
cause
|
|
104
|
+
}))));
|
|
83
105
|
});
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
},
|
|
91
|
-
async close() {
|
|
92
|
-
if (settled) {
|
|
93
|
-
await finish();
|
|
94
|
-
return;
|
|
106
|
+
} catch (cause) {
|
|
107
|
+
finish(Effect.fail(new BrowserAuthenticationError({
|
|
108
|
+
reason: "Platform",
|
|
109
|
+
message: "Could not start browser authentication.",
|
|
110
|
+
cause
|
|
111
|
+
})));
|
|
95
112
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
113
|
+
return Effect.sync(() => {
|
|
114
|
+
settled = true;
|
|
115
|
+
cancel = void 0;
|
|
116
|
+
}).pipe(Effect.andThen(Effect.promise(closeServer)));
|
|
117
|
+
}).pipe(Effect.timeoutOrElse({
|
|
118
|
+
duration: options.timeoutMs ?? 3e5,
|
|
119
|
+
orElse: () => Effect.fail(new BrowserAuthenticationError({
|
|
120
|
+
reason: "Cancelled",
|
|
121
|
+
message: "Browser authentication timed out."
|
|
122
|
+
}))
|
|
123
|
+
}));
|
|
124
|
+
}) };
|
|
125
|
+
}) };
|
|
103
126
|
}
|
|
104
127
|
//#endregion
|
|
105
128
|
export { createNodeBrowserAuthentication };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createNodeBrowserAuthentication.js","names":[],"sources":["../src/createNodeBrowserAuthentication.ts"],"sourcesContent":["import { serve } from \"@hono/node-server\";\nimport { BrowserAuthenticationCancelledError, type BrowserAuthentication } from \"@party-stack/runtime\";\nimport { Hono } from \"hono\";\nimport open from \"open\";\nimport type { Server } from \"node:http\";\n\nconst DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;\nconst LOOPBACK_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\"]);\n\nexport interface CreateNodeBrowserAuthenticationOptions {\n openUrl?: (url: string) => Promise<unknown>;\n timeoutMs?: number;\n}\n\nfunction closeServer(server: Server | undefined): Promise<void> {\n if (!server?.listening) return Promise.resolve();\n return new Promise((resolve, reject) => {\n server.close((error) => {\n if (error) reject(error);\n else resolve();\n });\n });\n}\n\nfunction validateRedirectUrl(redirectUrl: string): URL {\n const url = new URL(redirectUrl);\n if (url.protocol !== \"http:\" || !LOOPBACK_HOSTS.has(url.hostname) || !url.port) {\n throw new Error(\n \"Node browser authentication requires an http:// loopback redirect URL with an explicit port.\"\n );\n }\n return url;\n}\n\nexport function createNodeBrowserAuthentication(\n options: CreateNodeBrowserAuthenticationOptions = {}\n): BrowserAuthentication {\n const openUrl = options.openUrl ?? ((url: string) => open(url));\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n return {\n start({ redirectUrl }) {\n const redirect = validateRedirectUrl(redirectUrl);\n let server: Server | undefined;\n let rejectPending: ((error: Error) => void) | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let settled = false;\n\n const finish = async () => {\n if (timer) clearTimeout(timer);\n timer = undefined;\n rejectPending = undefined;\n await closeServer(server);\n server = undefined;\n };\n\n return {\n open(authorizationUrl) {\n if (server || settled) {\n return Promise.reject(\n new Error(\"This browser authentication session has already been used.\")\n );\n }\n\n return new Promise<{ callbackUrl: string }>((resolve, reject) => {\n rejectPending = reject;\n const app = new Hono();\n app.get(redirect.pathname, (context) => {\n if (settled) {\n return context.text(\"Authentication session is already complete.\", 409);\n }\n settled = true;\n const callbackUrl = context.req.url;\n setImmediate(() => {\n void finish().then(\n () =>\n resolve({\n callbackUrl,\n }),\n reject\n );\n });\n return context.html(`<!doctype html>\n<html lang=\"en\">\n<head><meta charset=\"utf-8\"><title>Authentication complete</title></head>\n<body>\n<p>Authentication complete. Returning to the terminal…</p>\n<script>window.close()</script>\n</body>\n</html>`);\n });\n\n server = serve({\n fetch: app.fetch,\n hostname: redirect.hostname.replace(/^\\[|\\]$/g, \"\"),\n port: Number(redirect.port),\n }) as Server;\n server.once(\"error\", (error) => {\n if (settled) return;\n settled = true;\n void finish().finally(() => reject(error));\n });\n server.once(\"listening\", () => {\n if (settled) return;\n void Promise.resolve()\n .then(() => openUrl(authorizationUrl))\n .catch((error: unknown) => {\n if (settled) return;\n settled = true;\n const reason = error instanceof Error ? error : new Error(String(error));\n void finish().finally(() => reject(reason));\n });\n });\n timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n void finish().finally(() =>\n reject(\n new BrowserAuthenticationCancelledError(\n \"Browser authentication timed out.\"\n )\n )\n );\n }, timeoutMs);\n });\n },\n async close() {\n if (settled) {\n await finish();\n return;\n }\n settled = true;\n const reject = rejectPending;\n await finish();\n reject?.(new BrowserAuthenticationCancelledError());\n },\n };\n },\n };\n}\n"],"mappings":";;;;;AAMA,MAAM,qBAAqB;AAC3B,MAAM,iCAAiB,IAAI,IAAI;CAAC;CAAa;CAAa;CAAO;AAAO,CAAC;AAOzE,SAAS,YAAY,QAA2C;CAC5D,IAAI,CAAC,QAAQ,WAAW,OAAO,QAAQ,QAAQ;CAC/C,OAAO,IAAI,SAAS,SAAS,WAAW;EACpC,OAAO,OAAO,UAAU;GACpB,IAAI,OAAO,OAAO,KAAK;QAClB,QAAQ;EACjB,CAAC;CACL,CAAC;AACL;AAEA,SAAS,oBAAoB,aAA0B;CACnD,MAAM,MAAM,IAAI,IAAI,WAAW;CAC/B,IAAI,IAAI,aAAa,WAAW,CAAC,eAAe,IAAI,IAAI,QAAQ,KAAK,CAAC,IAAI,MACtE,MAAM,IAAI,MACN,8FACJ;CAEJ,OAAO;AACX;AAEA,SAAgB,gCACZ,UAAkD,CAAC,GAC9B;CACrB,MAAM,UAAU,QAAQ,aAAa,QAAgB,KAAK,GAAG;CAC7D,MAAM,YAAY,QAAQ,aAAa;CAEvC,OAAO,EACH,MAAM,EAAE,eAAe;EACnB,MAAM,WAAW,oBAAoB,WAAW;EAChD,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI,UAAU;EAEd,MAAM,SAAS,YAAY;GACvB,IAAI,OAAO,aAAa,KAAK;GAC7B,QAAQ,KAAA;GACR,gBAAgB,KAAA;GAChB,MAAM,YAAY,MAAM;GACxB,SAAS,KAAA;EACb;EAEA,OAAO;GACH,KAAK,kBAAkB;IACnB,IAAI,UAAU,SACV,OAAO,QAAQ,uBACX,IAAI,MAAM,4DAA4D,CAC1E;IAGJ,OAAO,IAAI,SAAkC,SAAS,WAAW;KAC7D,gBAAgB;KAChB,MAAM,MAAM,IAAI,KAAK;KACrB,IAAI,IAAI,SAAS,WAAW,YAAY;MACpC,IAAI,SACA,OAAO,QAAQ,KAAK,+CAA+C,GAAG;MAE1E,UAAU;MACV,MAAM,cAAc,QAAQ,IAAI;MAChC,mBAAmB;OACf,OAAY,CAAC,CAAC,WAEN,QAAQ,EACJ,YACJ,CAAC,GACL,MACJ;MACJ,CAAC;MACD,OAAO,QAAQ,KAAK;;;;;;;QAOxC;KACgB,CAAC;KAED,SAAS,MAAM;MACX,OAAO,IAAI;MACX,UAAU,SAAS,SAAS,QAAQ,YAAY,EAAE;MAClD,MAAM,OAAO,SAAS,IAAI;KAC9B,CAAC;KACD,OAAO,KAAK,UAAU,UAAU;MAC5B,IAAI,SAAS;MACb,UAAU;MACV,OAAY,CAAC,CAAC,cAAc,OAAO,KAAK,CAAC;KAC7C,CAAC;KACD,OAAO,KAAK,mBAAmB;MAC3B,IAAI,SAAS;MACb,QAAa,QAAQ,CAAC,CACjB,WAAW,QAAQ,gBAAgB,CAAC,CAAC,CACrC,OAAO,UAAmB;OACvB,IAAI,SAAS;OACb,UAAU;OACV,MAAM,SAAS,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;OACvE,OAAY,CAAC,CAAC,cAAc,OAAO,MAAM,CAAC;MAC9C,CAAC;KACT,CAAC;KACD,QAAQ,iBAAiB;MACrB,IAAI,SAAS;MACb,UAAU;MACV,OAAY,CAAC,CAAC,cACV,OACI,IAAI,oCACA,mCACJ,CACJ,CACJ;KACJ,GAAG,SAAS;IAChB,CAAC;GACL;GACA,MAAM,QAAQ;IACV,IAAI,SAAS;KACT,MAAM,OAAO;KACb;IACJ;IACA,UAAU;IACV,MAAM,SAAS;IACf,MAAM,OAAO;IACb,SAAS,IAAI,oCAAoC,CAAC;GACtD;EACJ;CACJ,EACJ;AACJ"}
|
|
1
|
+
{"version":3,"file":"createNodeBrowserAuthentication.js","names":[],"sources":["../src/createNodeBrowserAuthentication.ts"],"sourcesContent":["import { serve } from \"@hono/node-server\";\nimport { BrowserAuthenticationError, type BrowserAuthentication } from \"@party-stack/runtime\";\nimport { Effect } from \"effect\";\nimport { Hono } from \"hono\";\nimport open from \"open\";\nimport type { Server } from \"node:http\";\n\nconst LOOPBACK_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\"]);\nexport interface CreateNodeBrowserAuthenticationOptions {\n openUrl?: (url: string) => Promise<unknown>;\n timeoutMs?: number;\n}\n\nexport function createNodeBrowserAuthentication(\n options: CreateNodeBrowserAuthenticationOptions = {}\n): BrowserAuthentication {\n const openUrl = options.openUrl ?? ((url: string) => open(url));\n return {\n start: Effect.fnUntraced(function* ({ redirectUrl }) {\n const redirect = yield* Effect.try({\n try: () => {\n const url = new URL(redirectUrl);\n if (url.protocol !== \"http:\" || !LOOPBACK_HOSTS.has(url.hostname) || !url.port)\n throw new Error(\n \"Node browser authentication requires an http:// loopback redirect URL with an explicit port.\"\n );\n return url;\n },\n catch: (cause) =>\n new BrowserAuthenticationError({\n reason: \"Configuration\",\n message: cause instanceof Error ? cause.message : \"Invalid redirect URL.\",\n cause,\n }),\n });\n let server: Server | undefined;\n let closing: Promise<void> | undefined;\n let listenFailed = false;\n let cancel: (() => void) | undefined;\n let used = false;\n let closed = false;\n const closeServer = () =>\n (closing ??= new Promise<void>((resolve, reject) => {\n if (!server || listenFailed) {\n resolve();\n return;\n }\n const active = server;\n const stop = () => {\n active.removeListener(\"error\", failed);\n active.close((error) => (error ? reject(error) : resolve()));\n active.closeAllConnections();\n };\n const failed = () => {\n active.removeListener(\"listening\", stop);\n resolve();\n };\n if (active.listening) stop();\n else {\n active.once(\"listening\", stop);\n active.once(\"error\", failed);\n }\n }));\n yield* Effect.addFinalizer(() =>\n Effect.sync(() => {\n closed = true;\n cancel?.();\n }).pipe(Effect.andThen(Effect.promise(closeServer)))\n );\n return {\n open: (authorizationUrl: string) =>\n Effect.suspend(() => {\n if (used || closed)\n return Effect.fail(\n new BrowserAuthenticationError({\n reason: \"Configuration\",\n message: \"This authentication session has already been used or closed.\",\n })\n );\n used = true;\n return Effect.callback<{ callbackUrl: string }, BrowserAuthenticationError>(\n (resume) => {\n let settled = false;\n const finish = (\n result: Effect.Effect<{ callbackUrl: string }, BrowserAuthenticationError>\n ) => {\n if (settled) return;\n settled = true;\n cancel = undefined;\n resume(result);\n };\n cancel = () =>\n finish(\n Effect.fail(\n new BrowserAuthenticationError({\n reason: \"Cancelled\",\n message: \"Browser authentication was cancelled.\",\n })\n )\n );\n const app = new Hono();\n app.get(redirect.pathname, (context) => {\n if (settled)\n return context.text(\n \"Authentication session is already complete.\",\n 409\n );\n const callbackUrl = context.req.url;\n setImmediate(() => finish(Effect.succeed({ callbackUrl })));\n return context.html(\n '<!doctype html><html lang=\"en\"><head><title>Authentication complete</title></head><body><p>Authentication complete. Returning to the terminal…</p><script>window.close()</script></body></html>'\n );\n });\n try {\n server = serve({\n fetch: app.fetch,\n hostname: redirect.hostname.replace(/^\\[|\\]$/g, \"\"),\n port: Number(redirect.port),\n }) as Server;\n server.once(\"error\", (cause) => {\n listenFailed = true;\n finish(\n Effect.fail(\n new BrowserAuthenticationError({\n reason: \"Platform\",\n message:\n \"Could not start the authentication callback server.\",\n cause,\n })\n )\n );\n });\n server.once(\"listening\", () => {\n if (settled) return;\n void Promise.resolve()\n .then(() => openUrl(authorizationUrl))\n .catch((cause: unknown) =>\n finish(\n Effect.fail(\n new BrowserAuthenticationError({\n reason: \"Platform\",\n message: \"Could not open browser authorization.\",\n cause,\n })\n )\n )\n );\n });\n } catch (cause) {\n finish(\n Effect.fail(\n new BrowserAuthenticationError({\n reason: \"Platform\",\n message: \"Could not start browser authentication.\",\n cause,\n })\n )\n );\n }\n return Effect.sync(() => {\n settled = true;\n cancel = undefined;\n }).pipe(Effect.andThen(Effect.promise(closeServer)));\n }\n ).pipe(\n Effect.timeoutOrElse({\n duration: options.timeoutMs ?? 5 * 60 * 1000,\n orElse: () =>\n Effect.fail(\n new BrowserAuthenticationError({\n reason: \"Cancelled\",\n message: \"Browser authentication timed out.\",\n })\n ),\n })\n );\n }),\n };\n }),\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "
|
|
1
|
+
import { Runtime, RuntimeError, RuntimePartition, Runtimes } from "@party-stack/runtime";
|
|
2
|
+
import { Effect, Layer } from "effect";
|
|
3
3
|
//#region src/createNodeRuntime.d.ts
|
|
4
4
|
export interface CreateNodeRuntimeOptions {
|
|
5
5
|
dataDirectory?: string;
|
|
6
6
|
}
|
|
7
|
-
export declare
|
|
8
|
-
export declare const
|
|
7
|
+
export declare const make: (options?: CreateNodeRuntimeOptions | undefined) => Effect.Effect<import("@party-stack/runtime").RuntimeService, import("@party-stack/ontology-storage").OntologyStorageError | RuntimeError, RuntimePartition | import("effect/Scope").Scope>;
|
|
8
|
+
export declare const layer: (options?: CreateNodeRuntimeOptions) => Layer.Layer<Runtime, import("@party-stack/ontology-storage").OntologyStorageError | RuntimeError, RuntimePartition>;
|
|
9
|
+
export declare const nodeRuntime: (options?: CreateNodeRuntimeOptions) => Layer.Layer<Runtimes, never, never>;
|
|
9
10
|
//#endregion
|
|
10
11
|
//# sourceMappingURL=createNodeRuntime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createNodeRuntime.d.ts","names":[],"sources":["../src/createNodeRuntime.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"createNodeRuntime.d.ts","names":[],"sources":["../src/createNodeRuntime.ts"],"mappings":";;;iBAiBiB;EACb;;qBAmBS,OAAI,UAAA,yCAAA,OAAA,sCAAA,wDAAA,uBAAA,cAAA,0CAAA;qBAgEJ,QAAK,UAAa,6BAAwB,MAAA,MAAA,iDAAA,uBAAA,cAAA;qBAC1C,cAAW,UAAa,6BAAwB,MAAA,MAAA"}
|
package/lib/createNodeRuntime.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { NodeFileSystemBlobBytesStore } from "./NodeFileSystemBlobBytesStore.js";
|
|
2
|
-
import {
|
|
2
|
+
import { makeKeyMaterialStore } from "./NodeKeyMaterialStore.js";
|
|
3
3
|
import { createNodeBrowserAuthentication } from "./createNodeBrowserAuthentication.js";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
|
-
import { mkdir, rm } from "node:fs/promises";
|
|
5
|
+
import { mkdir, rm, statfs } from "node:fs/promises";
|
|
6
6
|
import { join } from "node:path";
|
|
7
|
-
import {
|
|
8
|
-
import "
|
|
7
|
+
import { Runtime, RuntimeError, RuntimePartition, Runtimes, makeKeyMaterialStoreKeyring } from "@party-stack/runtime";
|
|
8
|
+
import { Effect, Layer } from "effect";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
10
|
+
import { storageError } from "@party-stack/ontology-storage/driver";
|
|
11
|
+
import * as SQLite from "@party-stack/sql-ontology-storage/sqlite";
|
|
12
|
+
import * as SqlTables from "@party-stack/sql-ontology-storage/tables";
|
|
12
13
|
import Database from "better-sqlite3";
|
|
14
|
+
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
15
|
+
import * as SqlClient from "effect/sql/SqlClient";
|
|
13
16
|
//#region src/createNodeRuntime.ts
|
|
14
17
|
function defaultDataDirectory() {
|
|
15
18
|
if (process.platform === "darwin") return join(homedir(), "Library", "Application Support", "party-stack");
|
|
@@ -19,35 +22,71 @@ function defaultDataDirectory() {
|
|
|
19
22
|
function runtimeId(owner, namespace) {
|
|
20
23
|
return createHash("sha256").update(JSON.stringify([owner, namespace])).digest("hex");
|
|
21
24
|
}
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
25
|
+
const make = Effect.fn("NodeRuntime.make")(function* (options = {}) {
|
|
26
|
+
const { owner, namespace } = yield* RuntimePartition;
|
|
27
|
+
const directory = join(options.dataDirectory ?? defaultDataDirectory(), runtimeId(owner, namespace));
|
|
28
|
+
yield* Effect.tryPromise({
|
|
29
|
+
try: () => mkdir(directory, { recursive: true }),
|
|
30
|
+
catch: (cause) => new RuntimeError({
|
|
31
|
+
operation: "nodeRuntime.directory",
|
|
32
|
+
cause
|
|
33
|
+
})
|
|
34
|
+
});
|
|
35
|
+
const tables = Effect.gen(function* () {
|
|
36
|
+
const storageDatabase = yield* Effect.acquireRelease(Effect.try({
|
|
37
|
+
try: () => new Database(join(directory, "storage.sqlite")),
|
|
38
|
+
catch: (cause) => storageError("sql", "open", cause)
|
|
39
|
+
}), (database) => Effect.sync(() => database.close()));
|
|
40
|
+
const sql = yield* SQLite.makeClient(storageDatabase).pipe(Effect.provide(Reactivity.layer), Effect.mapError((cause) => storageError("sql", "open", cause)));
|
|
41
|
+
return yield* SqlTables.make().pipe(Effect.provideService(SqlClient.SqlClient, sql));
|
|
42
|
+
});
|
|
43
|
+
const blobBytes = new NodeFileSystemBlobBytesStore({ directory: join(directory, "blobs") });
|
|
44
|
+
const secrets = yield* makeKeyMaterialStore({ service: `party-stack:${runtimeId(owner, namespace)}:secrets` });
|
|
45
|
+
const ownedTables = yield* tables;
|
|
46
|
+
const encryptionKeyring = yield* makeKeyMaterialStoreKeyring(secrets);
|
|
47
|
+
return yield* Runtime.make({
|
|
48
|
+
blobBytes,
|
|
49
|
+
capacity: {
|
|
50
|
+
memory: Effect.sync(() => ({
|
|
51
|
+
scope: "process",
|
|
52
|
+
usedBytes: process.memoryUsage().rss,
|
|
53
|
+
availableBytes: process.availableMemory(),
|
|
54
|
+
limitBytes: process.constrainedMemory() || void 0
|
|
55
|
+
})),
|
|
56
|
+
storage: Effect.tryPromise({
|
|
57
|
+
try: async () => {
|
|
58
|
+
const stats = await statfs(directory);
|
|
59
|
+
return {
|
|
60
|
+
scope: "filesystem",
|
|
61
|
+
limitBytes: stats.blocks * stats.bsize,
|
|
62
|
+
availableBytes: stats.bavail * stats.bsize,
|
|
63
|
+
usedBytes: (stats.blocks - stats.bfree) * stats.bsize
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
catch: (cause) => new RuntimeError({
|
|
67
|
+
operation: "capacity.storage",
|
|
68
|
+
cause
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
},
|
|
72
|
+
browserAuthentication: createNodeBrowserAuthentication(),
|
|
73
|
+
encryptionKeyring,
|
|
74
|
+
tables: ownedTables,
|
|
75
|
+
destroy: Effect.tryPromise({
|
|
76
|
+
try: () => rm(directory, {
|
|
43
77
|
recursive: true,
|
|
44
78
|
force: true
|
|
79
|
+
}),
|
|
80
|
+
catch: (cause) => new RuntimeError({
|
|
81
|
+
operation: "destroy",
|
|
82
|
+
cause
|
|
45
83
|
})
|
|
46
|
-
}
|
|
84
|
+
})
|
|
47
85
|
});
|
|
48
|
-
}
|
|
49
|
-
const
|
|
86
|
+
});
|
|
87
|
+
const layer = (options = {}) => Layer.effect(Runtime, make(options));
|
|
88
|
+
const nodeRuntime = (options = {}) => Runtimes.layer(layer(options), { browserAuthentication: createNodeBrowserAuthentication() });
|
|
50
89
|
//#endregion
|
|
51
|
-
export {
|
|
90
|
+
export { layer, make, nodeRuntime };
|
|
52
91
|
|
|
53
92
|
//# sourceMappingURL=createNodeRuntime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createNodeRuntime.js","names":[],"sources":["../src/createNodeRuntime.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"createNodeRuntime.js","names":[],"sources":["../src/createNodeRuntime.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { mkdir, rm, statfs } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { storageError } from \"@party-stack/ontology-storage/driver\";\nimport { Runtime, RuntimeError, makeKeyMaterialStoreKeyring, RuntimePartition } from \"@party-stack/runtime\";\nimport { Runtimes } from \"@party-stack/runtime\";\nimport * as SQLite from \"@party-stack/sql-ontology-storage/sqlite\";\nimport * as SqlTables from \"@party-stack/sql-ontology-storage/tables\";\nimport Database from \"better-sqlite3\";\nimport { Effect, Layer } from \"effect\";\nimport * as Reactivity from \"effect/reactivity/Reactivity\";\nimport * as SqlClient from \"effect/sql/SqlClient\";\nimport { createNodeBrowserAuthentication } from \"./createNodeBrowserAuthentication.js\";\nimport { NodeFileSystemBlobBytesStore } from \"./NodeFileSystemBlobBytesStore.js\";\nimport { makeKeyMaterialStore } from \"./NodeKeyMaterialStore.js\";\n\nexport interface CreateNodeRuntimeOptions {\n dataDirectory?: string;\n}\n\nfunction defaultDataDirectory(): string {\n if (process.platform === \"darwin\") {\n return join(homedir(), \"Library\", \"Application Support\", \"party-stack\");\n }\n if (process.platform === \"win32\") {\n return join(process.env.LOCALAPPDATA ?? join(homedir(), \"AppData\", \"Local\"), \"party-stack\");\n }\n return join(process.env.XDG_DATA_HOME ?? join(homedir(), \".local\", \"share\"), \"party-stack\");\n}\n\nfunction runtimeId(owner: string, namespace: string): string {\n return createHash(\"sha256\")\n .update(JSON.stringify([owner, namespace]))\n .digest(\"hex\");\n}\n\nexport const make = Effect.fn(\"NodeRuntime.make\")(function* (options: CreateNodeRuntimeOptions = {}) {\n const { owner, namespace } = yield* RuntimePartition;\n const directory = join(options.dataDirectory ?? defaultDataDirectory(), runtimeId(owner, namespace));\n yield* Effect.tryPromise({\n try: () => mkdir(directory, { recursive: true }),\n catch: (cause) => new RuntimeError({ operation: \"nodeRuntime.directory\", cause }),\n });\n\n const tables = Effect.gen(function* () {\n const storageDatabase = yield* Effect.acquireRelease(\n Effect.try({\n try: () => new Database(join(directory, \"storage.sqlite\")),\n catch: (cause) => storageError(\"sql\", \"open\", cause),\n }),\n (database) => Effect.sync(() => database.close())\n );\n const sql = yield* SQLite.makeClient(storageDatabase).pipe(\n Effect.provide(Reactivity.layer),\n Effect.mapError((cause) => storageError(\"sql\", \"open\", cause))\n );\n return yield* SqlTables.make().pipe(Effect.provideService(SqlClient.SqlClient, sql));\n });\n const blobBytes = new NodeFileSystemBlobBytesStore({\n directory: join(directory, \"blobs\"),\n });\n\n const secrets = yield* makeKeyMaterialStore({\n service: `party-stack:${runtimeId(owner, namespace)}:secrets`,\n });\n const ownedTables = yield* tables;\n const encryptionKeyring = yield* makeKeyMaterialStoreKeyring(secrets);\n return yield* Runtime.make({\n blobBytes,\n capacity: {\n memory: Effect.sync(() => ({\n scope: \"process\" as const,\n usedBytes: process.memoryUsage().rss,\n availableBytes: process.availableMemory(),\n limitBytes: process.constrainedMemory() || undefined,\n })),\n storage: Effect.tryPromise({\n try: async () => {\n const stats = await statfs(directory);\n return {\n scope: \"filesystem\" as const,\n limitBytes: stats.blocks * stats.bsize,\n availableBytes: stats.bavail * stats.bsize,\n usedBytes: (stats.blocks - stats.bfree) * stats.bsize,\n };\n },\n catch: (cause) => new RuntimeError({ operation: \"capacity.storage\", cause }),\n }),\n },\n browserAuthentication: createNodeBrowserAuthentication(),\n encryptionKeyring,\n\n tables: ownedTables,\n destroy: Effect.tryPromise({\n try: () => rm(directory, { recursive: true, force: true }),\n catch: (cause) => new RuntimeError({ operation: \"destroy\", cause }),\n }),\n });\n});\n\nexport const layer = (options: CreateNodeRuntimeOptions = {}) => Layer.effect(Runtime, make(options));\nexport const nodeRuntime = (options: CreateNodeRuntimeOptions = {}) =>\n Runtimes.layer(layer(options), { browserAuthentication: createNodeBrowserAuthentication() });\n"],"mappings":"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"}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { CreateNodeRuntimeOptions,
|
|
2
|
-
export { type CreateNodeRuntimeOptions,
|
|
1
|
+
import { CreateNodeRuntimeOptions, layer, make, nodeRuntime } from "./createNodeRuntime.js";
|
|
2
|
+
export { type CreateNodeRuntimeOptions, layer, make, nodeRuntime };
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { layer, make, nodeRuntime } from "./createNodeRuntime.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@party-stack/node-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"files": [
|
|
5
5
|
"lib"
|
|
6
6
|
],
|
|
@@ -13,21 +13,22 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@hono/node-server": "^2.1.1",
|
|
16
|
-
"@tanstack/node-db-sqlite-persistence": "0.2.34",
|
|
17
16
|
"better-sqlite3": "^12.9.0",
|
|
18
|
-
"
|
|
17
|
+
"effect": "^4.0.5",
|
|
19
18
|
"hono": "^4.13.5",
|
|
20
19
|
"open": "^11.0.1",
|
|
21
20
|
"temporal-polyfill": "^0.3.0",
|
|
22
|
-
"@party-stack/
|
|
23
|
-
"@party-stack/
|
|
21
|
+
"@party-stack/effect-node-keyring": "0.1.0",
|
|
22
|
+
"@party-stack/ontology-storage": "0.2.0",
|
|
23
|
+
"@party-stack/runtime": "0.5.0",
|
|
24
|
+
"@party-stack/sql-ontology-storage": "0.2.0"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
|
-
"@tanstack/db-sqlite-persistence-core": "0.4.9",
|
|
27
27
|
"@types/better-sqlite3": "^7.6.13",
|
|
28
28
|
"@types/node": "^24.10.1",
|
|
29
29
|
"typescript": "~7.0.2",
|
|
30
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.2.0",
|
|
30
31
|
"vite-plus": "1.2.0",
|
|
31
|
-
"@party-stack/build-config": "0.
|
|
32
|
+
"@party-stack/build-config": "0.2.0"
|
|
32
33
|
}
|
|
33
34
|
}
|
package/lib/NodeSecretStore.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { SecretStore } from "@party-stack/runtime";
|
|
2
|
-
//#region src/NodeSecretStore.d.ts
|
|
3
|
-
export interface NodeSecretStoreOptions {
|
|
4
|
-
service: string;
|
|
5
|
-
}
|
|
6
|
-
export declare class NodeSecretStore implements SecretStore {
|
|
7
|
-
#private;
|
|
8
|
-
constructor(options: NodeSecretStoreOptions);
|
|
9
|
-
get(key: string): Promise<string | undefined>;
|
|
10
|
-
set(key: string, value: string): Promise<void>;
|
|
11
|
-
delete(key: string): Promise<void>;
|
|
12
|
-
}
|
|
13
|
-
//#endregion
|
|
14
|
-
//# sourceMappingURL=NodeSecretStore.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"NodeSecretStore.d.ts","names":[],"sources":["../src/NodeSecretStore.ts"],"mappings":";;iBAaiB;EACb;;qBAmBS,2BAA2B;;EAIpC,YAAY,SAAS;EAIf,IAAI,cAAc;EAKlB,IAAI,aAAa,gBAAgB;EAKjC,OAAO,cAAc"}
|
package/lib/NodeSecretStore.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { getAllBackends } from "cross-keychain";
|
|
3
|
-
//#region src/NodeSecretStore.ts
|
|
4
|
-
const ALLOWED_BACKEND_IDS = /* @__PURE__ */ new Set([
|
|
5
|
-
"native-macos",
|
|
6
|
-
"macos",
|
|
7
|
-
"native-linux",
|
|
8
|
-
"secret-service",
|
|
9
|
-
"native-windows",
|
|
10
|
-
"windows"
|
|
11
|
-
]);
|
|
12
|
-
async function selectOSKeychainBackend() {
|
|
13
|
-
const candidates = (await getAllBackends()).filter((backend) => ALLOWED_BACKEND_IDS.has(backend.id));
|
|
14
|
-
if (candidates.length === 0) throw new Error("No OS keychain is available. Install or enable macOS Keychain, Windows Credential Manager, or a Linux Secret Service provider.");
|
|
15
|
-
return candidates.reduce((selected, candidate) => candidate.priority > selected.priority ? candidate : selected);
|
|
16
|
-
}
|
|
17
|
-
function encodeIdentifier(value) {
|
|
18
|
-
return `party-stack.${createHash("sha256").update(value).digest("hex")}`;
|
|
19
|
-
}
|
|
20
|
-
var NodeSecretStore = class {
|
|
21
|
-
#service;
|
|
22
|
-
#backend;
|
|
23
|
-
constructor(options) {
|
|
24
|
-
this.#service = encodeIdentifier(options.service);
|
|
25
|
-
}
|
|
26
|
-
async get(key) {
|
|
27
|
-
return await (await this.#getBackend()).getPassword(this.#service, encodeIdentifier(key)) ?? void 0;
|
|
28
|
-
}
|
|
29
|
-
async set(key, value) {
|
|
30
|
-
await (await this.#getBackend()).setPassword(this.#service, encodeIdentifier(key), value);
|
|
31
|
-
}
|
|
32
|
-
async delete(key) {
|
|
33
|
-
const backend = await this.#getBackend();
|
|
34
|
-
const account = encodeIdentifier(key);
|
|
35
|
-
if (await backend.getPassword(this.#service, account) === null) return;
|
|
36
|
-
await backend.deletePassword(this.#service, account);
|
|
37
|
-
}
|
|
38
|
-
#getBackend() {
|
|
39
|
-
this.#backend ??= selectOSKeychainBackend();
|
|
40
|
-
return this.#backend;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
//#endregion
|
|
44
|
-
export { NodeSecretStore };
|
|
45
|
-
|
|
46
|
-
//# sourceMappingURL=NodeSecretStore.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"NodeSecretStore.js","names":[],"sources":["../src/NodeSecretStore.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { getAllBackends, type SecretStorageBackend } from \"cross-keychain\";\nimport type { SecretStore } from \"@party-stack/runtime\";\n\nconst ALLOWED_BACKEND_IDS = new Set([\n \"native-macos\",\n \"macos\",\n \"native-linux\",\n \"secret-service\",\n \"native-windows\",\n \"windows\",\n]);\n\nexport interface NodeSecretStoreOptions {\n service: string;\n}\n\nasync function selectOSKeychainBackend(): Promise<SecretStorageBackend> {\n const candidates = (await getAllBackends()).filter((backend) => ALLOWED_BACKEND_IDS.has(backend.id));\n if (candidates.length === 0) {\n throw new Error(\n \"No OS keychain is available. Install or enable macOS Keychain, Windows Credential Manager, or a Linux Secret Service provider.\"\n );\n }\n return candidates.reduce((selected, candidate) =>\n candidate.priority > selected.priority ? candidate : selected\n );\n}\n\nfunction encodeIdentifier(value: string): string {\n return `party-stack.${createHash(\"sha256\").update(value).digest(\"hex\")}`;\n}\n\nexport class NodeSecretStore implements SecretStore {\n readonly #service: string;\n #backend?: Promise<SecretStorageBackend>;\n\n constructor(options: NodeSecretStoreOptions) {\n this.#service = encodeIdentifier(options.service);\n }\n\n async get(key: string): Promise<string | undefined> {\n const backend = await this.#getBackend();\n return (await backend.getPassword(this.#service, encodeIdentifier(key))) ?? undefined;\n }\n\n async set(key: string, value: string): Promise<void> {\n const backend = await this.#getBackend();\n await backend.setPassword(this.#service, encodeIdentifier(key), value);\n }\n\n async delete(key: string): Promise<void> {\n const backend = await this.#getBackend();\n const account = encodeIdentifier(key);\n if ((await backend.getPassword(this.#service, account)) === null) {\n return;\n }\n await backend.deletePassword(this.#service, account);\n }\n\n #getBackend(): Promise<SecretStorageBackend> {\n this.#backend ??= selectOSKeychainBackend();\n return this.#backend;\n }\n}\n"],"mappings":";;;AAIA,MAAM,sCAAsB,IAAI,IAAI;CAChC;CACA;CACA;CACA;CACA;CACA;AACJ,CAAC;AAMD,eAAe,0BAAyD;CACpE,MAAM,cAAc,MAAM,eAAe,EAAA,CAAG,QAAQ,YAAY,oBAAoB,IAAI,QAAQ,EAAE,CAAC;CACnG,IAAI,WAAW,WAAW,GACtB,MAAM,IAAI,MACN,gIACJ;CAEJ,OAAO,WAAW,QAAQ,UAAU,cAChC,UAAU,WAAW,SAAS,WAAW,YAAY,QACzD;AACJ;AAEA,SAAS,iBAAiB,OAAuB;CAC7C,OAAO,eAAe,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK;AACzE;AAEA,IAAa,kBAAb,MAAoD;CAChD;CACA;CAEA,YAAY,SAAiC;EACzC,KAAK,WAAW,iBAAiB,QAAQ,OAAO;CACpD;CAEA,MAAM,IAAI,KAA0C;EAEhD,OAAQ,OAAM,MADQ,KAAK,YAAY,EAAA,CACjB,YAAY,KAAK,UAAU,iBAAiB,GAAG,CAAC,KAAM,KAAA;CAChF;CAEA,MAAM,IAAI,KAAa,OAA8B;EAEjD,OAAM,MADgB,KAAK,YAAY,EAAA,CACzB,YAAY,KAAK,UAAU,iBAAiB,GAAG,GAAG,KAAK;CACzE;CAEA,MAAM,OAAO,KAA4B;EACrC,MAAM,UAAU,MAAM,KAAK,YAAY;EACvC,MAAM,UAAU,iBAAiB,GAAG;EACpC,IAAK,MAAM,QAAQ,YAAY,KAAK,UAAU,OAAO,MAAO,MACxD;EAEJ,MAAM,QAAQ,eAAe,KAAK,UAAU,OAAO;CACvD;CAEA,cAA6C;EACzC,KAAK,aAAa,wBAAwB;EAC1C,OAAO,KAAK;CAChB;AACJ"}
|