@raubjo/architect 0.5.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 (68) hide show
  1. package/README.md +860 -0
  2. package/package.json +121 -0
  3. package/src/cache/cache.ts +46 -0
  4. package/src/cache/contract.ts +9 -0
  5. package/src/cache/manager.ts +110 -0
  6. package/src/cache/provider.ts +11 -0
  7. package/src/config/contract.ts +63 -0
  8. package/src/config/discovery.ts +99 -0
  9. package/src/config/env.global.d.ts +6 -0
  10. package/src/config/env.ts +68 -0
  11. package/src/config/index.ts +5 -0
  12. package/src/config/provider.ts +17 -0
  13. package/src/config/repository.ts +164 -0
  14. package/src/container/adapters/builtin.ts +323 -0
  15. package/src/container/contract.ts +43 -0
  16. package/src/container/runtime.ts +29 -0
  17. package/src/events/bus.ts +174 -0
  18. package/src/events/concerns/dispatchable.ts +10 -0
  19. package/src/events/provider.ts +9 -0
  20. package/src/events/types.ts +9 -0
  21. package/src/foundation/application.ts +136 -0
  22. package/src/foundation/current-application.ts +20 -0
  23. package/src/index.ts +58 -0
  24. package/src/log/contract.ts +21 -0
  25. package/src/log/drivers/console.ts +54 -0
  26. package/src/log/drivers/null.ts +23 -0
  27. package/src/log/drivers/stack.ts +46 -0
  28. package/src/log/manager.ts +76 -0
  29. package/src/log/provider.ts +11 -0
  30. package/src/react.ts +2 -0
  31. package/src/renderers/adapters/react.tsx +25 -0
  32. package/src/renderers/adapters/solid.tsx +26 -0
  33. package/src/renderers/adapters/svelte.ts +73 -0
  34. package/src/renderers/adapters/vue.ts +22 -0
  35. package/src/renderers/contract.ts +12 -0
  36. package/src/runtimes/react.tsx +81 -0
  37. package/src/runtimes/solid.tsx +47 -0
  38. package/src/runtimes/svelte.ts +17 -0
  39. package/src/runtimes/vue.ts +34 -0
  40. package/src/solid.ts +2 -0
  41. package/src/store/adapters/contract.ts +11 -0
  42. package/src/store/adapters/indexed-db.ts +187 -0
  43. package/src/store/adapters/local-storage.ts +48 -0
  44. package/src/store/adapters/memory.ts +35 -0
  45. package/src/store/manager.ts +68 -0
  46. package/src/store/provider.ts +10 -0
  47. package/src/store/store.ts +1 -0
  48. package/src/support/arr.ts +372 -0
  49. package/src/support/collection.ts +889 -0
  50. package/src/support/facades/cache.ts +6 -0
  51. package/src/support/facades/config.ts +6 -0
  52. package/src/support/facades/event.ts +6 -0
  53. package/src/support/facades/facade.ts +146 -0
  54. package/src/support/facades/index.ts +5 -0
  55. package/src/support/facades/log.ts +6 -0
  56. package/src/support/facades/store.ts +6 -0
  57. package/src/support/fluent.ts +56 -0
  58. package/src/support/globals.ts +8 -0
  59. package/src/support/lazy-collection.ts +341 -0
  60. package/src/support/manager.ts +53 -0
  61. package/src/support/num.ts +50 -0
  62. package/src/support/pipeline.ts +29 -0
  63. package/src/support/service-provider.ts +19 -0
  64. package/src/support/str.ts +682 -0
  65. package/src/svelte.ts +2 -0
  66. package/src/types/peer-deps.d.ts +10 -0
  67. package/src/vue.ts +2 -0
  68. package/tsconfig.json +15 -0
@@ -0,0 +1,174 @@
1
+ export type {
2
+ EventClass,
3
+ EventIdentifier,
4
+ Listener,
5
+ ListenerObject,
6
+ Unsubscribe,
7
+ WildcardListener,
8
+ } from "./types"
9
+
10
+ import type { EventClass, EventIdentifier, Listener, ListenerObject, Unsubscribe, WildcardListener } from "./types"
11
+
12
+ export interface EventSubscriber {
13
+ subscribe(bus: Bus): Record<string, Listener | string> | void
14
+ }
15
+
16
+ export class Bus {
17
+ protected listeners = new Map<string, Listener[]>()
18
+ protected wildcardListeners: WildcardListener[] = []
19
+ protected pushedEvents = new Map<string, Record<string, unknown>[]>()
20
+
21
+ listen<T>(
22
+ event: EventIdentifier<T> | Array<EventIdentifier<T>>,
23
+ listener: Listener<T> | ListenerObject<T>,
24
+ ): Unsubscribe {
25
+ const events = Array.isArray(event) ? event : [event]
26
+ const normalized = this.normalizeListener(listener) as Listener
27
+ const unsubscribers: Unsubscribe[] = []
28
+
29
+ for (const ev of events) {
30
+ const name = this.resolveEventName(ev)
31
+
32
+ if (name === "*") {
33
+ const wl: WildcardListener = (_name, data) => void normalized(data as T)
34
+ this.wildcardListeners.push(wl)
35
+ unsubscribers.push(() => {
36
+ const idx = this.wildcardListeners.indexOf(wl)
37
+ if (idx !== -1) this.wildcardListeners.splice(idx, 1)
38
+ })
39
+ continue
40
+ }
41
+
42
+ const current = this.listeners.get(name) ?? []
43
+ current.push(normalized)
44
+ this.listeners.set(name, current)
45
+
46
+ unsubscribers.push(() => {
47
+ const all = this.listeners.get(name) ?? []
48
+ const updated = all.filter((l) => l !== normalized)
49
+ if (updated.length === 0) {
50
+ this.listeners.delete(name)
51
+ } else {
52
+ this.listeners.set(name, updated)
53
+ }
54
+ })
55
+ }
56
+
57
+ return () => {
58
+ unsubscribers.forEach((fn) => {
59
+ fn()
60
+ })
61
+ }
62
+ }
63
+
64
+ once<T>(event: EventIdentifier<T>, listener: Listener<T> | ListenerObject<T>): Unsubscribe {
65
+ const normalized = this.normalizeListener(listener)
66
+ let unsubscribe!: Unsubscribe
67
+
68
+ const wrapped: Listener<T> = async (data: T) => {
69
+ unsubscribe()
70
+ return normalized(data)
71
+ }
72
+
73
+ unsubscribe = this.listen(event, wrapped)
74
+ return unsubscribe
75
+ }
76
+
77
+ subscribe(subscriber: EventSubscriber | (new () => EventSubscriber)): void {
78
+ const instance = typeof subscriber === "function" ? new subscriber() : subscriber
79
+ const mappings = instance.subscribe(this)
80
+
81
+ if (!mappings) return
82
+
83
+ for (const [event, handler] of Object.entries(mappings)) {
84
+ const listener =
85
+ typeof handler === "string"
86
+ ? (data: unknown) => (instance as unknown as Record<string, Listener>)[handler]?.(data)
87
+ : (handler as Listener)
88
+ this.listen(event, listener)
89
+ }
90
+ }
91
+
92
+ async dispatch<T>(event: T | string, payload?: unknown): Promise<void> {
93
+ const [name, data] = this.parseEventAndPayload(event, payload)
94
+
95
+ for (const wl of [...this.wildcardListeners]) {
96
+ await wl(name, data)
97
+ }
98
+
99
+ const listeners = [...(this.listeners.get(name) ?? [])]
100
+ for (const listener of listeners) {
101
+ await listener(data)
102
+ }
103
+ }
104
+
105
+ fire<T>(event: T | string, payload?: unknown): Promise<void> {
106
+ return this.dispatch(event, payload)
107
+ }
108
+
109
+ async until<T>(event: T | string, payload?: unknown): Promise<unknown> {
110
+ const [name, data] = this.parseEventAndPayload(event, payload)
111
+
112
+ const listeners = [...(this.listeners.get(name) ?? [])]
113
+ for (const listener of listeners) {
114
+ const result = await listener(data)
115
+ if (result !== null && result !== false && result !== undefined) {
116
+ return result
117
+ }
118
+ }
119
+ return null
120
+ }
121
+
122
+ push(event: string, payload: Record<string, unknown> = {}): void {
123
+ const queued = this.pushedEvents.get(event) ?? []
124
+ queued.push(payload)
125
+ this.pushedEvents.set(event, queued)
126
+ }
127
+
128
+ async flush(event: string): Promise<void> {
129
+ const queued = [...(this.pushedEvents.get(event) ?? [])]
130
+ this.pushedEvents.delete(event)
131
+ for (const payload of queued) {
132
+ await this.dispatch(event, payload)
133
+ }
134
+ }
135
+
136
+ forget(event: EventIdentifier): void {
137
+ this.listeners.delete(this.resolveEventName(event))
138
+ }
139
+
140
+ forgetPushed(): void {
141
+ this.pushedEvents.clear()
142
+ }
143
+
144
+ hasListeners(event: EventIdentifier): boolean {
145
+ const name = this.resolveEventName(event)
146
+ return (this.listeners.get(name)?.length ?? 0) > 0 || this.wildcardListeners.length > 0
147
+ }
148
+
149
+ protected resolveEventName(event: unknown): string {
150
+ if (typeof event === "string") return event
151
+ if (typeof event === "function") {
152
+ // EventClass passed directly — prefer static label (minification-safe)
153
+ if ("label" in event && typeof event.label === "string") return event.label as string
154
+ return (event as EventClass).name
155
+ }
156
+ // Event instance — read label/name from its constructor
157
+ const ctor = (event as object)?.constructor
158
+ if (ctor && "label" in ctor && typeof (ctor as { label: unknown }).label === "string") {
159
+ return (ctor as { label: string }).label
160
+ }
161
+ return ctor?.name ?? ""
162
+ }
163
+
164
+ protected parseEventAndPayload<T>(event: T | string, payload?: unknown): [string, unknown] {
165
+ const name = this.resolveEventName(event)
166
+ const data = typeof event === "string" ? (payload ?? {}) : event
167
+ return [name, data]
168
+ }
169
+
170
+ protected normalizeListener<T>(listener: Listener<T> | ListenerObject<T>): Listener<T> {
171
+ if (typeof listener === "function") return listener
172
+ return (event: T) => listener.handle(event)
173
+ }
174
+ }
@@ -0,0 +1,10 @@
1
+ import { Event } from "../../support/facades/event"
2
+
3
+ export abstract class Dispatchable {
4
+ static dispatch<TThis extends new (...args: any[]) => Dispatchable>(
5
+ this: TThis,
6
+ ...args: ConstructorParameters<TThis>
7
+ ): Promise<void> {
8
+ return Event.dispatch(new this(...args)) as Promise<void>
9
+ }
10
+ }
@@ -0,0 +1,9 @@
1
+ import type { ServiceProviderContext } from "../support/service-provider"
2
+ import ServiceProvider from "../support/service-provider"
3
+ import { Bus } from "./bus"
4
+
5
+ export class EventsProvider extends ServiceProvider {
6
+ register({ container }: ServiceProviderContext) {
7
+ container.singleton("events", (_c) => new Bus())
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ export type EventClass<T = unknown> = new (...args: any[]) => T
2
+ export type EventIdentifier<T = unknown> = string | EventClass<T>
3
+ export type Listener<T = unknown> = (event: T) => void | boolean | Promise<void | boolean>
4
+ export type WildcardListener = (eventName: string, data: unknown) => void | Promise<void>
5
+ export type Unsubscribe = () => void
6
+
7
+ export interface ListenerObject<T = unknown> {
8
+ handle(event: T): void | boolean | Promise<void | boolean>
9
+ }
@@ -0,0 +1,136 @@
1
+ import { createConfig } from "../config/discovery"
2
+ import { registerGlobalEnv } from "../config/env"
3
+ import { ConfigProvider } from "../config/provider"
4
+ import type ConfigRepository from "../config/repository"
5
+ import type { ConfigItems } from "../config/repository"
6
+ import type { ContainerContract, ContainerIdentifier } from "../container/contract"
7
+ import {
8
+ type ContainerRuntimeOptions,
9
+ createRuntimeContainer,
10
+ mergeContainerRuntimeOptions,
11
+ } from "../container/runtime"
12
+ import { clearFacadeCache } from "../support/facades/facade"
13
+ import type ServiceProvider from "../support/service-provider"
14
+ import type { Cleanup, ServiceProviderContext } from "../support/service-provider"
15
+ import { getCurrentApplicationContainer, setCurrentApplicationContainer } from "./current-application"
16
+
17
+ type ApplicationRunContext = ServiceProviderContext & {
18
+ cleanupTasks: Cleanup[]
19
+ }
20
+
21
+ export type ApplicationConfigureOptions = {
22
+ basePath?: string
23
+ container?: ContainerRuntimeOptions
24
+ config?: ConfigItems
25
+ }
26
+
27
+ type ApplicationResolvedOptions = {
28
+ basePath: string
29
+ container: ReturnType<typeof mergeContainerRuntimeOptions>
30
+ config: ConfigItems
31
+ }
32
+
33
+ registerGlobalEnv()
34
+
35
+ function mergeConfigureOptions(options: ApplicationConfigureOptions = {}): ApplicationResolvedOptions {
36
+ return {
37
+ basePath: options.basePath ?? "./",
38
+ container: mergeContainerRuntimeOptions(options.container),
39
+ config: options.config ?? {},
40
+ }
41
+ }
42
+
43
+ export class Application {
44
+ protected providers: ServiceProvider[]
45
+ protected options: ApplicationResolvedOptions
46
+
47
+ constructor(options: ApplicationResolvedOptions) {
48
+ this.options = options
49
+ this.providers = []
50
+ }
51
+
52
+ protected getConfigItems(): ConfigRepository {
53
+ return createConfig(this.options.basePath, this.options.config)
54
+ }
55
+
56
+ static clearConfigCache(_basePath?: string): void {}
57
+
58
+ static configure(basePath?: string): Application
59
+ static configure(options?: ApplicationConfigureOptions): Application
60
+ static configure(basePathOrOptions: string | ApplicationConfigureOptions = "./") {
61
+ if (typeof basePathOrOptions === "string") {
62
+ return new Application(mergeConfigureOptions({ basePath: basePathOrOptions }))
63
+ }
64
+
65
+ return new Application(mergeConfigureOptions(basePathOrOptions))
66
+ }
67
+
68
+ static make<T>(identifier: ContainerIdentifier<T>): T {
69
+ const container = getCurrentApplicationContainer()
70
+ if (!container) {
71
+ throw new Error("Application container is not available. Call run() first.")
72
+ }
73
+
74
+ return container.make<T>(identifier)
75
+ }
76
+
77
+ withProviders(providers: ServiceProvider[]) {
78
+ this.providers.push(...providers)
79
+ return this
80
+ }
81
+
82
+ protected createContainer(): ContainerContract {
83
+ return createRuntimeContainer(this.options.container)
84
+ }
85
+
86
+ protected rememberCleanup(cleanupTasks: Cleanup[], cleanup: void | Cleanup): void {
87
+ if (typeof cleanup === "function") {
88
+ cleanupTasks.push(cleanup)
89
+ }
90
+ }
91
+
92
+ protected createStopHandler(container: ContainerContract, cleanupTasks: Cleanup[]): Cleanup {
93
+ const stop: Cleanup = () => {
94
+ for (const cleanup of cleanupTasks.reverse()) {
95
+ cleanup()
96
+ }
97
+
98
+ clearFacadeCache()
99
+
100
+ container.flush()
101
+
102
+ if (getCurrentApplicationContainer() === container) {
103
+ setCurrentApplicationContainer(null)
104
+ }
105
+ }
106
+
107
+ return stop
108
+ }
109
+
110
+ run() {
111
+ const container = this.createContainer()
112
+
113
+ setCurrentApplicationContainer(container)
114
+ clearFacadeCache()
115
+
116
+ const providers = [new ConfigProvider(this.getConfigItems()), ...this.providers]
117
+ const context: ApplicationRunContext = { container, cleanupTasks: [] }
118
+
119
+ for (const provider of providers) {
120
+ this.rememberCleanup(context.cleanupTasks, provider.register(context))
121
+ }
122
+
123
+ for (const provider of providers) {
124
+ this.rememberCleanup(context.cleanupTasks, provider.boot(context))
125
+ }
126
+
127
+ const stop = this.createStopHandler(container, context.cleanupTasks)
128
+
129
+ window.addEventListener("beforeunload", stop, { once: true })
130
+
131
+ return {
132
+ container,
133
+ stop,
134
+ }
135
+ }
136
+ }
@@ -0,0 +1,20 @@
1
+ import type { ContainerContract, ContainerIdentifier } from "../container/contract"
2
+
3
+ let currentContainer: ContainerContract | null = null
4
+
5
+ export function setCurrentApplicationContainer(container: ContainerContract | null): void {
6
+ currentContainer = container
7
+ }
8
+
9
+ export function getCurrentApplicationContainer(): ContainerContract | null {
10
+ return currentContainer
11
+ }
12
+
13
+ export function makeFromCurrentApplication<T>(identifier: ContainerIdentifier<T>): T {
14
+ const container = getCurrentApplicationContainer()
15
+ if (!container) {
16
+ throw new Error("Application container is not available. Call run() first.")
17
+ }
18
+
19
+ return container.make<T>(identifier)
20
+ }
package/src/index.ts ADDED
@@ -0,0 +1,58 @@
1
+ /// <reference path="./config/env.global.d.ts" />
2
+
3
+ export type { Contract as CacheStore } from "./cache/contract"
4
+ export { default as CacheManager } from "./cache/manager"
5
+ export { CacheProvider } from "./cache/provider"
6
+ export { createConfig } from "./config/discovery"
7
+ export { env } from "./config/env"
8
+ export { ConfigProvider } from "./config/provider"
9
+ export { default as ConfigRepository } from "./config/repository"
10
+ export { default as BuiltinContainer, inject } from "./container/adapters/builtin"
11
+ export type {
12
+ ContainerClass,
13
+ ContainerConcrete,
14
+ ContainerContract,
15
+ ContainerFactory,
16
+ ContainerIdentifier,
17
+ } from "./container/contract"
18
+ export type {
19
+ EventIdentifier,
20
+ EventSubscriber,
21
+ Listener,
22
+ ListenerObject,
23
+ Unsubscribe,
24
+ WildcardListener,
25
+ } from "./events/bus"
26
+ export { Bus } from "./events/bus"
27
+ export { Dispatchable } from "./events/concerns/dispatchable"
28
+ export { EventsProvider } from "./events/provider"
29
+ export type { ApplicationConfigureOptions } from "./foundation/application"
30
+ export { Application } from "./foundation/application"
31
+ export type { Contract as LogContract } from "./log/contract"
32
+ export { default as LogManager } from "./log/manager"
33
+ export { LogProvider } from "./log/provider"
34
+ export type { default as Contract, RendererContext, RootComponent } from "./renderers/contract"
35
+ export type { Adapter as StoreAdapter } from "./store/adapters/contract"
36
+ export { default as IndexedDbAdapter } from "./store/adapters/indexed-db"
37
+ export { default as LocalStorageAdapter } from "./store/adapters/local-storage"
38
+ export { default as MemoryStoreAdapter } from "./store/adapters/memory"
39
+ export { default as StoreManager } from "./store/manager"
40
+ export { StoreProvider } from "./store/provider"
41
+ export { Arr } from "./support/arr"
42
+ export { Collection } from "./support/collection"
43
+ export { Fluent } from "./support/fluent"
44
+ export { registerGlobalHelpers } from "./support/globals"
45
+ export { LazyCollection } from "./support/lazy-collection"
46
+ export { default as Manager } from "./support/manager"
47
+ export { Num } from "./support/num"
48
+ export { send } from "./support/pipeline"
49
+ export type { Cleanup, ServiceProviderContext } from "./support/service-provider"
50
+ export { DeferrableServiceProvider, default as ServiceProvider } from "./support/service-provider"
51
+ export { Str } from "./support/str"
52
+
53
+ import { CacheProvider } from "./cache/provider"
54
+ import { LogProvider } from "./log/provider"
55
+ import { StoreProvider } from "./store/provider"
56
+ import type ServiceProvider from "./support/service-provider"
57
+
58
+ export const defaultProviders: ServiceProvider[] = [new StoreProvider(), new CacheProvider(), new LogProvider()]
@@ -0,0 +1,21 @@
1
+ export interface Contract {
2
+ /**
3
+ * Log a debug-level message for detailed diagnostic information.
4
+ */
5
+ debug(message: string, context?: Record<string, unknown>): void
6
+
7
+ /**
8
+ * Log an informational message about normal application flow.
9
+ */
10
+ info(message: string, context?: Record<string, unknown>): void
11
+
12
+ /**
13
+ * Log a warning about a recoverable or unexpected condition.
14
+ */
15
+ warn(message: string, context?: Record<string, unknown>): void
16
+
17
+ /**
18
+ * Log an error indicating a failure that requires attention.
19
+ */
20
+ error(message: string, context?: Record<string, unknown>): void
21
+ }
@@ -0,0 +1,54 @@
1
+ import type { Contract } from "../contract"
2
+
3
+ const LEVELS = ["debug", "info", "warn", "error"] as const
4
+ type Level = (typeof LEVELS)[number]
5
+
6
+ export default class ConsoleLogger implements Contract {
7
+ protected threshold: number
8
+
9
+ constructor(level: Level = "debug") {
10
+ this.threshold = LEVELS.indexOf(level)
11
+ }
12
+
13
+ /**
14
+ * {@inheritDoc}
15
+ */
16
+ debug(message: string, context?: Record<string, unknown>): void {
17
+ if (this.passes(0)) console.debug(message, ...this.spread(context))
18
+ }
19
+
20
+ /**
21
+ * {@inheritDoc}
22
+ */
23
+ info(message: string, context?: Record<string, unknown>): void {
24
+ if (this.passes(1)) console.info(message, ...this.spread(context))
25
+ }
26
+
27
+ /**
28
+ * {@inheritDoc}
29
+ */
30
+ warn(message: string, context?: Record<string, unknown>): void {
31
+ if (this.passes(2)) console.warn(message, ...this.spread(context))
32
+ }
33
+
34
+ /**
35
+ * {@inheritDoc}
36
+ */
37
+ error(message: string, context?: Record<string, unknown>): void {
38
+ if (this.passes(3)) console.error(message, ...this.spread(context))
39
+ }
40
+
41
+ /**
42
+ * Returns true if the given numeric level meets or exceeds the configured threshold.
43
+ */
44
+ protected passes(level: number): boolean {
45
+ return level >= this.threshold
46
+ }
47
+
48
+ /**
49
+ * Returns the context as a single-element array, or an empty array when absent.
50
+ */
51
+ protected spread(context?: Record<string, unknown>): unknown[] {
52
+ return context !== undefined ? [context] : []
53
+ }
54
+ }
@@ -0,0 +1,23 @@
1
+ import type { Contract } from "../contract"
2
+
3
+ export default class NullLogger implements Contract {
4
+ /**
5
+ * {@inheritDoc}
6
+ */
7
+ debug(_message: string, _context?: Record<string, unknown>): void {}
8
+
9
+ /**
10
+ * {@inheritDoc}
11
+ */
12
+ info(_message: string, _context?: Record<string, unknown>): void {}
13
+
14
+ /**
15
+ * {@inheritDoc}
16
+ */
17
+ warn(_message: string, _context?: Record<string, unknown>): void {}
18
+
19
+ /**
20
+ * {@inheritDoc}
21
+ */
22
+ error(_message: string, _context?: Record<string, unknown>): void {}
23
+ }
@@ -0,0 +1,46 @@
1
+ import type { Contract } from "../contract"
2
+
3
+ export default class StackLogger implements Contract {
4
+ constructor(protected drivers: Contract[]) {}
5
+
6
+ /**
7
+ * {@inheritDoc}
8
+ */
9
+ debug(message: string, context?: Record<string, unknown>): void {
10
+ this.dispatch("debug", message, context)
11
+ }
12
+
13
+ /**
14
+ * {@inheritDoc}
15
+ */
16
+ info(message: string, context?: Record<string, unknown>): void {
17
+ this.dispatch("info", message, context)
18
+ }
19
+
20
+ /**
21
+ * {@inheritDoc}
22
+ */
23
+ warn(message: string, context?: Record<string, unknown>): void {
24
+ this.dispatch("warn", message, context)
25
+ }
26
+
27
+ /**
28
+ * {@inheritDoc}
29
+ */
30
+ error(message: string, context?: Record<string, unknown>): void {
31
+ this.dispatch("error", message, context)
32
+ }
33
+
34
+ /**
35
+ * Forwards the log call to each driver in order, swallowing any individual driver errors.
36
+ */
37
+ protected dispatch(level: keyof Contract, message: string, context?: Record<string, unknown>): void {
38
+ for (const driver of this.drivers) {
39
+ try {
40
+ driver[level](message, context)
41
+ } catch {
42
+ // swallow — a logging failure must not crash the application
43
+ }
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,76 @@
1
+ import type ConfigRepository from "../config/repository"
2
+ import Manager from "../support/manager"
3
+ import type { Contract } from "./contract"
4
+ import ConsoleLogger from "./drivers/console"
5
+ import NullLogger from "./drivers/null"
6
+ import StackLogger from "./drivers/stack"
7
+
8
+ export default class LogManager extends Manager<Contract, Contract> implements Contract {
9
+ /**
10
+ * {@inheritDoc}
11
+ */
12
+ debug(message: string, context?: Record<string, unknown>): void {
13
+ this.resolve(this.active).debug(message, context)
14
+ }
15
+
16
+ /**
17
+ * {@inheritDoc}
18
+ */
19
+ info(message: string, context?: Record<string, unknown>): void {
20
+ this.resolve(this.active).info(message, context)
21
+ }
22
+
23
+ /**
24
+ * {@inheritDoc}
25
+ */
26
+ warn(message: string, context?: Record<string, unknown>): void {
27
+ this.resolve(this.active).warn(message, context)
28
+ }
29
+
30
+ /**
31
+ * {@inheritDoc}
32
+ */
33
+ error(message: string, context?: Record<string, unknown>): void {
34
+ this.resolve(this.active).error(message, context)
35
+ }
36
+
37
+ /**
38
+ * Returns the driver as-is; no wrapping is needed for log drivers.
39
+ */
40
+ protected createDriver(raw: Contract): Contract {
41
+ return raw
42
+ }
43
+
44
+ /**
45
+ * Returns the human-readable driver type label used in error messages.
46
+ */
47
+ protected driverType(): string {
48
+ return "Log driver"
49
+ }
50
+
51
+ /**
52
+ * Creates a LogManager from the application config, registering the built-in console, null, and stack drivers.
53
+ */
54
+ static fromConfig(config: ConfigRepository): LogManager {
55
+ const active = config.get<string>("logging.default", "console")
56
+ const manager = new LogManager({}, active, config)
57
+
58
+ manager.extend("console", (cfg) => {
59
+ const level = cfg.get<"debug" | "info" | "warn" | "error">("logging.drivers.console.level", "debug")
60
+ return new ConsoleLogger(level ?? "debug")
61
+ })
62
+
63
+ manager.extend("null", (_cfg) => new NullLogger())
64
+
65
+ manager.extend("stack", (cfg) => {
66
+ const names = cfg.get<string[]>("logging.drivers.stack.drivers", []) ?? []
67
+ return new StackLogger(names.map((name) => manager.resolve(name)))
68
+ })
69
+
70
+ // Manager constructor only accepts pre-built drivers for active validation;
71
+ // re-set after custom creators are registered so the configured name resolves correctly.
72
+ manager.active = active
73
+
74
+ return manager
75
+ }
76
+ }
@@ -0,0 +1,11 @@
1
+ import type ConfigRepository from "../config/repository"
2
+ import type { ServiceProviderContext } from "../support/service-provider"
3
+ import ServiceProvider from "../support/service-provider"
4
+ import LogManager from "./manager"
5
+
6
+ export class LogProvider extends ServiceProvider {
7
+ register({ container }: ServiceProviderContext): void {
8
+ container.singleton("log", (c) => LogManager.fromConfig(c.make<ConfigRepository>("config")))
9
+ container.singleton(LogManager, (c) => c.make<LogManager>("log"))
10
+ }
11
+ }
package/src/react.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { default as Renderer } from "./renderers/adapters/react"
2
+ export { ApplicationProvider, ContextProvider, useContainer, useService } from "./runtimes/react"