@party-stack/node-runtime 0.2.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/LICENSE +21 -0
- package/lib/NodeFileSystemBlobBytesStore.d.ts +13 -0
- package/lib/NodeFileSystemBlobBytesStore.d.ts.map +1 -0
- package/lib/NodeFileSystemBlobBytesStore.js +47 -0
- package/lib/NodeFileSystemBlobBytesStore.js.map +1 -0
- package/lib/NodeFileSystemBlobBytesStore.test.d.ts +2 -0
- package/lib/NodeFileSystemBlobBytesStore.test.d.ts.map +1 -0
- package/lib/NodeFileSystemBlobBytesStore.test.js +38 -0
- package/lib/NodeFileSystemBlobBytesStore.test.js.map +1 -0
- package/lib/NodeSecretStore.d.ts +12 -0
- package/lib/NodeSecretStore.d.ts.map +1 -0
- package/lib/NodeSecretStore.js +48 -0
- package/lib/NodeSecretStore.js.map +1 -0
- package/lib/NodeSecretStore.test.d.ts +2 -0
- package/lib/NodeSecretStore.test.d.ts.map +1 -0
- package/lib/NodeSecretStore.test.js +69 -0
- package/lib/NodeSecretStore.test.js.map +1 -0
- package/lib/createNodeBrowserAuthentication.d.ts +7 -0
- package/lib/createNodeBrowserAuthentication.d.ts.map +1 -0
- package/lib/createNodeBrowserAuthentication.js +118 -0
- package/lib/createNodeBrowserAuthentication.js.map +1 -0
- package/lib/createNodeBrowserAuthentication.test.d.ts +2 -0
- package/lib/createNodeBrowserAuthentication.test.d.ts.map +1 -0
- package/lib/createNodeBrowserAuthentication.test.js +74 -0
- package/lib/createNodeBrowserAuthentication.test.js.map +1 -0
- package/lib/createNodeRuntime.d.ts +7 -0
- package/lib/createNodeRuntime.d.ts.map +1 -0
- package/lib/createNodeRuntime.js +65 -0
- package/lib/createNodeRuntime.js.map +1 -0
- package/lib/createNodeRuntime.test.d.ts +2 -0
- package/lib/createNodeRuntime.test.d.ts.map +1 -0
- package/lib/createNodeRuntime.test.js +64 -0
- package/lib/createNodeRuntime.test.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Robert Fidler
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BlobBytesStore } from "@party-stack/runtime";
|
|
2
|
+
export interface NodeFileSystemBlobBytesStoreOptions {
|
|
3
|
+
directory: string;
|
|
4
|
+
}
|
|
5
|
+
export declare class NodeFileSystemBlobBytesStore implements BlobBytesStore {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(options: NodeFileSystemBlobBytesStoreOptions);
|
|
8
|
+
write(id: string, blob: Blob): Promise<void>;
|
|
9
|
+
read(id: string): Promise<Blob>;
|
|
10
|
+
delete(id: string): Promise<void>;
|
|
11
|
+
clear(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=NodeFileSystemBlobBytesStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeFileSystemBlobBytesStore.d.ts","sourceRoot":"","sources":["../src/NodeFileSystemBlobBytesStore.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,WAAW,mCAAmC;IAChD,SAAS,EAAE,MAAM,CAAC;CACrB;AAMD,qBAAa,4BAA6B,YAAW,cAAc;;gBAGnD,OAAO,EAAE,mCAAmC;IAIlD,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAY5C,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa/B,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO/B"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
function fileName(id) {
|
|
5
|
+
return createHash("sha256").update(id).digest("hex");
|
|
6
|
+
}
|
|
7
|
+
export class NodeFileSystemBlobBytesStore {
|
|
8
|
+
#directory;
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.#directory = options.directory;
|
|
11
|
+
}
|
|
12
|
+
async write(id, blob) {
|
|
13
|
+
await mkdir(this.#directory, { recursive: true });
|
|
14
|
+
const target = this.#path(id);
|
|
15
|
+
const temporary = join(this.#directory, `.${fileName(id)}.${randomUUID()}.tmp`);
|
|
16
|
+
try {
|
|
17
|
+
await writeFile(temporary, new Uint8Array(await blob.arrayBuffer()));
|
|
18
|
+
await rename(temporary, target);
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
await rm(temporary, { force: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async read(id) {
|
|
25
|
+
try {
|
|
26
|
+
return new Blob([await readFile(this.#path(id))]);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
30
|
+
throw new Error(`Blob bytes not found for "${id}".`, {
|
|
31
|
+
cause: error,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async delete(id) {
|
|
38
|
+
await rm(this.#path(id), { force: true });
|
|
39
|
+
}
|
|
40
|
+
async clear() {
|
|
41
|
+
await rm(this.#directory, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
#path(id) {
|
|
44
|
+
return join(this.#directory, fileName(id));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=NodeFileSystemBlobBytesStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeFileSystemBlobBytesStore.js","sourceRoot":"","sources":["../src/NodeFileSystemBlobBytesStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAOjC,SAAS,QAAQ,CAAC,EAAU;IACxB,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,OAAO,4BAA4B;IAC5B,UAAU,CAAS;IAE5B,YAAY,OAA4C;QACpD,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,EAAU,EAAE,IAAU;QAC9B,MAAM,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,QAAQ,CAAC,EAAE,CAAC,IAAI,UAAU,EAAE,MAAM,CAAC,CAAC;QAChF,IAAI,CAAC;YACD,MAAM,SAAS,CAAC,SAAS,EAAE,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;YACrE,MAAM,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC;gBAAS,CAAC;YACP,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAU;QACjB,IAAI,CAAC;YACD,OAAO,IAAI,IAAI,CAAC,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACtD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACvE,MAAM,IAAI,KAAK,CAAC,6BAA6B,EAAE,IAAI,EAAE;oBACjD,KAAK,EAAE,KAAK;iBACf,CAAC,CAAC;YACP,CAAC;YACD,MAAM,KAAK,CAAC;QAChB,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAU;QACnB,MAAM,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,KAAK;QACP,MAAM,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChE,CAAC;IAED,KAAK,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/C,CAAC;CACJ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeFileSystemBlobBytesStore.test.d.ts","sourceRoot":"","sources":["../src/NodeFileSystemBlobBytesStore.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { mkdtemp } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { NodeFileSystemBlobBytesStore } from "./NodeFileSystemBlobBytesStore.js";
|
|
6
|
+
const directories = [];
|
|
7
|
+
afterEach(async () => {
|
|
8
|
+
await Promise.all(directories.splice(0).map(async (directory) => {
|
|
9
|
+
const store = new NodeFileSystemBlobBytesStore({
|
|
10
|
+
directory,
|
|
11
|
+
});
|
|
12
|
+
await store.clear();
|
|
13
|
+
}));
|
|
14
|
+
});
|
|
15
|
+
describe("NodeFileSystemBlobBytesStore", () => {
|
|
16
|
+
it("round-trips bytes and treats ids as opaque values", async () => {
|
|
17
|
+
const directory = await mkdtemp(join(tmpdir(), "party-stack-blobs-"));
|
|
18
|
+
directories.push(directory);
|
|
19
|
+
const store = new NodeFileSystemBlobBytesStore({
|
|
20
|
+
directory,
|
|
21
|
+
});
|
|
22
|
+
await store.write("../../outside", new Blob(["hello"]));
|
|
23
|
+
await expect((await store.read("../../outside")).text()).resolves.toBe("hello");
|
|
24
|
+
await expect(store.read("outside")).rejects.toThrow('Blob bytes not found for "outside".');
|
|
25
|
+
});
|
|
26
|
+
it("deletes missing and existing blobs idempotently", async () => {
|
|
27
|
+
const directory = await mkdtemp(join(tmpdir(), "party-stack-blobs-"));
|
|
28
|
+
directories.push(directory);
|
|
29
|
+
const store = new NodeFileSystemBlobBytesStore({
|
|
30
|
+
directory,
|
|
31
|
+
});
|
|
32
|
+
await store.write("one", new Blob(["one"]));
|
|
33
|
+
await store.delete("one");
|
|
34
|
+
await store.delete("one");
|
|
35
|
+
await expect(store.read("one")).rejects.toThrow('Blob bytes not found for "one".');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
//# sourceMappingURL=NodeFileSystemBlobBytesStore.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeFileSystemBlobBytesStore.test.js","sourceRoot":"","sources":["../src/NodeFileSystemBlobBytesStore.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;AAEjF,MAAM,WAAW,GAAa,EAAE,CAAC;AAEjC,SAAS,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,OAAO,CAAC,GAAG,CACb,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE;QAC1C,MAAM,KAAK,GAAG,IAAI,4BAA4B,CAAC;YAC3C,SAAS;SACZ,CAAC,CAAC;QACH,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC,CAAC,CACL,CAAC;AACN,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,oBAAoB,CAAC,CAAC,CAAC;QACtE,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5B,MAAM,KAAK,GAAG,IAAI,4BAA4B,CAAC;YAC3C,SAAS;SACZ,CAAC,CAAC;QAEH,MAAM,KAAK,CAAC,KAAK,CAAC,eAAe,EAAE,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAExD,MAAM,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAChF,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAC;IAC/F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,oBAAoB,CAAC,CAAC,CAAC;QACtE,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5B,MAAM,KAAK,GAAG,IAAI,4BAA4B,CAAC;YAC3C,SAAS;SACZ,CAAC,CAAC;QAEH,MAAM,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC5C,MAAM,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1B,MAAM,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAE1B,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC;IACvF,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SecretStore } from "@party-stack/runtime";
|
|
2
|
+
export interface NodeSecretStoreOptions {
|
|
3
|
+
service: string;
|
|
4
|
+
}
|
|
5
|
+
export declare class NodeSecretStore implements SecretStore {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(options: NodeSecretStoreOptions);
|
|
8
|
+
get(key: string): Promise<string | undefined>;
|
|
9
|
+
set(key: string, value: string): Promise<void>;
|
|
10
|
+
delete(key: string): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=NodeSecretStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeSecretStore.d.ts","sourceRoot":"","sources":["../src/NodeSecretStore.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAWxD,MAAM,WAAW,sBAAsB;IACnC,OAAO,EAAE,MAAM,CAAC;CACnB;AAkBD,qBAAa,eAAgB,YAAW,WAAW;;gBAInC,OAAO,EAAE,sBAAsB;IAIrC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAK7C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAa3C"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { getAllBackends } from "cross-keychain";
|
|
3
|
+
const ALLOWED_BACKEND_IDS = new Set([
|
|
4
|
+
"native-macos",
|
|
5
|
+
"macos",
|
|
6
|
+
"native-linux",
|
|
7
|
+
"secret-service",
|
|
8
|
+
"native-windows",
|
|
9
|
+
"windows",
|
|
10
|
+
]);
|
|
11
|
+
async function selectOSKeychainBackend() {
|
|
12
|
+
const candidates = (await getAllBackends()).filter((backend) => ALLOWED_BACKEND_IDS.has(backend.id));
|
|
13
|
+
if (candidates.length === 0) {
|
|
14
|
+
throw new Error("No OS keychain is available. Install or enable macOS Keychain, Windows Credential Manager, or a Linux Secret Service provider.");
|
|
15
|
+
}
|
|
16
|
+
return candidates.reduce((selected, candidate) => candidate.priority > selected.priority ? candidate : selected);
|
|
17
|
+
}
|
|
18
|
+
function encodeIdentifier(value) {
|
|
19
|
+
return `party-stack.${createHash("sha256").update(value).digest("hex")}`;
|
|
20
|
+
}
|
|
21
|
+
export class NodeSecretStore {
|
|
22
|
+
#service;
|
|
23
|
+
#backend;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.#service = encodeIdentifier(options.service);
|
|
26
|
+
}
|
|
27
|
+
async get(key) {
|
|
28
|
+
const backend = await this.#getBackend();
|
|
29
|
+
return (await backend.getPassword(this.#service, encodeIdentifier(key))) ?? undefined;
|
|
30
|
+
}
|
|
31
|
+
async set(key, value) {
|
|
32
|
+
const backend = await this.#getBackend();
|
|
33
|
+
await backend.setPassword(this.#service, encodeIdentifier(key), value);
|
|
34
|
+
}
|
|
35
|
+
async delete(key) {
|
|
36
|
+
const backend = await this.#getBackend();
|
|
37
|
+
const account = encodeIdentifier(key);
|
|
38
|
+
if ((await backend.getPassword(this.#service, account)) === null) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
await backend.deletePassword(this.#service, account);
|
|
42
|
+
}
|
|
43
|
+
#getBackend() {
|
|
44
|
+
this.#backend ??= selectOSKeychainBackend();
|
|
45
|
+
return this.#backend;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=NodeSecretStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeSecretStore.js","sourceRoot":"","sources":["../src/NodeSecretStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,cAAc,EAA6B,MAAM,gBAAgB,CAAC;AAG3E,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IAChC,cAAc;IACd,OAAO;IACP,cAAc;IACd,gBAAgB;IAChB,gBAAgB;IAChB,SAAS;CACZ,CAAC,CAAC;AAMH,KAAK,UAAU,uBAAuB;IAClC,MAAM,UAAU,GAAG,CAAC,MAAM,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IACrG,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACX,gIAAgI,CACnI,CAAC;IACN,CAAC;IACD,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,CAC7C,SAAS,CAAC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAChE,CAAC;AACN,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAa;IACnC,OAAO,eAAe,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAC7E,CAAC;AAED,MAAM,OAAO,eAAe;IACf,QAAQ,CAAS;IAC1B,QAAQ,CAAiC;IAEzC,YAAY,OAA+B;QACvC,IAAI,CAAC,QAAQ,GAAG,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACjB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QACzC,OAAO,CAAC,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC;IAC1F,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAa;QAChC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QACzC,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,gBAAgB,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;IAC3E,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAW;QACpB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YAC/D,OAAO;QACX,CAAC;QACD,MAAM,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzD,CAAC;IAED,WAAW;QACP,IAAI,CAAC,QAAQ,KAAK,uBAAuB,EAAE,CAAC;QAC5C,OAAO,IAAI,CAAC,QAAQ,CAAC;IACzB,CAAC;CACJ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeSecretStore.test.d.ts","sourceRoot":"","sources":["../src/NodeSecretStore.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { NodeSecretStore } from "./NodeSecretStore.js";
|
|
3
|
+
const keychain = vi.hoisted(() => ({
|
|
4
|
+
getAllBackends: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
vi.mock("cross-keychain", () => keychain);
|
|
7
|
+
function memoryBackend(id = "native-macos") {
|
|
8
|
+
const values = new Map();
|
|
9
|
+
return {
|
|
10
|
+
id,
|
|
11
|
+
name: id,
|
|
12
|
+
priority: 10,
|
|
13
|
+
getPassword(service, account) {
|
|
14
|
+
return Promise.resolve(values.get(`${service}:${account}`) ?? null);
|
|
15
|
+
},
|
|
16
|
+
setPassword(service, account, password) {
|
|
17
|
+
values.set(`${service}:${account}`, password);
|
|
18
|
+
return Promise.resolve();
|
|
19
|
+
},
|
|
20
|
+
deletePassword(service, account) {
|
|
21
|
+
values.delete(`${service}:${account}`);
|
|
22
|
+
return Promise.resolve();
|
|
23
|
+
},
|
|
24
|
+
getCredential() {
|
|
25
|
+
return Promise.resolve(null);
|
|
26
|
+
},
|
|
27
|
+
withProperties() {
|
|
28
|
+
return this;
|
|
29
|
+
},
|
|
30
|
+
diagnose() {
|
|
31
|
+
return Promise.resolve({});
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
describe("NodeSecretStore", () => {
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
keychain.getAllBackends.mockReset();
|
|
38
|
+
});
|
|
39
|
+
it("namespaces and hashes keychain accounts", async () => {
|
|
40
|
+
const backend = memoryBackend();
|
|
41
|
+
keychain.getAllBackends.mockResolvedValue([backend]);
|
|
42
|
+
const setPassword = vi.spyOn(backend, "setPassword");
|
|
43
|
+
const store = new NodeSecretStore({
|
|
44
|
+
service: "party-stack:test:secrets",
|
|
45
|
+
});
|
|
46
|
+
await store.set("tokens/client:id", "secret");
|
|
47
|
+
expect(setPassword).toHaveBeenCalledWith(expect.stringMatching(/^party-stack\.[a-f0-9]{64}$/), expect.stringMatching(/^party-stack\.[a-f0-9]{64}$/), "secret");
|
|
48
|
+
await expect(store.get("tokens/client:id")).resolves.toBe("secret");
|
|
49
|
+
await store.delete("tokens/client:id");
|
|
50
|
+
await store.delete("tokens/client:id");
|
|
51
|
+
});
|
|
52
|
+
it("rejects file-only keychain availability", async () => {
|
|
53
|
+
keychain.getAllBackends.mockResolvedValue([memoryBackend("file")]);
|
|
54
|
+
const store = new NodeSecretStore({
|
|
55
|
+
service: "party-stack:test:secrets",
|
|
56
|
+
});
|
|
57
|
+
await expect(store.get("token")).rejects.toThrow("No OS keychain is available");
|
|
58
|
+
});
|
|
59
|
+
it("selects the Linux Secret Service lazily", async () => {
|
|
60
|
+
keychain.getAllBackends.mockResolvedValue([memoryBackend("secret-service")]);
|
|
61
|
+
const store = new NodeSecretStore({
|
|
62
|
+
service: "party-stack:test:secrets",
|
|
63
|
+
});
|
|
64
|
+
expect(keychain.getAllBackends).not.toHaveBeenCalled();
|
|
65
|
+
await expect(store.get("token")).resolves.toBeUndefined();
|
|
66
|
+
expect(keychain.getAllBackends).toHaveBeenCalledOnce();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
//# sourceMappingURL=NodeSecretStore.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeSecretStore.test.js","sourceRoot":"","sources":["../src/NodeSecretStore.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type BrowserAuthentication } from "@party-stack/runtime";
|
|
2
|
+
export interface CreateNodeBrowserAuthenticationOptions {
|
|
3
|
+
openUrl?: (url: string) => Promise<unknown>;
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function createNodeBrowserAuthentication(options?: CreateNodeBrowserAuthenticationOptions): BrowserAuthentication;
|
|
7
|
+
//# sourceMappingURL=createNodeBrowserAuthentication.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createNodeBrowserAuthentication.d.ts","sourceRoot":"","sources":["../src/createNodeBrowserAuthentication.ts"],"names":[],"mappings":"AACA,OAAO,EAAuC,KAAK,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAQvG,MAAM,WAAW,sCAAsC;IACnD,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAsBD,wBAAgB,+BAA+B,CAC3C,OAAO,GAAE,sCAA2C,GACrD,qBAAqB,CAuGvB"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { serve } from "@hono/node-server";
|
|
2
|
+
import { BrowserAuthenticationCancelledError } from "@party-stack/runtime";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
import open from "open";
|
|
5
|
+
const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
6
|
+
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
7
|
+
function closeServer(server) {
|
|
8
|
+
if (!server?.listening)
|
|
9
|
+
return Promise.resolve();
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
server.close((error) => {
|
|
12
|
+
if (error)
|
|
13
|
+
reject(error);
|
|
14
|
+
else
|
|
15
|
+
resolve();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function validateRedirectUrl(redirectUrl) {
|
|
20
|
+
const url = new URL(redirectUrl);
|
|
21
|
+
if (url.protocol !== "http:" || !LOOPBACK_HOSTS.has(url.hostname) || !url.port) {
|
|
22
|
+
throw new Error("Node browser authentication requires an http:// loopback redirect URL with an explicit port.");
|
|
23
|
+
}
|
|
24
|
+
return url;
|
|
25
|
+
}
|
|
26
|
+
export function createNodeBrowserAuthentication(options = {}) {
|
|
27
|
+
const openUrl = options.openUrl ?? ((url) => open(url));
|
|
28
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
29
|
+
return {
|
|
30
|
+
start({ redirectUrl }) {
|
|
31
|
+
const redirect = validateRedirectUrl(redirectUrl);
|
|
32
|
+
let server;
|
|
33
|
+
let rejectPending;
|
|
34
|
+
let timer;
|
|
35
|
+
let settled = false;
|
|
36
|
+
const finish = async () => {
|
|
37
|
+
if (timer)
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
timer = undefined;
|
|
40
|
+
rejectPending = undefined;
|
|
41
|
+
await closeServer(server);
|
|
42
|
+
server = undefined;
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
open(authorizationUrl) {
|
|
46
|
+
if (server || settled) {
|
|
47
|
+
return Promise.reject(new Error("This browser authentication session has already been used."));
|
|
48
|
+
}
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
rejectPending = reject;
|
|
51
|
+
const app = new Hono();
|
|
52
|
+
app.get(redirect.pathname, (context) => {
|
|
53
|
+
if (settled) {
|
|
54
|
+
return context.text("Authentication session is already complete.", 409);
|
|
55
|
+
}
|
|
56
|
+
settled = true;
|
|
57
|
+
const callbackUrl = context.req.url;
|
|
58
|
+
setImmediate(() => {
|
|
59
|
+
void finish().then(() => resolve({
|
|
60
|
+
callbackUrl,
|
|
61
|
+
}), reject);
|
|
62
|
+
});
|
|
63
|
+
return context.html(`<!doctype html>
|
|
64
|
+
<html lang="en">
|
|
65
|
+
<head><meta charset="utf-8"><title>Authentication complete</title></head>
|
|
66
|
+
<body>
|
|
67
|
+
<p>Authentication complete. Returning to the terminal…</p>
|
|
68
|
+
<script>window.close()</script>
|
|
69
|
+
</body>
|
|
70
|
+
</html>`);
|
|
71
|
+
});
|
|
72
|
+
server = serve({
|
|
73
|
+
fetch: app.fetch,
|
|
74
|
+
hostname: redirect.hostname.replace(/^\[|\]$/g, ""),
|
|
75
|
+
port: Number(redirect.port),
|
|
76
|
+
});
|
|
77
|
+
server.once("error", (error) => {
|
|
78
|
+
if (settled)
|
|
79
|
+
return;
|
|
80
|
+
settled = true;
|
|
81
|
+
void finish().finally(() => reject(error));
|
|
82
|
+
});
|
|
83
|
+
server.once("listening", () => {
|
|
84
|
+
if (settled)
|
|
85
|
+
return;
|
|
86
|
+
void Promise.resolve()
|
|
87
|
+
.then(() => openUrl(authorizationUrl))
|
|
88
|
+
.catch((error) => {
|
|
89
|
+
if (settled)
|
|
90
|
+
return;
|
|
91
|
+
settled = true;
|
|
92
|
+
const reason = error instanceof Error ? error : new Error(String(error));
|
|
93
|
+
void finish().finally(() => reject(reason));
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
timer = setTimeout(() => {
|
|
97
|
+
if (settled)
|
|
98
|
+
return;
|
|
99
|
+
settled = true;
|
|
100
|
+
void finish().finally(() => reject(new BrowserAuthenticationCancelledError("Browser authentication timed out.")));
|
|
101
|
+
}, timeoutMs);
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
async close() {
|
|
105
|
+
if (settled) {
|
|
106
|
+
await finish();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
settled = true;
|
|
110
|
+
const reject = rejectPending;
|
|
111
|
+
await finish();
|
|
112
|
+
reject?.(new BrowserAuthenticationCancelledError());
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=createNodeBrowserAuthentication.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createNodeBrowserAuthentication.js","sourceRoot":"","sources":["../src/createNodeBrowserAuthentication.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createNodeBrowserAuthentication.test.d.ts","sourceRoot":"","sources":["../src/createNodeBrowserAuthentication.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createServer } from "node:net";
|
|
2
|
+
import { BrowserAuthenticationCancelledError } from "@party-stack/runtime";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { createNodeBrowserAuthentication } from "./createNodeBrowserAuthentication.js";
|
|
5
|
+
async function availablePort() {
|
|
6
|
+
const server = createServer();
|
|
7
|
+
await new Promise((resolve, reject) => {
|
|
8
|
+
server.once("error", reject);
|
|
9
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
10
|
+
});
|
|
11
|
+
const address = server.address();
|
|
12
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
13
|
+
await new Promise((resolve, reject) => {
|
|
14
|
+
server.close((error) => {
|
|
15
|
+
if (error)
|
|
16
|
+
reject(error);
|
|
17
|
+
else
|
|
18
|
+
resolve();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
return port;
|
|
22
|
+
}
|
|
23
|
+
describe("createNodeBrowserAuthentication", () => {
|
|
24
|
+
it("opens the browser and resolves the loopback callback", async () => {
|
|
25
|
+
const port = await availablePort();
|
|
26
|
+
const redirectUrl = `http://127.0.0.1:${port}/oauth/callback`;
|
|
27
|
+
let responseText;
|
|
28
|
+
const openUrl = vi.fn(async (authorizationUrl) => {
|
|
29
|
+
expect(authorizationUrl).toBe("https://auth.example/authorize");
|
|
30
|
+
const response = await fetch(`${redirectUrl}?code=code&state=state`);
|
|
31
|
+
responseText = await response.text();
|
|
32
|
+
});
|
|
33
|
+
const authentication = createNodeBrowserAuthentication({
|
|
34
|
+
openUrl,
|
|
35
|
+
});
|
|
36
|
+
const session = authentication.start({
|
|
37
|
+
redirectUrl,
|
|
38
|
+
});
|
|
39
|
+
await expect(session.open("https://auth.example/authorize")).resolves.toEqual({
|
|
40
|
+
callbackUrl: `${redirectUrl}?code=code&state=state`,
|
|
41
|
+
});
|
|
42
|
+
await vi.waitFor(() => {
|
|
43
|
+
expect(responseText).toContain("window.close()");
|
|
44
|
+
});
|
|
45
|
+
expect(openUrl).toHaveBeenCalledOnce();
|
|
46
|
+
});
|
|
47
|
+
it("rejects a pending session when closed", async () => {
|
|
48
|
+
const port = await availablePort();
|
|
49
|
+
let opened;
|
|
50
|
+
const didOpen = new Promise((resolve) => {
|
|
51
|
+
opened = resolve;
|
|
52
|
+
});
|
|
53
|
+
const authentication = createNodeBrowserAuthentication({
|
|
54
|
+
openUrl: () => {
|
|
55
|
+
opened?.();
|
|
56
|
+
return Promise.resolve();
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
const session = authentication.start({
|
|
60
|
+
redirectUrl: `http://127.0.0.1:${port}/callback`,
|
|
61
|
+
});
|
|
62
|
+
const result = session.open("https://auth.example/authorize");
|
|
63
|
+
await didOpen;
|
|
64
|
+
await session.close();
|
|
65
|
+
await expect(result).rejects.toBeInstanceOf(BrowserAuthenticationCancelledError);
|
|
66
|
+
});
|
|
67
|
+
it("rejects non-loopback redirects", () => {
|
|
68
|
+
const authentication = createNodeBrowserAuthentication();
|
|
69
|
+
expect(() => authentication.start({
|
|
70
|
+
redirectUrl: "https://app.example/callback",
|
|
71
|
+
})).toThrow("requires an http:// loopback redirect URL");
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
//# sourceMappingURL=createNodeBrowserAuthentication.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createNodeBrowserAuthentication.test.js","sourceRoot":"","sources":["../src/createNodeBrowserAuthentication.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type RuntimeAdapterProvider } from "@party-stack/runtime";
|
|
2
|
+
export interface CreateNodeRuntimeOptions {
|
|
3
|
+
dataDirectory?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function createNodeRuntimeWithOptions(options: CreateNodeRuntimeOptions): RuntimeAdapterProvider;
|
|
6
|
+
export declare const createNodeRuntime: RuntimeAdapterProvider;
|
|
7
|
+
//# sourceMappingURL=createNodeRuntime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createNodeRuntime.d.ts","sourceRoot":"","sources":["../src/createNodeRuntime.ts"],"names":[],"mappings":"AAKA,OAAO,EAAiB,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAOlF,MAAM,WAAW,wBAAwB;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAkBD,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,wBAAwB,GAAG,sBAAsB,CAwCtG;AAED,eAAO,MAAM,iBAAiB,wBAAmC,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { SingleProcessCoordination } from "@party-stack/coordination";
|
|
6
|
+
import { defineRuntime } from "@party-stack/runtime";
|
|
7
|
+
import { createNodeSQLitePersistence } from "@tanstack/node-db-sqlite-persistence";
|
|
8
|
+
import Database from "better-sqlite3";
|
|
9
|
+
import { createNodeBrowserAuthentication } from "./createNodeBrowserAuthentication.js";
|
|
10
|
+
import { NodeFileSystemBlobBytesStore } from "./NodeFileSystemBlobBytesStore.js";
|
|
11
|
+
import { NodeSecretStore } from "./NodeSecretStore.js";
|
|
12
|
+
function defaultDataDirectory() {
|
|
13
|
+
if (process.platform === "darwin") {
|
|
14
|
+
return join(homedir(), "Library", "Application Support", "party-stack");
|
|
15
|
+
}
|
|
16
|
+
if (process.platform === "win32") {
|
|
17
|
+
return join(process.env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local"), "party-stack");
|
|
18
|
+
}
|
|
19
|
+
return join(process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share"), "party-stack");
|
|
20
|
+
}
|
|
21
|
+
function runtimeId(owner, namespace) {
|
|
22
|
+
return createHash("sha256")
|
|
23
|
+
.update(JSON.stringify([owner, namespace]))
|
|
24
|
+
.digest("hex");
|
|
25
|
+
}
|
|
26
|
+
export function createNodeRuntimeWithOptions(options) {
|
|
27
|
+
return defineRuntime(async (owner, namespace) => {
|
|
28
|
+
const scope = `party-stack:${owner}:${namespace}`;
|
|
29
|
+
const directory = join(options.dataDirectory ?? defaultDataDirectory(), runtimeId(owner, namespace));
|
|
30
|
+
await mkdir(directory, {
|
|
31
|
+
recursive: true,
|
|
32
|
+
});
|
|
33
|
+
const database = new Database(join(directory, "collections.sqlite"));
|
|
34
|
+
const { adapter: persistence } = createNodeSQLitePersistence({
|
|
35
|
+
database,
|
|
36
|
+
});
|
|
37
|
+
const coordination = new SingleProcessCoordination({
|
|
38
|
+
scope,
|
|
39
|
+
});
|
|
40
|
+
const blobBytes = new NodeFileSystemBlobBytesStore({
|
|
41
|
+
directory: join(directory, "blobs"),
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
owner,
|
|
45
|
+
namespace,
|
|
46
|
+
blobBytes,
|
|
47
|
+
browserAuthentication: createNodeBrowserAuthentication(),
|
|
48
|
+
secrets: new NodeSecretStore({
|
|
49
|
+
service: `party-stack:${runtimeId(owner, namespace)}:secrets`,
|
|
50
|
+
}),
|
|
51
|
+
coordination,
|
|
52
|
+
persistence,
|
|
53
|
+
cleanup: async () => {
|
|
54
|
+
await coordination.close();
|
|
55
|
+
database.close();
|
|
56
|
+
},
|
|
57
|
+
destroy: () => rm(directory, {
|
|
58
|
+
recursive: true,
|
|
59
|
+
force: true,
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export const createNodeRuntime = createNodeRuntimeWithOptions({});
|
|
65
|
+
//# sourceMappingURL=createNodeRuntime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createNodeRuntime.js","sourceRoot":"","sources":["../src/createNodeRuntime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createNodeRuntime.test.d.ts","sourceRoot":"","sources":["../src/createNodeRuntime.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { createLocalCollection } from "@party-stack/runtime";
|
|
5
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
6
|
+
import { createNodeRuntimeWithOptions } from "./createNodeRuntime.js";
|
|
7
|
+
const directories = [];
|
|
8
|
+
afterEach(async () => {
|
|
9
|
+
await Promise.all(directories.splice(0).map((directory) => rm(directory, {
|
|
10
|
+
recursive: true,
|
|
11
|
+
force: true,
|
|
12
|
+
})));
|
|
13
|
+
});
|
|
14
|
+
function createItems(runtime) {
|
|
15
|
+
return createLocalCollection({
|
|
16
|
+
name: "items",
|
|
17
|
+
getKey: (item) => item.id,
|
|
18
|
+
runtime,
|
|
19
|
+
schemaVersion: 1,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
describe("createNodeRuntime", () => {
|
|
23
|
+
it("reopens persisted SQLite collections", async () => {
|
|
24
|
+
const directory = await mkdtemp(join(tmpdir(), "party-stack-runtime-"));
|
|
25
|
+
directories.push(directory);
|
|
26
|
+
const provider = createNodeRuntimeWithOptions({
|
|
27
|
+
dataDirectory: directory,
|
|
28
|
+
});
|
|
29
|
+
const firstRuntime = await provider("owner", "namespace");
|
|
30
|
+
const first = createItems(firstRuntime);
|
|
31
|
+
await first.preload();
|
|
32
|
+
await first.insert({
|
|
33
|
+
id: "one",
|
|
34
|
+
title: "Persisted",
|
|
35
|
+
}).isPersisted.promise;
|
|
36
|
+
await first.cleanup();
|
|
37
|
+
await firstRuntime.cleanup?.();
|
|
38
|
+
const secondRuntime = await provider("owner", "namespace");
|
|
39
|
+
const second = createItems(secondRuntime);
|
|
40
|
+
await second.preload();
|
|
41
|
+
expect(second.get("one")).toMatchObject({
|
|
42
|
+
id: "one",
|
|
43
|
+
title: "Persisted",
|
|
44
|
+
});
|
|
45
|
+
await second.cleanup();
|
|
46
|
+
await secondRuntime.destroy?.();
|
|
47
|
+
});
|
|
48
|
+
it("isolates runtime namespaces", async () => {
|
|
49
|
+
const directory = await mkdtemp(join(tmpdir(), "party-stack-runtime-"));
|
|
50
|
+
directories.push(directory);
|
|
51
|
+
const provider = createNodeRuntimeWithOptions({
|
|
52
|
+
dataDirectory: directory,
|
|
53
|
+
});
|
|
54
|
+
const first = await provider("owner", "one");
|
|
55
|
+
const second = await provider("owner", "two");
|
|
56
|
+
await first.blobBytes.write("blob", new Blob(["first"]));
|
|
57
|
+
await second.blobBytes.write("blob", new Blob(["second"]));
|
|
58
|
+
await expect((await first.blobBytes.read("blob")).text()).resolves.toBe("first");
|
|
59
|
+
await expect((await second.blobBytes.read("blob")).text()).resolves.toBe("second");
|
|
60
|
+
await first.destroy?.();
|
|
61
|
+
await second.destroy?.();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
//# sourceMappingURL=createNodeRuntime.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createNodeRuntime.test.js","sourceRoot":"","sources":["../src/createNodeRuntime.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,4BAA4B,EAAE,MAAM,wBAAwB,CAAC;AAOtE,MAAM,WAAW,GAAa,EAAE,CAAC;AAEjC,SAAS,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,OAAO,CAAC,GAAG,CACb,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CACpC,EAAE,CAAC,SAAS,EAAE;QACV,SAAS,EAAE,IAAI;QACf,KAAK,EAAE,IAAI;KACd,CAAC,CACL,CACJ,CAAC;AACN,CAAC,CAAC,CAAC;AAEH,SAAS,WAAW,CAAC,OAA6E;IAC9F,OAAO,qBAAqB,CAAe;QACvC,IAAI,EAAE,OAAO;QACb,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE;QACzB,OAAO;QACP,aAAa,EAAE,CAAC;KACnB,CAAC,CAAC;AACP,CAAC;AAED,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,sBAAsB,CAAC,CAAC,CAAC;QACxE,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5B,MAAM,QAAQ,GAAG,4BAA4B,CAAC;YAC1C,aAAa,EAAE,SAAS;SAC3B,CAAC,CAAC;QACH,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG,WAAW,CAAC,YAAY,CAAC,CAAC;QACxC,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;QACtB,MAAM,KAAK,CAAC,MAAM,CAAC;YACf,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,WAAW;SACrB,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC;QACvB,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;QACtB,MAAM,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC;QAE/B,MAAM,aAAa,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QAC3D,MAAM,MAAM,GAAG,WAAW,CAAC,aAAa,CAAC,CAAC;QAC1C,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;QAEvB,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC;YACpC,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,WAAW;SACrB,CAAC,CAAC;QACH,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;QACvB,MAAM,aAAa,CAAC,OAAO,EAAE,EAAE,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QACzC,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,sBAAsB,CAAC,CAAC,CAAC;QACxE,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5B,MAAM,QAAQ,GAAG,4BAA4B,CAAC;YAC1C,aAAa,EAAE,SAAS;SAC3B,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAE9C,MAAM,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACzD,MAAM,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAE3D,MAAM,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACjF,MAAM,MAAM,CAAC,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnF,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;QACxB,MAAM,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;IAC7B,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,iBAAiB,EACjB,4BAA4B,EAC5B,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAC"}
|
package/lib/index.js
ADDED
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,iBAAiB,EACjB,4BAA4B,GAE/B,MAAM,wBAAwB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@party-stack/node-runtime",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./lib/index.d.ts",
|
|
8
|
+
"default": "./lib/index.js"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"lib"
|
|
13
|
+
],
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@hono/node-server": "^2.1.1",
|
|
16
|
+
"@tanstack/node-db-sqlite-persistence": "^0.2.6",
|
|
17
|
+
"better-sqlite3": "^12.4.6",
|
|
18
|
+
"cross-keychain": "^1.1.0",
|
|
19
|
+
"hono": "^4.13.5",
|
|
20
|
+
"open": "^11.0.1",
|
|
21
|
+
"@party-stack/coordination": "0.2.0",
|
|
22
|
+
"@party-stack/runtime": "0.3.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@bobbyfidz/universal-build-config": "^0.1.1",
|
|
26
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
27
|
+
"@types/node": "^24.10.1",
|
|
28
|
+
"eslint": "^9.25.0",
|
|
29
|
+
"typescript": "~5.8.3",
|
|
30
|
+
"vitest": "^3.1.1"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc",
|
|
34
|
+
"lint": "eslint",
|
|
35
|
+
"test": "vitest run"
|
|
36
|
+
}
|
|
37
|
+
}
|