@digitalpaws/starr 0.0.1
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.rst +75 -0
- package/data/.nfs.811bbb0d.43c34.4 +0 -0
- package/data/.nfs.81ba4cd0.ffff87804.4 +0 -0
- package/data/.nfs.82d127f7.fffff5704.4 +0 -0
- package/data/.nfs.82d127fe.fffff5704.4 +0 -0
- package/data/.nfs.82d159a5.fffff5aa4.4 +0 -0
- package/data/.nfs.82d159aa.fffff5aa4.4 +0 -0
- package/data/.nfs.82d15a9c.fffff5bb4.4 +0 -0
- package/data/.nfs.82d15aa1.fffff5bb4.4 +0 -0
- package/data/.nfs.82d15c65.fffff5c24.4 +0 -0
- package/data/.nfs.82d1852c.fffff5e24.4 +0 -0
- package/data/.nfs.82d18536.fffff5e24.4 +0 -0
- package/data/.nfs.82d18dbe.fffff5e44.4 +0 -0
- package/data/.nfs.82d18dc6.fffff5e44.4 +0 -0
- package/data/.nfs.8309a3f5.fffff82f4.4 +0 -0
- package/data/.nfs.8309a3fe.fffff82f4.4 +0 -0
- package/data/.nfs.8314122e.fffffb044.4 +0 -0
- package/data/.nfs.83141236.fffffb044.4 +0 -0
- package/data/.nfs.833c4fe9.fffffbc34.4 +0 -0
- package/data/.nfs.833c4ff3.fffffbc34.4 +0 -0
- package/data/logs/starr.log +66244 -0
- package/data/starr.db +0 -0
- package/data/starr.db-shm +0 -0
- package/data/starr.db-wal +0 -0
- package/data/starr.jwt +1 -0
- package/data/starr.key +1 -0
- package/dist/auth/auth-service.d.ts +42 -0
- package/dist/auth/auth-service.d.ts.map +1 -0
- package/dist/auth/auth-service.js +226 -0
- package/dist/auth/auth-service.js.map +1 -0
- package/dist/auth/http-server.d.ts +16 -0
- package/dist/auth/http-server.d.ts.map +1 -0
- package/dist/auth/http-server.js +207 -0
- package/dist/auth/http-server.js.map +1 -0
- package/dist/auth/jwt-secret.d.ts +6 -0
- package/dist/auth/jwt-secret.d.ts.map +1 -0
- package/dist/auth/jwt-secret.js +51 -0
- package/dist/auth/jwt-secret.js.map +1 -0
- package/dist/auth/oidc/oidc-flow.d.ts +60 -0
- package/dist/auth/oidc/oidc-flow.d.ts.map +1 -0
- package/dist/auth/oidc/oidc-flow.js +201 -0
- package/dist/auth/oidc/oidc-flow.js.map +1 -0
- package/dist/auth/oidc/oidc-provider-service.d.ts +59 -0
- package/dist/auth/oidc/oidc-provider-service.d.ts.map +1 -0
- package/dist/auth/oidc/oidc-provider-service.js +124 -0
- package/dist/auth/oidc/oidc-provider-service.js.map +1 -0
- package/dist/auth/oidc/user-service.d.ts +67 -0
- package/dist/auth/oidc/user-service.d.ts.map +1 -0
- package/dist/auth/oidc/user-service.js +177 -0
- package/dist/auth/oidc/user-service.js.map +1 -0
- package/dist/auth/rate-limiter.d.ts +31 -0
- package/dist/auth/rate-limiter.d.ts.map +1 -0
- package/dist/auth/rate-limiter.js +70 -0
- package/dist/auth/rate-limiter.js.map +1 -0
- package/dist/config/config-service.d.ts +83 -0
- package/dist/config/config-service.d.ts.map +1 -0
- package/dist/config/config-service.js +309 -0
- package/dist/config/config-service.js.map +1 -0
- package/dist/config/validate.d.ts +12 -0
- package/dist/config/validate.d.ts.map +1 -0
- package/dist/config/validate.js +140 -0
- package/dist/config/validate.js.map +1 -0
- package/dist/content-types/registry.d.ts +75 -0
- package/dist/content-types/registry.d.ts.map +1 -0
- package/dist/content-types/registry.js +171 -0
- package/dist/content-types/registry.js.map +1 -0
- package/dist/datastore/db.d.ts +9 -0
- package/dist/datastore/db.d.ts.map +1 -0
- package/dist/datastore/db.js +27 -0
- package/dist/datastore/db.js.map +1 -0
- package/dist/datastore/encrypt.d.ts +9 -0
- package/dist/datastore/encrypt.d.ts.map +1 -0
- package/dist/datastore/encrypt.js +84 -0
- package/dist/datastore/encrypt.js.map +1 -0
- package/dist/datastore/migrations.d.ts +10 -0
- package/dist/datastore/migrations.d.ts.map +1 -0
- package/dist/datastore/migrations.js +340 -0
- package/dist/datastore/migrations.js.map +1 -0
- package/dist/datastore/storage.d.ts +4 -0
- package/dist/datastore/storage.d.ts.map +1 -0
- package/dist/datastore/storage.js +60 -0
- package/dist/datastore/storage.js.map +1 -0
- package/dist/events/event-bus.d.ts +15 -0
- package/dist/events/event-bus.d.ts.map +1 -0
- package/dist/events/event-bus.js +140 -0
- package/dist/events/event-bus.js.map +1 -0
- package/dist/events/spine.d.ts +31 -0
- package/dist/events/spine.d.ts.map +1 -0
- package/dist/events/spine.js +68 -0
- package/dist/events/spine.js.map +1 -0
- package/dist/events/trace.d.ts +24 -0
- package/dist/events/trace.d.ts.map +1 -0
- package/dist/events/trace.js +33 -0
- package/dist/events/trace.js.map +1 -0
- package/dist/host.d.ts +65 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +94 -0
- package/dist/host.js.map +1 -0
- package/dist/library/importer.d.ts +29 -0
- package/dist/library/importer.d.ts.map +1 -0
- package/dist/library/importer.js +145 -0
- package/dist/library/importer.js.map +1 -0
- package/dist/library/library-cache.d.ts +46 -0
- package/dist/library/library-cache.d.ts.map +1 -0
- package/dist/library/library-cache.js +152 -0
- package/dist/library/library-cache.js.map +1 -0
- package/dist/library/library-config.d.ts +12 -0
- package/dist/library/library-config.d.ts.map +1 -0
- package/dist/library/library-config.js +10 -0
- package/dist/library/library-config.js.map +1 -0
- package/dist/library/library-scanner.d.ts +62 -0
- package/dist/library/library-scanner.d.ts.map +1 -0
- package/dist/library/library-scanner.js +336 -0
- package/dist/library/library-scanner.js.map +1 -0
- package/dist/library/library-service.d.ts +65 -0
- package/dist/library/library-service.d.ts.map +1 -0
- package/dist/library/library-service.js +197 -0
- package/dist/library/library-service.js.map +1 -0
- package/dist/library/metadata-matcher.d.ts +56 -0
- package/dist/library/metadata-matcher.d.ts.map +1 -0
- package/dist/library/metadata-matcher.js +207 -0
- package/dist/library/metadata-matcher.js.map +1 -0
- package/dist/library/naming.d.ts +8 -0
- package/dist/library/naming.d.ts.map +1 -0
- package/dist/library/naming.js +47 -0
- package/dist/library/naming.js.map +1 -0
- package/dist/library/scan-controller.d.ts +95 -0
- package/dist/library/scan-controller.d.ts.map +1 -0
- package/dist/library/scan-controller.js +379 -0
- package/dist/library/scan-controller.js.map +1 -0
- package/dist/library/source-resolver.d.ts +17 -0
- package/dist/library/source-resolver.d.ts.map +1 -0
- package/dist/library/source-resolver.js +75 -0
- package/dist/library/source-resolver.js.map +1 -0
- package/dist/logging/logger.d.ts +3 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +107 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/main.d.ts +36 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +229 -0
- package/dist/main.js.map +1 -0
- package/dist/plugins/context.d.ts +10 -0
- package/dist/plugins/context.d.ts.map +1 -0
- package/dist/plugins/context.js +110 -0
- package/dist/plugins/context.js.map +1 -0
- package/dist/plugins/discovery.d.ts +2 -0
- package/dist/plugins/discovery.d.ts.map +1 -0
- package/dist/plugins/discovery.js +32 -0
- package/dist/plugins/discovery.js.map +1 -0
- package/dist/plugins/loader.d.ts +9 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +245 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/state/request-intake.d.ts +26 -0
- package/dist/state/request-intake.d.ts.map +1 -0
- package/dist/state/request-intake.js +74 -0
- package/dist/state/request-intake.js.map +1 -0
- package/dist/state/state-store.d.ts +146 -0
- package/dist/state/state-store.d.ts.map +1 -0
- package/dist/state/state-store.js +465 -0
- package/dist/state/state-store.js.map +1 -0
- package/dist/state/trace-store.d.ts +12 -0
- package/dist/state/trace-store.d.ts.map +1 -0
- package/dist/state/trace-store.js +24 -0
- package/dist/state/trace-store.js.map +1 -0
- package/dist/types/plugin.d.ts +93 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/plugin.js +4 -0
- package/dist/types/plugin.js.map +1 -0
- package/dist/web/index.d.ts +10 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +153 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/routes/activity.d.ts +6 -0
- package/dist/web/routes/activity.d.ts.map +1 -0
- package/dist/web/routes/activity.js +31 -0
- package/dist/web/routes/activity.js.map +1 -0
- package/dist/web/routes/auth.d.ts +6 -0
- package/dist/web/routes/auth.d.ts.map +1 -0
- package/dist/web/routes/auth.js +142 -0
- package/dist/web/routes/auth.js.map +1 -0
- package/dist/web/routes/config.d.ts +7 -0
- package/dist/web/routes/config.d.ts.map +1 -0
- package/dist/web/routes/config.js +204 -0
- package/dist/web/routes/config.js.map +1 -0
- package/dist/web/routes/content-types.d.ts +6 -0
- package/dist/web/routes/content-types.d.ts.map +1 -0
- package/dist/web/routes/content-types.js +29 -0
- package/dist/web/routes/content-types.js.map +1 -0
- package/dist/web/routes/discovery.d.ts +6 -0
- package/dist/web/routes/discovery.d.ts.map +1 -0
- package/dist/web/routes/discovery.js +111 -0
- package/dist/web/routes/discovery.js.map +1 -0
- package/dist/web/routes/library-import.d.ts +6 -0
- package/dist/web/routes/library-import.d.ts.map +1 -0
- package/dist/web/routes/library-import.js +120 -0
- package/dist/web/routes/library-import.js.map +1 -0
- package/dist/web/routes/library-management.d.ts +6 -0
- package/dist/web/routes/library-management.d.ts.map +1 -0
- package/dist/web/routes/library-management.js +120 -0
- package/dist/web/routes/library-management.js.map +1 -0
- package/dist/web/routes/library.d.ts +6 -0
- package/dist/web/routes/library.d.ts.map +1 -0
- package/dist/web/routes/library.js +28 -0
- package/dist/web/routes/library.js.map +1 -0
- package/dist/web/routes/oidc.d.ts +9 -0
- package/dist/web/routes/oidc.d.ts.map +1 -0
- package/dist/web/routes/oidc.js +203 -0
- package/dist/web/routes/oidc.js.map +1 -0
- package/dist/web/routes/requests.d.ts +6 -0
- package/dist/web/routes/requests.d.ts.map +1 -0
- package/dist/web/routes/requests.js +183 -0
- package/dist/web/routes/requests.js.map +1 -0
- package/dist/web/routes/sse.d.ts +6 -0
- package/dist/web/routes/sse.d.ts.map +1 -0
- package/dist/web/routes/sse.js +54 -0
- package/dist/web/routes/sse.js.map +1 -0
- package/package.json +33 -0
- package/package.json.license +2 -0
- package/src/auth/auth-service.ts +333 -0
- package/src/auth/http-server.ts +249 -0
- package/src/auth/jwt-secret.ts +65 -0
- package/src/auth/oidc/oidc-flow.ts +266 -0
- package/src/auth/oidc/oidc-provider-service.ts +174 -0
- package/src/auth/oidc/user-service.ts +227 -0
- package/src/auth/rate-limiter.ts +85 -0
- package/src/config/config-service.ts +434 -0
- package/src/config/validate.ts +153 -0
- package/src/content-types/registry.ts +194 -0
- package/src/datastore/db.ts +38 -0
- package/src/datastore/encrypt.ts +107 -0
- package/src/datastore/migrations.ts +367 -0
- package/src/datastore/storage.ts +83 -0
- package/src/events/event-bus.ts +180 -0
- package/src/events/spine.ts +87 -0
- package/src/events/trace.ts +38 -0
- package/src/host.ts +130 -0
- package/src/library/importer.ts +174 -0
- package/src/library/library-cache.ts +212 -0
- package/src/library/library-config.ts +22 -0
- package/src/library/library-scanner.ts +404 -0
- package/src/library/library-service.ts +282 -0
- package/src/library/metadata-matcher.ts +285 -0
- package/src/library/naming.ts +59 -0
- package/src/library/scan-controller.ts +490 -0
- package/src/library/source-resolver.ts +92 -0
- package/src/logging/logger.ts +115 -0
- package/src/main.ts +364 -0
- package/src/plugins/context.ts +138 -0
- package/src/plugins/discovery.ts +29 -0
- package/src/plugins/loader.ts +309 -0
- package/src/state/request-intake.ts +101 -0
- package/src/state/state-store.ts +621 -0
- package/src/state/trace-store.ts +27 -0
- package/src/types/plugin.ts +110 -0
- package/src/web/index.ts +182 -0
- package/src/web/routes/activity.ts +41 -0
- package/src/web/routes/auth.ts +170 -0
- package/src/web/routes/config.ts +282 -0
- package/src/web/routes/content-types.ts +42 -0
- package/src/web/routes/discovery.ts +156 -0
- package/src/web/routes/library-import.ts +196 -0
- package/src/web/routes/library-management.ts +140 -0
- package/src/web/routes/library.ts +46 -0
- package/src/web/routes/oidc.ts +260 -0
- package/src/web/routes/requests.ts +250 -0
- package/src/web/routes/sse.ts +92 -0
- package/tests/auth.test.ts +510 -0
- package/tests/config-route.test.ts +345 -0
- package/tests/config.test.ts +612 -0
- package/tests/content-types.test.ts +376 -0
- package/tests/datastore.test.ts +345 -0
- package/tests/e2e.test.ts +430 -0
- package/tests/event-bus.test.ts +925 -0
- package/tests/host.test.ts +449 -0
- package/tests/library-cache.test.ts +171 -0
- package/tests/library-scanner.test.ts +177 -0
- package/tests/library.test.ts +210 -0
- package/tests/loader-context.test.ts +644 -0
- package/tests/logger-jsonl.test.ts +154 -0
- package/tests/metadata-matcher.test.ts +198 -0
- package/tests/oidc.test.ts +218 -0
- package/tests/scan-controller.test.ts +328 -0
- package/tests/source-resolver.test.ts +100 -0
- package/tests/starr-video-integration.test.ts +153 -0
- package/tests/state-store.test.ts +700 -0
- package/tests/trace-history.test.ts +356 -0
- package/tsconfig.json +8 -0
- package/tsconfig.json.license +2 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
// SPDX-License-Identifier: 0BSD
|
|
2
|
+
// SPDX-FileCopyrightText: 2025 Digi <digitalpaws@proton.me>
|
|
3
|
+
|
|
4
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
5
|
+
import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import Database from "better-sqlite3";
|
|
9
|
+
import { createLogger } from "../src/logging/logger.js";
|
|
10
|
+
import { createContext } from "../src/plugins/context.js";
|
|
11
|
+
import { loadPlugins, stopPlugins, reloadPlugin } from "../src/plugins/loader.js";
|
|
12
|
+
import { runMigrations, CORE_MIGRATIONS } from "../src/datastore/migrations.js";
|
|
13
|
+
import { Starr } from "../src/host.js";
|
|
14
|
+
|
|
15
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
function createTestDb(): Database.Database {
|
|
18
|
+
const db = new Database(":memory:");
|
|
19
|
+
db.pragma("journal_mode = WAL");
|
|
20
|
+
db.pragma("foreign_keys = ON");
|
|
21
|
+
runMigrations(db, CORE_MIGRATIONS, createLogger("test", "silent"));
|
|
22
|
+
return db;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function writePlugin(
|
|
26
|
+
baseDir: string,
|
|
27
|
+
dirName: string,
|
|
28
|
+
code: string,
|
|
29
|
+
): Promise<string> {
|
|
30
|
+
const dir = path.join(baseDir, dirName);
|
|
31
|
+
await mkdir(dir, { recursive: true });
|
|
32
|
+
await writeFile(
|
|
33
|
+
path.join(dir, "package.json"),
|
|
34
|
+
JSON.stringify({ type: "module", main: "./index.js" }),
|
|
35
|
+
);
|
|
36
|
+
await writeFile(path.join(dir, "index.js"), code);
|
|
37
|
+
return dir;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Phase 1: PluginStorage.deleteAll ─────────────────────────────────
|
|
41
|
+
|
|
42
|
+
describe("PluginStorage.deleteAll", () => {
|
|
43
|
+
let db: Database.Database;
|
|
44
|
+
|
|
45
|
+
beforeAll(() => {
|
|
46
|
+
db = createTestDb();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
afterAll(() => {
|
|
50
|
+
db.close();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("removes all keys for a plugin", async () => {
|
|
54
|
+
const logger = createLogger("core", "silent");
|
|
55
|
+
const starr = new Starr({
|
|
56
|
+
pluginsDir: "/tmp",
|
|
57
|
+
apiVersion: "1.0.0",
|
|
58
|
+
logger,
|
|
59
|
+
});
|
|
60
|
+
const ctx = createContext(starr, "/tmp/test-plugin", db);
|
|
61
|
+
ctx.define({
|
|
62
|
+
id: "test-plugin",
|
|
63
|
+
version: "1.0.0",
|
|
64
|
+
starrApiVersion: "^1.0.0",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await ctx.storage.set("key1", "val1");
|
|
68
|
+
await ctx.storage.set("key2", "val2");
|
|
69
|
+
|
|
70
|
+
const before = await ctx.storage.list();
|
|
71
|
+
expect(before).toHaveLength(2);
|
|
72
|
+
|
|
73
|
+
await ctx.storage.deleteAll();
|
|
74
|
+
|
|
75
|
+
const after = await ctx.storage.list();
|
|
76
|
+
expect(after).toHaveLength(0);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("does not affect another plugin's keys", async () => {
|
|
80
|
+
const logger = createLogger("core", "silent");
|
|
81
|
+
const starr = new Starr({
|
|
82
|
+
pluginsDir: "/tmp",
|
|
83
|
+
apiVersion: "1.0.0",
|
|
84
|
+
logger,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const ctxA = createContext(starr, "/tmp/a", db);
|
|
88
|
+
ctxA.define({ id: "a", version: "1.0.0", starrApiVersion: "^1.0.0" });
|
|
89
|
+
await ctxA.storage.set("key", "a-value");
|
|
90
|
+
|
|
91
|
+
const ctxB = createContext(starr, "/tmp/b", db);
|
|
92
|
+
ctxB.define({ id: "b", version: "1.0.0", starrApiVersion: "^1.0.0" });
|
|
93
|
+
await ctxB.storage.set("key", "b-value");
|
|
94
|
+
|
|
95
|
+
await ctxA.storage.deleteAll();
|
|
96
|
+
|
|
97
|
+
const aAfter = await ctxA.storage.list();
|
|
98
|
+
const bAfter = await ctxB.storage.list();
|
|
99
|
+
|
|
100
|
+
expect(aAfter).toHaveLength(0);
|
|
101
|
+
expect(bAfter).toHaveLength(1);
|
|
102
|
+
expect(bAfter[0].value).toBe("b-value");
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ── Phase 2: context unsubscribeAll ──────────────────────────────────
|
|
107
|
+
|
|
108
|
+
describe("context unsubscribeAll", () => {
|
|
109
|
+
let db: Database.Database;
|
|
110
|
+
|
|
111
|
+
beforeAll(() => {
|
|
112
|
+
db = createTestDb();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
afterAll(() => {
|
|
116
|
+
db.close();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("removes all event subscriptions", () => {
|
|
120
|
+
const logger = createLogger("core", "silent");
|
|
121
|
+
const starr = new Starr({
|
|
122
|
+
pluginsDir: "/tmp",
|
|
123
|
+
apiVersion: "1.0.0",
|
|
124
|
+
logger,
|
|
125
|
+
});
|
|
126
|
+
const ctx = createContext(starr, "/tmp/test-plugin", db);
|
|
127
|
+
ctx.define({
|
|
128
|
+
id: "test-plugin",
|
|
129
|
+
version: "1.0.0",
|
|
130
|
+
starrApiVersion: "^1.0.0",
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const calls: string[] = [];
|
|
134
|
+
ctx.events.on("test.event", () => calls.push("a"));
|
|
135
|
+
ctx.events.on("test.event", () => calls.push("b"));
|
|
136
|
+
|
|
137
|
+
ctx.unsubscribeAll();
|
|
138
|
+
|
|
139
|
+
ctx.events.emit("test.event", {});
|
|
140
|
+
expect(calls).toHaveLength(0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("is safe to call when no subscriptions exist", () => {
|
|
144
|
+
const logger = createLogger("core", "silent");
|
|
145
|
+
const starr = new Starr({
|
|
146
|
+
pluginsDir: "/tmp",
|
|
147
|
+
apiVersion: "1.0.0",
|
|
148
|
+
logger,
|
|
149
|
+
});
|
|
150
|
+
const ctx = createContext(starr, "/tmp/test-plugin", db);
|
|
151
|
+
ctx.define({
|
|
152
|
+
id: "test-plugin",
|
|
153
|
+
version: "1.0.0",
|
|
154
|
+
starrApiVersion: "^1.0.0",
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(() => ctx.unsubscribeAll()).not.toThrow();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// ── Phase 4: Rollback tests ──────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
describe("plugin register failure rollback", () => {
|
|
164
|
+
let tmpDir: string;
|
|
165
|
+
|
|
166
|
+
beforeAll(async () => {
|
|
167
|
+
tmpDir = await mkdtemp(path.join(tmpdir(), "starr-rollback-"));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
afterAll(async () => {
|
|
171
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("rolls back event subscriptions when a plugin fails", async () => {
|
|
175
|
+
const subDir = await mkdtemp(path.join(tmpdir(), "starr-sub-rollback-"));
|
|
176
|
+
try {
|
|
177
|
+
// Plugin A subscribes to an event, then throws
|
|
178
|
+
await writePlugin(
|
|
179
|
+
subDir,
|
|
180
|
+
"failing-plugin",
|
|
181
|
+
`
|
|
182
|
+
export default function register(ctx) {
|
|
183
|
+
ctx.define({ id: "failing", version: "1.0.0", starrApiVersion: "^1.0.0" });
|
|
184
|
+
ctx.events.on("test.event", () => {});
|
|
185
|
+
throw new Error("boom after subscribe");
|
|
186
|
+
}
|
|
187
|
+
`,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
await writePlugin(
|
|
191
|
+
subDir,
|
|
192
|
+
"observer-plugin",
|
|
193
|
+
`
|
|
194
|
+
export default function register(ctx) {
|
|
195
|
+
ctx.define({ id: "observer", version: "1.0.0", starrApiVersion: "^1.0.0" });
|
|
196
|
+
let count = 0;
|
|
197
|
+
ctx.events.on("test.event", () => { count++; });
|
|
198
|
+
return {
|
|
199
|
+
start() {
|
|
200
|
+
ctx.events.emit("test.event", {});
|
|
201
|
+
globalThis.__starr_observer_count = count;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
`,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const db = createTestDb();
|
|
209
|
+
const logger = createLogger("core", "error");
|
|
210
|
+
const starr = new Starr({
|
|
211
|
+
pluginsDir: subDir,
|
|
212
|
+
apiVersion: "1.0.0",
|
|
213
|
+
logger,
|
|
214
|
+
});
|
|
215
|
+
starr.datastore = { db, dbPath: ":memory:", close: () => {} };
|
|
216
|
+
|
|
217
|
+
await loadPlugins(starr);
|
|
218
|
+
|
|
219
|
+
// The failing plugin's subscription should have been rolled back.
|
|
220
|
+
// observer emits test.event in start — count should be 1 (only observer's
|
|
221
|
+
// own handler), not 2 (the failing plugin's handler should be gone).
|
|
222
|
+
expect((globalThis as any).__starr_observer_count).toBe(1);
|
|
223
|
+
|
|
224
|
+
delete (globalThis as any).__starr_observer_count;
|
|
225
|
+
db.close();
|
|
226
|
+
} finally {
|
|
227
|
+
await rm(subDir, { recursive: true, force: true });
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("rolls back content-type descriptors when a plugin fails", async () => {
|
|
232
|
+
const subDir = await mkdtemp(
|
|
233
|
+
path.join(tmpdir(), "starr-ct-rollback-"),
|
|
234
|
+
);
|
|
235
|
+
try {
|
|
236
|
+
await writePlugin(
|
|
237
|
+
subDir,
|
|
238
|
+
"ct-failing",
|
|
239
|
+
`
|
|
240
|
+
export default function register(ctx) {
|
|
241
|
+
ctx.define({ id: "ct-fail", version: "1.0.0", starrApiVersion: "^1.0.0" });
|
|
242
|
+
ctx.contentTypes.register({
|
|
243
|
+
id: "test/movie@1",
|
|
244
|
+
name: "Movie",
|
|
245
|
+
identity: ["tmdb"],
|
|
246
|
+
fields: [],
|
|
247
|
+
});
|
|
248
|
+
throw new Error("boom after content type");
|
|
249
|
+
}
|
|
250
|
+
`,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const db = createTestDb();
|
|
254
|
+
const logger = createLogger("core", "error");
|
|
255
|
+
const starr = new Starr({
|
|
256
|
+
pluginsDir: subDir,
|
|
257
|
+
apiVersion: "1.0.0",
|
|
258
|
+
logger,
|
|
259
|
+
});
|
|
260
|
+
starr.datastore = { db, dbPath: ":memory:", close: () => {} };
|
|
261
|
+
|
|
262
|
+
await loadPlugins(starr);
|
|
263
|
+
|
|
264
|
+
// The content-type descriptor should have been rolled back
|
|
265
|
+
expect(
|
|
266
|
+
starr.contentTypeRegistry.get("test/movie@1"),
|
|
267
|
+
).toBeUndefined();
|
|
268
|
+
db.close();
|
|
269
|
+
} finally {
|
|
270
|
+
await rm(subDir, { recursive: true, force: true });
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("rolls back plugin storage when a plugin fails", async () => {
|
|
275
|
+
const subDir = await mkdtemp(
|
|
276
|
+
path.join(tmpdir(), "starr-storage-rollback-"),
|
|
277
|
+
);
|
|
278
|
+
try {
|
|
279
|
+
await writePlugin(
|
|
280
|
+
subDir,
|
|
281
|
+
"storage-failing",
|
|
282
|
+
`
|
|
283
|
+
export default async function register(ctx) {
|
|
284
|
+
ctx.define({ id: "storage-fail", version: "1.0.0", starrApiVersion: "^1.0.0" });
|
|
285
|
+
await ctx.storage.set("key", "should-be-rolled-back");
|
|
286
|
+
throw new Error("boom after storage write");
|
|
287
|
+
}
|
|
288
|
+
`,
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const db = createTestDb();
|
|
292
|
+
const logger = createLogger("core", "error");
|
|
293
|
+
const starr = new Starr({
|
|
294
|
+
pluginsDir: subDir,
|
|
295
|
+
apiVersion: "1.0.0",
|
|
296
|
+
logger,
|
|
297
|
+
});
|
|
298
|
+
starr.datastore = { db, dbPath: ":memory:", close: () => {} };
|
|
299
|
+
|
|
300
|
+
await loadPlugins(starr);
|
|
301
|
+
|
|
302
|
+
// Storage should be cleaned up — create a new context to check
|
|
303
|
+
const checkCtx = createContext(
|
|
304
|
+
starr,
|
|
305
|
+
"/tmp/storage-fail",
|
|
306
|
+
db,
|
|
307
|
+
);
|
|
308
|
+
checkCtx.define({
|
|
309
|
+
id: "storage-fail",
|
|
310
|
+
version: "1.0.0",
|
|
311
|
+
starrApiVersion: "^1.0.0",
|
|
312
|
+
});
|
|
313
|
+
const val = await checkCtx.storage.get("key");
|
|
314
|
+
expect(val).toBeUndefined();
|
|
315
|
+
db.close();
|
|
316
|
+
} finally {
|
|
317
|
+
await rm(subDir, { recursive: true, force: true });
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// ── Phase 3: Start/stop timeout tests ────────────────────────────────
|
|
323
|
+
|
|
324
|
+
describe("start/stop timeouts", () => {
|
|
325
|
+
it("start timeout does not block other plugins from starting", async () => {
|
|
326
|
+
const tmpDir = await mkdtemp(path.join(tmpdir(), "starr-timeouts-start-"));
|
|
327
|
+
await writePlugin(
|
|
328
|
+
tmpDir,
|
|
329
|
+
"slow-starter",
|
|
330
|
+
`
|
|
331
|
+
export default function register(ctx) {
|
|
332
|
+
ctx.define({ id: "slow", version: "1.0.0", starrApiVersion: "^1.0.0" });
|
|
333
|
+
return {
|
|
334
|
+
start() {
|
|
335
|
+
return new Promise(() => {}); // never resolves
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
`,
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
await writePlugin(
|
|
343
|
+
tmpDir,
|
|
344
|
+
"fast-starter",
|
|
345
|
+
`
|
|
346
|
+
export default function register(ctx) {
|
|
347
|
+
ctx.define({ id: "fast", version: "1.0.0", starrApiVersion: "^1.0.0" });
|
|
348
|
+
return {
|
|
349
|
+
start() {
|
|
350
|
+
globalThis.__starr_fast_started = true;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
`,
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const db = createTestDb();
|
|
358
|
+
const logger = createLogger("core", "error");
|
|
359
|
+
const starr = new Starr({
|
|
360
|
+
pluginsDir: tmpDir,
|
|
361
|
+
apiVersion: "1.0.0",
|
|
362
|
+
logger,
|
|
363
|
+
});
|
|
364
|
+
starr.datastore = { db, dbPath: ":memory:", close: () => {} };
|
|
365
|
+
|
|
366
|
+
// This should complete (not hang) — fast starter should start,
|
|
367
|
+
// slow starter should time out after 30s.
|
|
368
|
+
await loadPlugins(starr);
|
|
369
|
+
|
|
370
|
+
expect((globalThis as any).__starr_fast_started).toBe(true);
|
|
371
|
+
|
|
372
|
+
// slow plugin should NOT be in runtimes (timed out → startOrder = -1)
|
|
373
|
+
const runtimes = starr.getRuntimes();
|
|
374
|
+
const slowRuntime = runtimes.find((r) => r.meta.id === "slow");
|
|
375
|
+
expect(slowRuntime?.startOrder).toBe(-1);
|
|
376
|
+
|
|
377
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
378
|
+
delete (globalThis as any).__starr_fast_started;
|
|
379
|
+
db.close();
|
|
380
|
+
}, 40_000); // Extended timeout — the test waits for the 30s start timeout
|
|
381
|
+
|
|
382
|
+
it("stop timeout does not block shutdown", async () => {
|
|
383
|
+
const tmpDir = await mkdtemp(path.join(tmpdir(), "starr-timeouts-stop-"));
|
|
384
|
+
await writePlugin(
|
|
385
|
+
tmpDir,
|
|
386
|
+
"slow-stopper",
|
|
387
|
+
`
|
|
388
|
+
export default function register(ctx) {
|
|
389
|
+
ctx.define({ id: "slow-stop", version: "1.0.0", starrApiVersion: "^1.0.0" });
|
|
390
|
+
return {
|
|
391
|
+
stop() {
|
|
392
|
+
return new Promise(() => {}); // never resolves
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
`,
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
await writePlugin(
|
|
400
|
+
tmpDir,
|
|
401
|
+
"fast-stopper",
|
|
402
|
+
`
|
|
403
|
+
export default function register(ctx) {
|
|
404
|
+
ctx.define({ id: "fast-stop", version: "1.0.0", starrApiVersion: "^1.0.0" });
|
|
405
|
+
return {
|
|
406
|
+
stop() {
|
|
407
|
+
globalThis.__starr_fast_stopped = true;
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
`,
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
const db = createTestDb();
|
|
415
|
+
const logger = createLogger("core", "error");
|
|
416
|
+
const starr = new Starr({
|
|
417
|
+
pluginsDir: tmpDir,
|
|
418
|
+
apiVersion: "1.0.0",
|
|
419
|
+
logger,
|
|
420
|
+
});
|
|
421
|
+
starr.datastore = { db, dbPath: ":memory:", close: () => {} };
|
|
422
|
+
|
|
423
|
+
await loadPlugins(starr);
|
|
424
|
+
await stopPlugins(starr);
|
|
425
|
+
|
|
426
|
+
// Fast stopper should have run
|
|
427
|
+
expect((globalThis as any).__starr_fast_stopped).toBe(true);
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
431
|
+
delete (globalThis as any).__starr_fast_stopped;
|
|
432
|
+
db.close();
|
|
433
|
+
}, 20_000); // Extended timeout — the test waits for the 10s stop timeout
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// ── Phase 5: reloadPlugin tests ──────────────────────────────────────
|
|
437
|
+
|
|
438
|
+
describe("reloadPlugin", () => {
|
|
439
|
+
it("stops, re-imports, and restarts a plugin", async () => {
|
|
440
|
+
const tmpDir = await mkdtemp(path.join(tmpdir(), "starr-reload-seq-"));
|
|
441
|
+
const events: string[] = [];
|
|
442
|
+
const key = "__starr_reload_seq";
|
|
443
|
+
(globalThis as any)[key] = events;
|
|
444
|
+
|
|
445
|
+
await writePlugin(
|
|
446
|
+
tmpDir,
|
|
447
|
+
"reloadable",
|
|
448
|
+
`
|
|
449
|
+
const k = "${key}";
|
|
450
|
+
export default function register(ctx) {
|
|
451
|
+
globalThis[k].push("register");
|
|
452
|
+
ctx.define({ id: "reloadable", version: "1.0.0", starrApiVersion: "^1.0.0" });
|
|
453
|
+
return {
|
|
454
|
+
start() { globalThis[k].push("start"); },
|
|
455
|
+
stop() { globalThis[k].push("stop"); },
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
`,
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
const db = createTestDb();
|
|
462
|
+
const logger = createLogger("core", "error");
|
|
463
|
+
const starr = new Starr({
|
|
464
|
+
pluginsDir: tmpDir,
|
|
465
|
+
apiVersion: "1.0.0",
|
|
466
|
+
logger,
|
|
467
|
+
});
|
|
468
|
+
starr.datastore = { db, dbPath: ":memory:", close: () => {} };
|
|
469
|
+
|
|
470
|
+
await loadPlugins(starr);
|
|
471
|
+
|
|
472
|
+
// Initial: register → start
|
|
473
|
+
expect(events).toEqual(["register", "start"]);
|
|
474
|
+
|
|
475
|
+
// Clear events for reload
|
|
476
|
+
events.length = 0;
|
|
477
|
+
|
|
478
|
+
await reloadPlugin(starr, "reloadable");
|
|
479
|
+
|
|
480
|
+
// Reload: stop → register → start
|
|
481
|
+
expect(events).toEqual(["stop", "register", "start"]);
|
|
482
|
+
|
|
483
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
484
|
+
delete (globalThis as any)[key];
|
|
485
|
+
db.close();
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it("reload picks up code changes (cache bust)", async () => {
|
|
489
|
+
const tmpDir = await mkdtemp(path.join(tmpdir(), "starr-reload-cache-"));
|
|
490
|
+
let value = 1;
|
|
491
|
+
const key = "__starr_reload_value";
|
|
492
|
+
|
|
493
|
+
const dir = path.join(tmpDir, "versioned-plugin");
|
|
494
|
+
await mkdir(dir, { recursive: true });
|
|
495
|
+
await writeFile(
|
|
496
|
+
path.join(dir, "package.json"),
|
|
497
|
+
JSON.stringify({ type: "module", main: "./index.js" }),
|
|
498
|
+
);
|
|
499
|
+
await writeFile(
|
|
500
|
+
path.join(dir, "index.js"),
|
|
501
|
+
`
|
|
502
|
+
export default function register(ctx) {
|
|
503
|
+
ctx.define({ id: "versioned", version: "1.0.0", starrApiVersion: "^1.0.0" });
|
|
504
|
+
return {
|
|
505
|
+
start() {
|
|
506
|
+
globalThis["${key}"] = ${value};
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
`,
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
const db = createTestDb();
|
|
514
|
+
const logger = createLogger("core", "error");
|
|
515
|
+
const starr = new Starr({
|
|
516
|
+
pluginsDir: tmpDir,
|
|
517
|
+
apiVersion: "1.0.0",
|
|
518
|
+
logger,
|
|
519
|
+
});
|
|
520
|
+
starr.datastore = { db, dbPath: ":memory:", close: () => {} };
|
|
521
|
+
|
|
522
|
+
await loadPlugins(starr);
|
|
523
|
+
expect((globalThis as any)[key]).toBe(1);
|
|
524
|
+
|
|
525
|
+
// Update the source file
|
|
526
|
+
value = 2;
|
|
527
|
+
await writeFile(
|
|
528
|
+
path.join(dir, "index.js"),
|
|
529
|
+
`
|
|
530
|
+
export default function register(ctx) {
|
|
531
|
+
ctx.define({ id: "versioned", version: "1.0.0", starrApiVersion: "^1.0.0" });
|
|
532
|
+
return {
|
|
533
|
+
start() {
|
|
534
|
+
globalThis["${key}"] = ${value};
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
`,
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
await reloadPlugin(starr, "versioned");
|
|
542
|
+
expect((globalThis as any)[key]).toBe(2);
|
|
543
|
+
|
|
544
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
545
|
+
delete (globalThis as any)[key];
|
|
546
|
+
db.close();
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it("reload of unknown plugin does not throw", async () => {
|
|
550
|
+
const tmpDir = await mkdtemp(path.join(tmpdir(), "starr-reload-unknown-"));
|
|
551
|
+
const db = createTestDb();
|
|
552
|
+
const logger = createLogger("core", "error");
|
|
553
|
+
const starr = new Starr({
|
|
554
|
+
pluginsDir: tmpDir,
|
|
555
|
+
apiVersion: "1.0.0",
|
|
556
|
+
logger,
|
|
557
|
+
});
|
|
558
|
+
starr.datastore = { db, dbPath: ":memory:", close: () => {} };
|
|
559
|
+
|
|
560
|
+
await expect(
|
|
561
|
+
reloadPlugin(starr, "nonexistent"),
|
|
562
|
+
).resolves.toBeUndefined();
|
|
563
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
564
|
+
db.close();
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// ── Phase 4: Comprehensive rollback ──────────────────────────────────
|
|
569
|
+
|
|
570
|
+
describe("comprehensive rollback (L5)", () => {
|
|
571
|
+
let tmpDir: string;
|
|
572
|
+
|
|
573
|
+
beforeAll(async () => {
|
|
574
|
+
tmpDir = await mkdtemp(
|
|
575
|
+
path.join(tmpdir(), "starr-comprehensive-"),
|
|
576
|
+
);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
afterAll(async () => {
|
|
580
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it("rolls back subscriptions, descriptors, and storage on failure", async () => {
|
|
584
|
+
await writePlugin(
|
|
585
|
+
tmpDir,
|
|
586
|
+
"multi-fail",
|
|
587
|
+
`
|
|
588
|
+
export default async function register(ctx) {
|
|
589
|
+
ctx.define({ id: "multi-fail", version: "1.0.0", starrApiVersion: "^1.0.0" });
|
|
590
|
+
ctx.events.on("test.event", () => {});
|
|
591
|
+
ctx.contentTypes.register({
|
|
592
|
+
id: "fail/type@1",
|
|
593
|
+
name: "FailType",
|
|
594
|
+
identity: ["x"],
|
|
595
|
+
fields: [],
|
|
596
|
+
});
|
|
597
|
+
await ctx.storage.set("key", "value");
|
|
598
|
+
throw new Error("late failure");
|
|
599
|
+
}
|
|
600
|
+
`,
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
await writePlugin(
|
|
604
|
+
tmpDir,
|
|
605
|
+
"clean-plugin",
|
|
606
|
+
`
|
|
607
|
+
export default function register(ctx) {
|
|
608
|
+
ctx.define({ id: "clean", version: "1.0.0", starrApiVersion: "^1.0.0" });
|
|
609
|
+
}
|
|
610
|
+
`,
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
const db = createTestDb();
|
|
614
|
+
const logger = createLogger("core", "error");
|
|
615
|
+
const starr = new Starr({
|
|
616
|
+
pluginsDir: tmpDir,
|
|
617
|
+
apiVersion: "1.0.0",
|
|
618
|
+
logger,
|
|
619
|
+
});
|
|
620
|
+
starr.datastore = { db, dbPath: ":memory:", close: () => {} };
|
|
621
|
+
|
|
622
|
+
await loadPlugins(starr);
|
|
623
|
+
|
|
624
|
+
// multi-fail should be rejected
|
|
625
|
+
const runtimes = starr.getRuntimes();
|
|
626
|
+
expect(runtimes.map((r) => r.meta.id)).toEqual(["clean"]);
|
|
627
|
+
|
|
628
|
+
// Content-type descriptor should be rolled back
|
|
629
|
+
expect(
|
|
630
|
+
starr.contentTypeRegistry.get("fail/type@1"),
|
|
631
|
+
).toBeUndefined();
|
|
632
|
+
|
|
633
|
+
// Storage should be cleaned up
|
|
634
|
+
const checkCtx = createContext(starr, "/tmp/multi-fail", db);
|
|
635
|
+
checkCtx.define({
|
|
636
|
+
id: "multi-fail",
|
|
637
|
+
version: "1.0.0",
|
|
638
|
+
starrApiVersion: "^1.0.0",
|
|
639
|
+
});
|
|
640
|
+
const val = await checkCtx.storage.get("key");
|
|
641
|
+
expect(val).toBeUndefined();
|
|
642
|
+
db.close();
|
|
643
|
+
});
|
|
644
|
+
});
|