@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.
Files changed (290) hide show
  1. package/README.rst +75 -0
  2. package/data/.nfs.811bbb0d.43c34.4 +0 -0
  3. package/data/.nfs.81ba4cd0.ffff87804.4 +0 -0
  4. package/data/.nfs.82d127f7.fffff5704.4 +0 -0
  5. package/data/.nfs.82d127fe.fffff5704.4 +0 -0
  6. package/data/.nfs.82d159a5.fffff5aa4.4 +0 -0
  7. package/data/.nfs.82d159aa.fffff5aa4.4 +0 -0
  8. package/data/.nfs.82d15a9c.fffff5bb4.4 +0 -0
  9. package/data/.nfs.82d15aa1.fffff5bb4.4 +0 -0
  10. package/data/.nfs.82d15c65.fffff5c24.4 +0 -0
  11. package/data/.nfs.82d1852c.fffff5e24.4 +0 -0
  12. package/data/.nfs.82d18536.fffff5e24.4 +0 -0
  13. package/data/.nfs.82d18dbe.fffff5e44.4 +0 -0
  14. package/data/.nfs.82d18dc6.fffff5e44.4 +0 -0
  15. package/data/.nfs.8309a3f5.fffff82f4.4 +0 -0
  16. package/data/.nfs.8309a3fe.fffff82f4.4 +0 -0
  17. package/data/.nfs.8314122e.fffffb044.4 +0 -0
  18. package/data/.nfs.83141236.fffffb044.4 +0 -0
  19. package/data/.nfs.833c4fe9.fffffbc34.4 +0 -0
  20. package/data/.nfs.833c4ff3.fffffbc34.4 +0 -0
  21. package/data/logs/starr.log +66244 -0
  22. package/data/starr.db +0 -0
  23. package/data/starr.db-shm +0 -0
  24. package/data/starr.db-wal +0 -0
  25. package/data/starr.jwt +1 -0
  26. package/data/starr.key +1 -0
  27. package/dist/auth/auth-service.d.ts +42 -0
  28. package/dist/auth/auth-service.d.ts.map +1 -0
  29. package/dist/auth/auth-service.js +226 -0
  30. package/dist/auth/auth-service.js.map +1 -0
  31. package/dist/auth/http-server.d.ts +16 -0
  32. package/dist/auth/http-server.d.ts.map +1 -0
  33. package/dist/auth/http-server.js +207 -0
  34. package/dist/auth/http-server.js.map +1 -0
  35. package/dist/auth/jwt-secret.d.ts +6 -0
  36. package/dist/auth/jwt-secret.d.ts.map +1 -0
  37. package/dist/auth/jwt-secret.js +51 -0
  38. package/dist/auth/jwt-secret.js.map +1 -0
  39. package/dist/auth/oidc/oidc-flow.d.ts +60 -0
  40. package/dist/auth/oidc/oidc-flow.d.ts.map +1 -0
  41. package/dist/auth/oidc/oidc-flow.js +201 -0
  42. package/dist/auth/oidc/oidc-flow.js.map +1 -0
  43. package/dist/auth/oidc/oidc-provider-service.d.ts +59 -0
  44. package/dist/auth/oidc/oidc-provider-service.d.ts.map +1 -0
  45. package/dist/auth/oidc/oidc-provider-service.js +124 -0
  46. package/dist/auth/oidc/oidc-provider-service.js.map +1 -0
  47. package/dist/auth/oidc/user-service.d.ts +67 -0
  48. package/dist/auth/oidc/user-service.d.ts.map +1 -0
  49. package/dist/auth/oidc/user-service.js +177 -0
  50. package/dist/auth/oidc/user-service.js.map +1 -0
  51. package/dist/auth/rate-limiter.d.ts +31 -0
  52. package/dist/auth/rate-limiter.d.ts.map +1 -0
  53. package/dist/auth/rate-limiter.js +70 -0
  54. package/dist/auth/rate-limiter.js.map +1 -0
  55. package/dist/config/config-service.d.ts +83 -0
  56. package/dist/config/config-service.d.ts.map +1 -0
  57. package/dist/config/config-service.js +309 -0
  58. package/dist/config/config-service.js.map +1 -0
  59. package/dist/config/validate.d.ts +12 -0
  60. package/dist/config/validate.d.ts.map +1 -0
  61. package/dist/config/validate.js +140 -0
  62. package/dist/config/validate.js.map +1 -0
  63. package/dist/content-types/registry.d.ts +75 -0
  64. package/dist/content-types/registry.d.ts.map +1 -0
  65. package/dist/content-types/registry.js +171 -0
  66. package/dist/content-types/registry.js.map +1 -0
  67. package/dist/datastore/db.d.ts +9 -0
  68. package/dist/datastore/db.d.ts.map +1 -0
  69. package/dist/datastore/db.js +27 -0
  70. package/dist/datastore/db.js.map +1 -0
  71. package/dist/datastore/encrypt.d.ts +9 -0
  72. package/dist/datastore/encrypt.d.ts.map +1 -0
  73. package/dist/datastore/encrypt.js +84 -0
  74. package/dist/datastore/encrypt.js.map +1 -0
  75. package/dist/datastore/migrations.d.ts +10 -0
  76. package/dist/datastore/migrations.d.ts.map +1 -0
  77. package/dist/datastore/migrations.js +340 -0
  78. package/dist/datastore/migrations.js.map +1 -0
  79. package/dist/datastore/storage.d.ts +4 -0
  80. package/dist/datastore/storage.d.ts.map +1 -0
  81. package/dist/datastore/storage.js +60 -0
  82. package/dist/datastore/storage.js.map +1 -0
  83. package/dist/events/event-bus.d.ts +15 -0
  84. package/dist/events/event-bus.d.ts.map +1 -0
  85. package/dist/events/event-bus.js +140 -0
  86. package/dist/events/event-bus.js.map +1 -0
  87. package/dist/events/spine.d.ts +31 -0
  88. package/dist/events/spine.d.ts.map +1 -0
  89. package/dist/events/spine.js +68 -0
  90. package/dist/events/spine.js.map +1 -0
  91. package/dist/events/trace.d.ts +24 -0
  92. package/dist/events/trace.d.ts.map +1 -0
  93. package/dist/events/trace.js +33 -0
  94. package/dist/events/trace.js.map +1 -0
  95. package/dist/host.d.ts +65 -0
  96. package/dist/host.d.ts.map +1 -0
  97. package/dist/host.js +94 -0
  98. package/dist/host.js.map +1 -0
  99. package/dist/library/importer.d.ts +29 -0
  100. package/dist/library/importer.d.ts.map +1 -0
  101. package/dist/library/importer.js +145 -0
  102. package/dist/library/importer.js.map +1 -0
  103. package/dist/library/library-cache.d.ts +46 -0
  104. package/dist/library/library-cache.d.ts.map +1 -0
  105. package/dist/library/library-cache.js +152 -0
  106. package/dist/library/library-cache.js.map +1 -0
  107. package/dist/library/library-config.d.ts +12 -0
  108. package/dist/library/library-config.d.ts.map +1 -0
  109. package/dist/library/library-config.js +10 -0
  110. package/dist/library/library-config.js.map +1 -0
  111. package/dist/library/library-scanner.d.ts +62 -0
  112. package/dist/library/library-scanner.d.ts.map +1 -0
  113. package/dist/library/library-scanner.js +336 -0
  114. package/dist/library/library-scanner.js.map +1 -0
  115. package/dist/library/library-service.d.ts +65 -0
  116. package/dist/library/library-service.d.ts.map +1 -0
  117. package/dist/library/library-service.js +197 -0
  118. package/dist/library/library-service.js.map +1 -0
  119. package/dist/library/metadata-matcher.d.ts +56 -0
  120. package/dist/library/metadata-matcher.d.ts.map +1 -0
  121. package/dist/library/metadata-matcher.js +207 -0
  122. package/dist/library/metadata-matcher.js.map +1 -0
  123. package/dist/library/naming.d.ts +8 -0
  124. package/dist/library/naming.d.ts.map +1 -0
  125. package/dist/library/naming.js +47 -0
  126. package/dist/library/naming.js.map +1 -0
  127. package/dist/library/scan-controller.d.ts +95 -0
  128. package/dist/library/scan-controller.d.ts.map +1 -0
  129. package/dist/library/scan-controller.js +379 -0
  130. package/dist/library/scan-controller.js.map +1 -0
  131. package/dist/library/source-resolver.d.ts +17 -0
  132. package/dist/library/source-resolver.d.ts.map +1 -0
  133. package/dist/library/source-resolver.js +75 -0
  134. package/dist/library/source-resolver.js.map +1 -0
  135. package/dist/logging/logger.d.ts +3 -0
  136. package/dist/logging/logger.d.ts.map +1 -0
  137. package/dist/logging/logger.js +107 -0
  138. package/dist/logging/logger.js.map +1 -0
  139. package/dist/main.d.ts +36 -0
  140. package/dist/main.d.ts.map +1 -0
  141. package/dist/main.js +229 -0
  142. package/dist/main.js.map +1 -0
  143. package/dist/plugins/context.d.ts +10 -0
  144. package/dist/plugins/context.d.ts.map +1 -0
  145. package/dist/plugins/context.js +110 -0
  146. package/dist/plugins/context.js.map +1 -0
  147. package/dist/plugins/discovery.d.ts +2 -0
  148. package/dist/plugins/discovery.d.ts.map +1 -0
  149. package/dist/plugins/discovery.js +32 -0
  150. package/dist/plugins/discovery.js.map +1 -0
  151. package/dist/plugins/loader.d.ts +9 -0
  152. package/dist/plugins/loader.d.ts.map +1 -0
  153. package/dist/plugins/loader.js +245 -0
  154. package/dist/plugins/loader.js.map +1 -0
  155. package/dist/state/request-intake.d.ts +26 -0
  156. package/dist/state/request-intake.d.ts.map +1 -0
  157. package/dist/state/request-intake.js +74 -0
  158. package/dist/state/request-intake.js.map +1 -0
  159. package/dist/state/state-store.d.ts +146 -0
  160. package/dist/state/state-store.d.ts.map +1 -0
  161. package/dist/state/state-store.js +465 -0
  162. package/dist/state/state-store.js.map +1 -0
  163. package/dist/state/trace-store.d.ts +12 -0
  164. package/dist/state/trace-store.d.ts.map +1 -0
  165. package/dist/state/trace-store.js +24 -0
  166. package/dist/state/trace-store.js.map +1 -0
  167. package/dist/types/plugin.d.ts +93 -0
  168. package/dist/types/plugin.d.ts.map +1 -0
  169. package/dist/types/plugin.js +4 -0
  170. package/dist/types/plugin.js.map +1 -0
  171. package/dist/web/index.d.ts +10 -0
  172. package/dist/web/index.d.ts.map +1 -0
  173. package/dist/web/index.js +153 -0
  174. package/dist/web/index.js.map +1 -0
  175. package/dist/web/routes/activity.d.ts +6 -0
  176. package/dist/web/routes/activity.d.ts.map +1 -0
  177. package/dist/web/routes/activity.js +31 -0
  178. package/dist/web/routes/activity.js.map +1 -0
  179. package/dist/web/routes/auth.d.ts +6 -0
  180. package/dist/web/routes/auth.d.ts.map +1 -0
  181. package/dist/web/routes/auth.js +142 -0
  182. package/dist/web/routes/auth.js.map +1 -0
  183. package/dist/web/routes/config.d.ts +7 -0
  184. package/dist/web/routes/config.d.ts.map +1 -0
  185. package/dist/web/routes/config.js +204 -0
  186. package/dist/web/routes/config.js.map +1 -0
  187. package/dist/web/routes/content-types.d.ts +6 -0
  188. package/dist/web/routes/content-types.d.ts.map +1 -0
  189. package/dist/web/routes/content-types.js +29 -0
  190. package/dist/web/routes/content-types.js.map +1 -0
  191. package/dist/web/routes/discovery.d.ts +6 -0
  192. package/dist/web/routes/discovery.d.ts.map +1 -0
  193. package/dist/web/routes/discovery.js +111 -0
  194. package/dist/web/routes/discovery.js.map +1 -0
  195. package/dist/web/routes/library-import.d.ts +6 -0
  196. package/dist/web/routes/library-import.d.ts.map +1 -0
  197. package/dist/web/routes/library-import.js +120 -0
  198. package/dist/web/routes/library-import.js.map +1 -0
  199. package/dist/web/routes/library-management.d.ts +6 -0
  200. package/dist/web/routes/library-management.d.ts.map +1 -0
  201. package/dist/web/routes/library-management.js +120 -0
  202. package/dist/web/routes/library-management.js.map +1 -0
  203. package/dist/web/routes/library.d.ts +6 -0
  204. package/dist/web/routes/library.d.ts.map +1 -0
  205. package/dist/web/routes/library.js +28 -0
  206. package/dist/web/routes/library.js.map +1 -0
  207. package/dist/web/routes/oidc.d.ts +9 -0
  208. package/dist/web/routes/oidc.d.ts.map +1 -0
  209. package/dist/web/routes/oidc.js +203 -0
  210. package/dist/web/routes/oidc.js.map +1 -0
  211. package/dist/web/routes/requests.d.ts +6 -0
  212. package/dist/web/routes/requests.d.ts.map +1 -0
  213. package/dist/web/routes/requests.js +183 -0
  214. package/dist/web/routes/requests.js.map +1 -0
  215. package/dist/web/routes/sse.d.ts +6 -0
  216. package/dist/web/routes/sse.d.ts.map +1 -0
  217. package/dist/web/routes/sse.js +54 -0
  218. package/dist/web/routes/sse.js.map +1 -0
  219. package/package.json +33 -0
  220. package/package.json.license +2 -0
  221. package/src/auth/auth-service.ts +333 -0
  222. package/src/auth/http-server.ts +249 -0
  223. package/src/auth/jwt-secret.ts +65 -0
  224. package/src/auth/oidc/oidc-flow.ts +266 -0
  225. package/src/auth/oidc/oidc-provider-service.ts +174 -0
  226. package/src/auth/oidc/user-service.ts +227 -0
  227. package/src/auth/rate-limiter.ts +85 -0
  228. package/src/config/config-service.ts +434 -0
  229. package/src/config/validate.ts +153 -0
  230. package/src/content-types/registry.ts +194 -0
  231. package/src/datastore/db.ts +38 -0
  232. package/src/datastore/encrypt.ts +107 -0
  233. package/src/datastore/migrations.ts +367 -0
  234. package/src/datastore/storage.ts +83 -0
  235. package/src/events/event-bus.ts +180 -0
  236. package/src/events/spine.ts +87 -0
  237. package/src/events/trace.ts +38 -0
  238. package/src/host.ts +130 -0
  239. package/src/library/importer.ts +174 -0
  240. package/src/library/library-cache.ts +212 -0
  241. package/src/library/library-config.ts +22 -0
  242. package/src/library/library-scanner.ts +404 -0
  243. package/src/library/library-service.ts +282 -0
  244. package/src/library/metadata-matcher.ts +285 -0
  245. package/src/library/naming.ts +59 -0
  246. package/src/library/scan-controller.ts +490 -0
  247. package/src/library/source-resolver.ts +92 -0
  248. package/src/logging/logger.ts +115 -0
  249. package/src/main.ts +364 -0
  250. package/src/plugins/context.ts +138 -0
  251. package/src/plugins/discovery.ts +29 -0
  252. package/src/plugins/loader.ts +309 -0
  253. package/src/state/request-intake.ts +101 -0
  254. package/src/state/state-store.ts +621 -0
  255. package/src/state/trace-store.ts +27 -0
  256. package/src/types/plugin.ts +110 -0
  257. package/src/web/index.ts +182 -0
  258. package/src/web/routes/activity.ts +41 -0
  259. package/src/web/routes/auth.ts +170 -0
  260. package/src/web/routes/config.ts +282 -0
  261. package/src/web/routes/content-types.ts +42 -0
  262. package/src/web/routes/discovery.ts +156 -0
  263. package/src/web/routes/library-import.ts +196 -0
  264. package/src/web/routes/library-management.ts +140 -0
  265. package/src/web/routes/library.ts +46 -0
  266. package/src/web/routes/oidc.ts +260 -0
  267. package/src/web/routes/requests.ts +250 -0
  268. package/src/web/routes/sse.ts +92 -0
  269. package/tests/auth.test.ts +510 -0
  270. package/tests/config-route.test.ts +345 -0
  271. package/tests/config.test.ts +612 -0
  272. package/tests/content-types.test.ts +376 -0
  273. package/tests/datastore.test.ts +345 -0
  274. package/tests/e2e.test.ts +430 -0
  275. package/tests/event-bus.test.ts +925 -0
  276. package/tests/host.test.ts +449 -0
  277. package/tests/library-cache.test.ts +171 -0
  278. package/tests/library-scanner.test.ts +177 -0
  279. package/tests/library.test.ts +210 -0
  280. package/tests/loader-context.test.ts +644 -0
  281. package/tests/logger-jsonl.test.ts +154 -0
  282. package/tests/metadata-matcher.test.ts +198 -0
  283. package/tests/oidc.test.ts +218 -0
  284. package/tests/scan-controller.test.ts +328 -0
  285. package/tests/source-resolver.test.ts +100 -0
  286. package/tests/starr-video-integration.test.ts +153 -0
  287. package/tests/state-store.test.ts +700 -0
  288. package/tests/trace-history.test.ts +356 -0
  289. package/tsconfig.json +8 -0
  290. 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
+ }