@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
package/src/main.ts
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
// SPDX-License-Identifier: 0BSD
|
|
2
|
+
// SPDX-FileCopyrightText: 2025 Digi <digitalpaws@proton.me>
|
|
3
|
+
|
|
4
|
+
export type {
|
|
5
|
+
PluginContext,
|
|
6
|
+
PluginLifecycle,
|
|
7
|
+
PluginMeta,
|
|
8
|
+
ConfigSchema,
|
|
9
|
+
ConfigField,
|
|
10
|
+
Logger,
|
|
11
|
+
EventBus,
|
|
12
|
+
PluginStorage,
|
|
13
|
+
ContentTypeRegistry,
|
|
14
|
+
RegisterFn,
|
|
15
|
+
StarrEvent,
|
|
16
|
+
EventHandler,
|
|
17
|
+
Unsubscribe,
|
|
18
|
+
EmitOpts,
|
|
19
|
+
RequestOpts,
|
|
20
|
+
ConnectionTestResult,
|
|
21
|
+
} from "./types/plugin.js";
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
SPINE_EVENTS,
|
|
25
|
+
SPINE_NAMESPACES,
|
|
26
|
+
isSpineType,
|
|
27
|
+
validateSpineEmission,
|
|
28
|
+
} from "./events/spine.js";
|
|
29
|
+
|
|
30
|
+
export { EventBusImpl } from "./events/event-bus.js";
|
|
31
|
+
|
|
32
|
+
export { Starr } from "./host.js";
|
|
33
|
+
export type { StarrOptions, PluginRuntime } from "./host.js";
|
|
34
|
+
export { createLogger } from "./logging/logger.js";
|
|
35
|
+
export { discoverPluginDirs } from "./plugins/discovery.js";
|
|
36
|
+
export { createContext } from "./plugins/context.js";
|
|
37
|
+
export { loadPlugins, stopPlugins } from "./plugins/loader.js";
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
ContentTypeRegistryImpl,
|
|
41
|
+
validateDescriptor,
|
|
42
|
+
computeContentIdentity,
|
|
43
|
+
areSameItem,
|
|
44
|
+
} from "./content-types/registry.js";
|
|
45
|
+
|
|
46
|
+
export { StateStore } from "./state/state-store.js";
|
|
47
|
+
export type {
|
|
48
|
+
TrackedRequest,
|
|
49
|
+
TrackedRequestData,
|
|
50
|
+
RequestStatus,
|
|
51
|
+
TraceEntry,
|
|
52
|
+
} from "./state/state-store.js";
|
|
53
|
+
export { requestIntake } from "./state/request-intake.js";
|
|
54
|
+
export type { RequestIntakeInput } from "./state/request-intake.js";
|
|
55
|
+
|
|
56
|
+
export { AuthService } from "./auth/auth-service.js";
|
|
57
|
+
export { createAuthServer, authenticate, withAuth, parseCookies, COOKIE_NAME } from "./auth/http-server.js";
|
|
58
|
+
export { loadJwtSecret, getJwtSecret, resetJwtSecret } from "./auth/jwt-secret.js";
|
|
59
|
+
export { RateLimiter } from "./auth/rate-limiter.js";
|
|
60
|
+
|
|
61
|
+
export { Importer } from "./library/importer.js";
|
|
62
|
+
export type { ImportResult } from "./library/importer.js";
|
|
63
|
+
export { LibraryService } from "./library/library-service.js";
|
|
64
|
+
export type { Library, LibraryInput } from "./library/library-service.js";
|
|
65
|
+
export { resolveNaming } from "./library/naming.js";
|
|
66
|
+
export type { NamingRule, NamingSegment } from "@digitalpaws/starr-types-core";
|
|
67
|
+
export { DEFAULT_LIBRARY_CONFIG } from "./library/library-config.js";
|
|
68
|
+
export type { LibraryConfig, LibraryRoots } from "./library/library-config.js";
|
|
69
|
+
export { LibraryCache } from "./library/library-cache.js";
|
|
70
|
+
export type { CachedLibraryItem } from "./library/library-cache.js";
|
|
71
|
+
export { resolveSourcePath } from "./library/source-resolver.js";
|
|
72
|
+
export type { ResolvedSource } from "./library/source-resolver.js";
|
|
73
|
+
export { LibraryScanner } from "./library/library-scanner.js";
|
|
74
|
+
export type { DiscoveredGroup } from "./library/library-scanner.js";
|
|
75
|
+
export { MetadataMatcher } from "./library/metadata-matcher.js";
|
|
76
|
+
export type { MatchResult } from "./library/metadata-matcher.js";
|
|
77
|
+
export { ScanController } from "./library/scan-controller.js";
|
|
78
|
+
|
|
79
|
+
// Bootstrap — only run when this module is the entry point (not during tests/imports).
|
|
80
|
+
import { fileURLToPath } from "node:url";
|
|
81
|
+
import path from "node:path";
|
|
82
|
+
|
|
83
|
+
const isMain = process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
|
|
84
|
+
|
|
85
|
+
if (isMain) {
|
|
86
|
+
const { Starr } = await import("./host.js");
|
|
87
|
+
const { createLogger } = await import("./logging/logger.js");
|
|
88
|
+
const { loadPlugins, stopPlugins } = await import("./plugins/loader.js");
|
|
89
|
+
const { openDatastore } = await import("./datastore/db.js");
|
|
90
|
+
const { runMigrations, CORE_MIGRATIONS } = await import("./datastore/migrations.js");
|
|
91
|
+
const { loadEncryptionKey } = await import("./datastore/encrypt.js");
|
|
92
|
+
const { ConfigService } = await import("./config/config-service.js");
|
|
93
|
+
const { StateStore } = await import("./state/state-store.js");
|
|
94
|
+
const { loadJwtSecret } = await import("./auth/jwt-secret.js");
|
|
95
|
+
const { AuthService } = await import("./auth/auth-service.js");
|
|
96
|
+
const { createWebServer, initAuthService, initOidcServices } = await import("./web/index.js");
|
|
97
|
+
const { initContentTypeRoutes } = await import("./web/routes/content-types.js");
|
|
98
|
+
const { initDiscoveryRoutes } = await import("./web/routes/discovery.js");
|
|
99
|
+
const { initRequestRoutes } = await import("./web/routes/requests.js");
|
|
100
|
+
const { initConfigRoutes } = await import("./web/routes/config.js");
|
|
101
|
+
const { initActivityRoutes } = await import("./web/routes/activity.js");
|
|
102
|
+
const { initSseRoutes } = await import("./web/routes/sse.js");
|
|
103
|
+
const { initAuthRoutes } = await import("./web/routes/auth.js");
|
|
104
|
+
const { initTraceStore } = await import("./state/trace-store.js");
|
|
105
|
+
const { OidcProviderService } = await import("./auth/oidc/oidc-provider-service.js");
|
|
106
|
+
const { UserService } = await import("./auth/oidc/user-service.js");
|
|
107
|
+
const { OidcFlow } = await import("./auth/oidc/oidc-flow.js");
|
|
108
|
+
const { Importer } = await import("./library/importer.js");
|
|
109
|
+
const { getNamingRule } = await import("./content-types/registry.js");
|
|
110
|
+
const { DEFAULT_LIBRARY_CONFIG } = await import("./library/library-config.js");
|
|
111
|
+
const { LibraryService } = await import("./library/library-service.js");
|
|
112
|
+
const { LibraryCache } = await import("./library/library-cache.js");
|
|
113
|
+
const { initLibraryRoutes } = await import("./web/routes/library.js");
|
|
114
|
+
const { initLibraryManagementRoutes } = await import("./web/routes/library-management.js");
|
|
115
|
+
const { initLibraryImportRoutes } = await import("./web/routes/library-import.js");
|
|
116
|
+
const { LibraryScanner } = await import("./library/library-scanner.js");
|
|
117
|
+
const { MetadataMatcher } = await import("./library/metadata-matcher.js");
|
|
118
|
+
const { ScanController } = await import("./library/scan-controller.js");
|
|
119
|
+
const { resolveSourcePath } = await import("./library/source-resolver.js");
|
|
120
|
+
|
|
121
|
+
async function main() {
|
|
122
|
+
const pluginsDir = path.resolve(process.env.STARR_PLUGINS_DIR ?? "./plugins");
|
|
123
|
+
const dataDir = path.resolve(process.env.STARR_DATA_DIR ?? "./data");
|
|
124
|
+
const apiVersion = process.env.STARR_API_VERSION ?? "1.0.0";
|
|
125
|
+
const logLevel = process.env.STARR_LOG_LEVEL ?? "info";
|
|
126
|
+
|
|
127
|
+
const logger = createLogger("core", logLevel);
|
|
128
|
+
|
|
129
|
+
// 1. Open datastore
|
|
130
|
+
const datastore = openDatastore(dataDir, logger);
|
|
131
|
+
|
|
132
|
+
// 2. Run core migrations
|
|
133
|
+
runMigrations(datastore.db, CORE_MIGRATIONS, logger);
|
|
134
|
+
|
|
135
|
+
// 3. Load encryption key
|
|
136
|
+
loadEncryptionKey(dataDir, logger);
|
|
137
|
+
|
|
138
|
+
// 3a. Load JWT secret
|
|
139
|
+
loadJwtSecret(dataDir, logger);
|
|
140
|
+
|
|
141
|
+
// 4. Create host
|
|
142
|
+
const starr = new Starr({ pluginsDir, apiVersion, logger });
|
|
143
|
+
starr.datastore = datastore;
|
|
144
|
+
|
|
145
|
+
// 5. Create ConfigService (after datastore and encryption are ready)
|
|
146
|
+
const configService = new ConfigService(
|
|
147
|
+
datastore.db,
|
|
148
|
+
logger.child("config"),
|
|
149
|
+
// Reload callback — use lazy import to avoid circular deps
|
|
150
|
+
async (pluginId: string) => {
|
|
151
|
+
const { reloadPlugin } = await import("./plugins/loader.js");
|
|
152
|
+
await reloadPlugin(starr, pluginId);
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
starr.configService = configService;
|
|
156
|
+
|
|
157
|
+
// 6. Initialize state store (subscribes to spine events)
|
|
158
|
+
const stateStore = new StateStore(
|
|
159
|
+
datastore.db,
|
|
160
|
+
starr.coreBus,
|
|
161
|
+
logger,
|
|
162
|
+
);
|
|
163
|
+
starr.stateStore = stateStore;
|
|
164
|
+
|
|
165
|
+
// 5a. Create LibraryService (after migrations so table exists)
|
|
166
|
+
const libraryService = new LibraryService(
|
|
167
|
+
datastore.db,
|
|
168
|
+
starr.coreBus,
|
|
169
|
+
logger.child("library-service"),
|
|
170
|
+
);
|
|
171
|
+
starr.libraryService = libraryService;
|
|
172
|
+
|
|
173
|
+
// Bootstrap default library if none exist
|
|
174
|
+
const defaultLib = libraryService.getGlobalDefault();
|
|
175
|
+
if (!defaultLib) {
|
|
176
|
+
libraryService.create({
|
|
177
|
+
name: "Default Library",
|
|
178
|
+
isDefault: true,
|
|
179
|
+
defaultLibraryRoot: DEFAULT_LIBRARY_CONFIG.defaultLibraryRoot,
|
|
180
|
+
linkType: DEFAULT_LIBRARY_CONFIG.linkType,
|
|
181
|
+
importSubtitles: DEFAULT_LIBRARY_CONFIG.importSubtitles,
|
|
182
|
+
subtitleExtensions: [...DEFAULT_LIBRARY_CONFIG.subtitleExtensions],
|
|
183
|
+
});
|
|
184
|
+
logger.info("created default library");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 6a. Create importer (reacts to download.completed)
|
|
188
|
+
const importer = new Importer(
|
|
189
|
+
datastore.db,
|
|
190
|
+
starr.coreBus,
|
|
191
|
+
logger,
|
|
192
|
+
() => {
|
|
193
|
+
const defaultLib = libraryService.getGlobalDefault();
|
|
194
|
+
return {
|
|
195
|
+
defaultLibraryRoot: defaultLib?.defaultLibraryRoot ?? DEFAULT_LIBRARY_CONFIG.defaultLibraryRoot,
|
|
196
|
+
roots: defaultLib?.roots ?? {},
|
|
197
|
+
linkType: defaultLib?.linkType ?? DEFAULT_LIBRARY_CONFIG.linkType,
|
|
198
|
+
importSubtitles: defaultLib?.importSubtitles ?? DEFAULT_LIBRARY_CONFIG.importSubtitles,
|
|
199
|
+
subtitleExtensions: defaultLib?.subtitleExtensions ?? DEFAULT_LIBRARY_CONFIG.subtitleExtensions,
|
|
200
|
+
};
|
|
201
|
+
},
|
|
202
|
+
(contentTypeId: string) => getNamingRule(contentTypeId, starr.contentTypeRegistry),
|
|
203
|
+
libraryService,
|
|
204
|
+
);
|
|
205
|
+
starr.importer = importer;
|
|
206
|
+
|
|
207
|
+
// 6b. Create library cache (populated from imported items)
|
|
208
|
+
const libraryCache = new LibraryCache(datastore.db, starr.coreBus, logger);
|
|
209
|
+
libraryCache.refreshFromDb();
|
|
210
|
+
starr.libraryCache = libraryCache;
|
|
211
|
+
|
|
212
|
+
// 6c. Create library scanner, metadata matcher, and scan controller
|
|
213
|
+
const libraryScanner = new LibraryScanner(
|
|
214
|
+
datastore.db,
|
|
215
|
+
logger,
|
|
216
|
+
libraryService,
|
|
217
|
+
);
|
|
218
|
+
const metadataMatcher = new MetadataMatcher(
|
|
219
|
+
starr.coreBus,
|
|
220
|
+
starr.contentTypeRegistry,
|
|
221
|
+
logger,
|
|
222
|
+
);
|
|
223
|
+
const scanController = new ScanController(
|
|
224
|
+
libraryScanner,
|
|
225
|
+
metadataMatcher,
|
|
226
|
+
datastore.db,
|
|
227
|
+
starr.coreBus,
|
|
228
|
+
logger,
|
|
229
|
+
libraryCache,
|
|
230
|
+
starr.contentTypeRegistry,
|
|
231
|
+
importer,
|
|
232
|
+
);
|
|
233
|
+
starr.scanController = scanController;
|
|
234
|
+
|
|
235
|
+
// Subscribe importer to download.completed
|
|
236
|
+
starr.coreBus.on("download.completed", async (event) => {
|
|
237
|
+
const payload = event.payload as {
|
|
238
|
+
requestId: string; release: any; location?: string; libraryId?: string;
|
|
239
|
+
};
|
|
240
|
+
const req = stateStore.getByRequestId(payload.requestId);
|
|
241
|
+
if (!req) return;
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const resolved = await resolveSourcePath(payload.location);
|
|
245
|
+
|
|
246
|
+
// Import each media file found at the source
|
|
247
|
+
let anySucceeded = false;
|
|
248
|
+
for (const filePath of resolved.files) {
|
|
249
|
+
const ext = path.extname(filePath).slice(1).toLowerCase() || "mkv";
|
|
250
|
+
const result = await importer.import(
|
|
251
|
+
payload.requestId, filePath, req.item, req.contentTypeId, ext,
|
|
252
|
+
req.contentIdentity,
|
|
253
|
+
payload.libraryId,
|
|
254
|
+
);
|
|
255
|
+
if (result.success) {
|
|
256
|
+
anySucceeded = true;
|
|
257
|
+
} else {
|
|
258
|
+
logger.error("import failed for file", {
|
|
259
|
+
requestId: payload.requestId,
|
|
260
|
+
file: filePath,
|
|
261
|
+
error: result.error,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!anySucceeded) {
|
|
267
|
+
logger.error("all imports failed", { requestId: payload.requestId });
|
|
268
|
+
}
|
|
269
|
+
} catch (err) {
|
|
270
|
+
logger.error("import resolution failed", {
|
|
271
|
+
requestId: payload.requestId,
|
|
272
|
+
error: String(err),
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// 7. Create AuthService (before web server so auth hook can reference it)
|
|
278
|
+
const authService = new AuthService(datastore.db, logger.child("auth"));
|
|
279
|
+
|
|
280
|
+
// 7a. Create UserService
|
|
281
|
+
const userService = new UserService(datastore.db, logger.child("users"));
|
|
282
|
+
|
|
283
|
+
// 7b. Create OIDC Provider Service
|
|
284
|
+
const { encrypt, decrypt } = await import("./datastore/encrypt.js");
|
|
285
|
+
const oidcProviderService = new OidcProviderService(
|
|
286
|
+
datastore.db,
|
|
287
|
+
(plaintext: string) => encrypt(plaintext),
|
|
288
|
+
(ciphertext: string) => decrypt(ciphertext),
|
|
289
|
+
logger.child("oidc-providers"),
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
// 7c. Create OIDC Flow
|
|
293
|
+
const port = parseInt(process.env.STARR_PORT ?? "3457", 10);
|
|
294
|
+
const baseUrl = process.env.STARR_BASE_URL ?? `http://localhost:${port}`;
|
|
295
|
+
const oidcFlow = new OidcFlow(
|
|
296
|
+
datastore.db,
|
|
297
|
+
oidcProviderService,
|
|
298
|
+
userService,
|
|
299
|
+
baseUrl,
|
|
300
|
+
logger.child("oidc-flow"),
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
// 8. Create web server (registers route stubs via dynamic import, auth hook)
|
|
304
|
+
const webServer = await createWebServer(logger.child("web"));
|
|
305
|
+
starr.webServer = webServer;
|
|
306
|
+
|
|
307
|
+
// Wire auth routes and auth hook into web server
|
|
308
|
+
initAuthService(authService);
|
|
309
|
+
initAuthRoutes(authService);
|
|
310
|
+
|
|
311
|
+
// Wire OIDC services into web server (so oidc route init gets them)
|
|
312
|
+
initOidcServices(oidcProviderService, oidcFlow, userService);
|
|
313
|
+
|
|
314
|
+
// Wire config routes (configService already exists)
|
|
315
|
+
initConfigRoutes(configService, () => starr.getRuntimes());
|
|
316
|
+
|
|
317
|
+
// Wire trace-store wrapper
|
|
318
|
+
initTraceStore(stateStore);
|
|
319
|
+
|
|
320
|
+
// 9. Load plugins (populates content-type registry, event bus handlers)
|
|
321
|
+
logger.info("booting", { pluginsDir, apiVersion, dataDir });
|
|
322
|
+
await loadPlugins(starr);
|
|
323
|
+
|
|
324
|
+
// 10. Wire remaining routes that depend on plugin-loaded data
|
|
325
|
+
initContentTypeRoutes(
|
|
326
|
+
() => starr.contentTypeRegistry.list(),
|
|
327
|
+
(id: string) => starr.contentTypeRegistry.get(id),
|
|
328
|
+
);
|
|
329
|
+
initDiscoveryRoutes(starr.coreBus);
|
|
330
|
+
initRequestRoutes(starr);
|
|
331
|
+
initActivityRoutes(starr);
|
|
332
|
+
initSseRoutes(starr.coreBus);
|
|
333
|
+
initLibraryRoutes(libraryCache);
|
|
334
|
+
initLibraryManagementRoutes(libraryService);
|
|
335
|
+
initLibraryImportRoutes(scanController);
|
|
336
|
+
|
|
337
|
+
// 11. Start web server
|
|
338
|
+
await webServer.listen({ port, host: "0.0.0.0" });
|
|
339
|
+
logger.info("web server listening", { port });
|
|
340
|
+
|
|
341
|
+
starr.markRunning();
|
|
342
|
+
|
|
343
|
+
// 12. Graceful shutdown
|
|
344
|
+
let shuttingDown = false;
|
|
345
|
+
for (const signal of ["SIGTERM", "SIGINT"]) {
|
|
346
|
+
process.on(signal, async () => {
|
|
347
|
+
if (shuttingDown) return;
|
|
348
|
+
shuttingDown = true;
|
|
349
|
+
logger.info("shutting down", { signal });
|
|
350
|
+
// Close web server first to release the port immediately
|
|
351
|
+
try { await webServer.close(); } catch { /* ignore close errors during shutdown */ }
|
|
352
|
+
await stopPlugins(starr);
|
|
353
|
+
starr.markStopped();
|
|
354
|
+
datastore.close();
|
|
355
|
+
process.exit(0);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
main().catch((err) => {
|
|
361
|
+
console.error(err);
|
|
362
|
+
process.exit(1);
|
|
363
|
+
});
|
|
364
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// SPDX-License-Identifier: 0BSD
|
|
2
|
+
// SPDX-FileCopyrightText: 2025 Digi <digitalpaws@proton.me>
|
|
3
|
+
|
|
4
|
+
import semver from "semver";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import type Database from "better-sqlite3";
|
|
7
|
+
import type { ConfigSchema, Logger, PluginContext, PluginMeta, PluginStorage, EventBus, EmitOpts, RequestOpts } from "../types/plugin.js";
|
|
8
|
+
import type { Starr } from "../host.js";
|
|
9
|
+
import type { ConfigService } from "../config/config-service.js";
|
|
10
|
+
|
|
11
|
+
import { createPluginStorage } from "../datastore/storage.js";
|
|
12
|
+
|
|
13
|
+
export function createContext(
|
|
14
|
+
starr: Starr,
|
|
15
|
+
pluginDir: string,
|
|
16
|
+
db: Database.Database,
|
|
17
|
+
configService?: ConfigService,
|
|
18
|
+
instanceId = "default",
|
|
19
|
+
): PluginContext & {
|
|
20
|
+
getMeta: () => PluginMeta | undefined;
|
|
21
|
+
unsubscribeAll: () => void;
|
|
22
|
+
deleteAllStorage: () => Promise<void>;
|
|
23
|
+
} {
|
|
24
|
+
let meta: PluginMeta | undefined;
|
|
25
|
+
let defined = false;
|
|
26
|
+
let _storage: PluginStorage | null = null;
|
|
27
|
+
let _subscriptions: Array<() => void> = [];
|
|
28
|
+
|
|
29
|
+
const guard = () => {
|
|
30
|
+
if (!defined) throw new Error("ctx.define must be called before any other context method");
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
let logger: Logger = starr.logger.child(path.basename(pluginDir));
|
|
34
|
+
|
|
35
|
+
const context: PluginContext & {
|
|
36
|
+
getMeta: () => PluginMeta | undefined;
|
|
37
|
+
unsubscribeAll: () => void;
|
|
38
|
+
deleteAllStorage: () => Promise<void>;
|
|
39
|
+
} = {
|
|
40
|
+
apiVersion: starr.apiVersion,
|
|
41
|
+
|
|
42
|
+
define(m) {
|
|
43
|
+
if (defined) throw new Error("ctx.define can only be called once");
|
|
44
|
+
if (!m.id) throw new Error("plugin id is required");
|
|
45
|
+
if (!semver.valid(m.version)) throw new Error(`invalid plugin version: ${m.version}`);
|
|
46
|
+
if (!semver.validRange(m.starrApiVersion))
|
|
47
|
+
throw new Error(`invalid api range: ${m.starrApiVersion}`);
|
|
48
|
+
if (!semver.satisfies(starr.apiVersion, m.starrApiVersion)) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`API version mismatch: host ${starr.apiVersion} does not satisfy ${m.starrApiVersion}`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
meta = m;
|
|
54
|
+
defined = true;
|
|
55
|
+
logger = starr.logger.child(m.id);
|
|
56
|
+
logger.debug("defined", { meta: m });
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
config<T>(schema: ConfigSchema<T>) {
|
|
60
|
+
guard();
|
|
61
|
+
if (!meta) throw new Error("ctx.config called before ctx.define");
|
|
62
|
+
if (configService) {
|
|
63
|
+
return configService.register(meta.id, schema, instanceId) as Readonly<T>;
|
|
64
|
+
}
|
|
65
|
+
// Fallback for tests / bootstrap paths that do not provide a ConfigService
|
|
66
|
+
const values: Record<string, unknown> = {};
|
|
67
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
68
|
+
values[key] = (field as { default?: unknown }).default ?? undefined;
|
|
69
|
+
}
|
|
70
|
+
return Object.freeze(values) as Readonly<T>;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
events: (() => {
|
|
74
|
+
let _pluginBus: EventBus | null = null;
|
|
75
|
+
const getBus = (): EventBus => {
|
|
76
|
+
if (!_pluginBus) {
|
|
77
|
+
_pluginBus = starr.busImpl.pluginBus(meta!.id);
|
|
78
|
+
}
|
|
79
|
+
return _pluginBus;
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
on<P>(type: string, handler: (event: import("../types/plugin.js").StarrEvent<P>) => void | Promise<void>) {
|
|
83
|
+
guard();
|
|
84
|
+
const unsub = getBus().on(type, handler);
|
|
85
|
+
_subscriptions.push(unsub);
|
|
86
|
+
return unsub;
|
|
87
|
+
},
|
|
88
|
+
emit<P>(type: string, payload: P, opts?: EmitOpts) {
|
|
89
|
+
guard();
|
|
90
|
+
getBus().emit(type, payload, opts);
|
|
91
|
+
},
|
|
92
|
+
request<Res>(type: string, payload: unknown, opts: RequestOpts) {
|
|
93
|
+
guard();
|
|
94
|
+
return getBus().request<Res>(type, payload, opts);
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
})(),
|
|
98
|
+
|
|
99
|
+
get storage(): PluginStorage {
|
|
100
|
+
if (!_storage && defined && meta) {
|
|
101
|
+
_storage = createPluginStorage(db, meta.id, instanceId);
|
|
102
|
+
}
|
|
103
|
+
if (!_storage) {
|
|
104
|
+
throw new Error("ctx.storage called before ctx.define");
|
|
105
|
+
}
|
|
106
|
+
return _storage;
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
contentTypes: {
|
|
110
|
+
register(descriptor) {
|
|
111
|
+
guard();
|
|
112
|
+
if (!meta) throw new Error("ctx.contentTypes.register called before ctx.define");
|
|
113
|
+
starr.contentTypeRegistry.register(descriptor, meta.id);
|
|
114
|
+
logger.debug("registered content type", { typeId: descriptor.id });
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
get logger() {
|
|
119
|
+
return logger;
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
getMeta: () => meta,
|
|
123
|
+
|
|
124
|
+
unsubscribeAll: () => {
|
|
125
|
+
for (const unsub of _subscriptions) unsub();
|
|
126
|
+
_subscriptions = [];
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
deleteAllStorage: async () => {
|
|
130
|
+
if (_storage) {
|
|
131
|
+
await _storage.deleteAll();
|
|
132
|
+
_storage = null;
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return context;
|
|
138
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// SPDX-License-Identifier: 0BSD
|
|
2
|
+
// SPDX-FileCopyrightText: 2025 Digi <digitalpaws@proton.me>
|
|
3
|
+
|
|
4
|
+
import { readdir, stat } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
export async function discoverPluginDirs(pluginsDir: string): Promise<string[]> {
|
|
8
|
+
let entries;
|
|
9
|
+
try {
|
|
10
|
+
entries = await readdir(pluginsDir, { withFileTypes: true });
|
|
11
|
+
} catch {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const dirs: string[] = [];
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
if (!entry.isDirectory()) continue;
|
|
18
|
+
if (entry.name.startsWith(".")) continue;
|
|
19
|
+
if (entry.name === "node_modules") continue;
|
|
20
|
+
const full = path.join(pluginsDir, entry.name);
|
|
21
|
+
try {
|
|
22
|
+
await stat(path.join(full, "package.json"));
|
|
23
|
+
dirs.push(full);
|
|
24
|
+
} catch {
|
|
25
|
+
// not a plugin package
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return dirs.sort((a, b) => path.basename(a).localeCompare(path.basename(b)));
|
|
29
|
+
}
|