@raubjo/architect-core 0.1.0 → 0.1.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 (67) hide show
  1. package/README.md +162 -0
  2. package/bun.lock +82 -1
  3. package/package.json +54 -6
  4. package/src/cache/cache.ts +2 -2
  5. package/src/cache/manager.ts +93 -83
  6. package/src/config/adapters/esm.ts +26 -0
  7. package/src/config/clone.ts +5 -5
  8. package/src/config/env.global.d.ts +2 -1
  9. package/src/config/env.ts +53 -55
  10. package/src/config/env_test.helpers.ts +58 -0
  11. package/src/config/repository.ts +180 -142
  12. package/src/container/adapters/builtin.ts +347 -0
  13. package/src/container/adapters/inversify.ts +123 -0
  14. package/src/container/contract.ts +58 -0
  15. package/src/container/runtime.ts +149 -0
  16. package/src/filesystem/adapters/local.ts +92 -83
  17. package/src/filesystem/adapters/local_test.helpers.ts +50 -0
  18. package/src/filesystem/filesystem.ts +11 -11
  19. package/src/foundation/application.ts +205 -175
  20. package/src/foundation/application_test.helpers.ts +31 -0
  21. package/src/index.ts +15 -6
  22. package/src/react.ts +2 -0
  23. package/src/renderers/adapters/react.tsx +35 -0
  24. package/src/renderers/adapters/solid.tsx +32 -0
  25. package/src/renderers/adapters/svelte.ts +44 -0
  26. package/src/renderers/adapters/vue.ts +28 -0
  27. package/src/renderers/contract.ts +15 -0
  28. package/src/runtimes/react.tsx +24 -12
  29. package/src/runtimes/solid.tsx +30 -0
  30. package/src/runtimes/svelte.ts +23 -0
  31. package/src/runtimes/vue.ts +20 -0
  32. package/src/solid.ts +2 -0
  33. package/src/storage/adapters/contract.ts +10 -0
  34. package/src/storage/adapters/indexed-db.ts +170 -156
  35. package/src/storage/adapters/local-storage.ts +34 -34
  36. package/src/storage/adapters/memory.ts +25 -25
  37. package/src/storage/manager.ts +65 -61
  38. package/src/storage/storage.ts +1 -8
  39. package/src/support/facades/cache.ts +40 -40
  40. package/src/support/facades/config.ts +78 -48
  41. package/src/support/facades/facade.ts +43 -28
  42. package/src/support/facades/storage.ts +41 -41
  43. package/src/support/service-provider.ts +11 -11
  44. package/src/support/str.ts +94 -90
  45. package/src/support/str_test.helpers.ts +26 -0
  46. package/src/svelte.ts +2 -0
  47. package/src/vue.ts +2 -0
  48. package/tsconfig.json +16 -0
  49. package/coverage/lcov.info +0 -1078
  50. package/src/config/app.ts +0 -5
  51. package/src/rendering/adapters/react.tsx +0 -27
  52. package/src/rendering/renderer.ts +0 -13
  53. package/src/support/providers/config-service-provider.ts +0 -19
  54. package/tests/application.test.ts +0 -236
  55. package/tests/cache-facade.test.ts +0 -45
  56. package/tests/cache.test.ts +0 -68
  57. package/tests/config-clone.test.ts +0 -31
  58. package/tests/config-env.test.ts +0 -88
  59. package/tests/config-facade.test.ts +0 -96
  60. package/tests/config-repository.test.ts +0 -124
  61. package/tests/facade-base.test.ts +0 -80
  62. package/tests/filesystem.test.ts +0 -81
  63. package/tests/runtime-react.test.tsx +0 -37
  64. package/tests/service-provider.test.ts +0 -23
  65. package/tests/storage-facade.test.ts +0 -46
  66. package/tests/storage.test.ts +0 -264
  67. package/tests/str.test.ts +0 -73
@@ -1,207 +1,237 @@
1
- import CacheManager from "../cache/manager";
2
- import { Container } from "inversify";
3
- import appConfig from "../config/app";
4
- import { cloneConfigItems } from "../config/clone";
5
- import { registerGlobalEnv } from "../config/env";
6
- import ConfigRepository, { type ConfigItems } from "../config/repository";
7
- import { FileSystem } from "../filesystem/filesystem";
8
- import LocalAdapter, { __localAdapterTesting } from "../filesystem/adapters/local";
9
- import ReactRenderer from "../rendering/adapters/react";
10
- import type { RendererAdapter, RootComponent } from "../rendering/renderer";
11
- import StorageManager from "../storage/manager";
1
+ import CacheManager from "@/cache/manager";
2
+ import { cloneConfigItems } from "@/config/clone";
3
+ import { registerGlobalEnv } from "@/config/env";
4
+ import ConfigRepository, { type ConfigItems } from "@/config/repository";
5
+ import type {
6
+ ContainerContract,
7
+ ContainerIdentifier,
8
+ } from "@/container/contract";
9
+ import {
10
+ createRuntimeContainer,
11
+ mergeContainerRuntimeOptions,
12
+ type ContainerRuntimeOptions,
13
+ } from "@/container/runtime";
14
+ import type Contract from "@/renderers/contract";
15
+ import type { RootComponent } from "@/renderers/contract";
16
+ import StorageManager from "@/storage/manager";
12
17
  import ServiceProvider, {
13
- type Cleanup,
14
- type ServiceProviderContext,
15
- } from "../support/service-provider";
16
- import { registerGlobalStr } from "../support/str";
17
- import Facade from "../support/facades/facade";
18
+ type Cleanup,
19
+ type ServiceProviderContext,
20
+ } from "@/support/service-provider";
21
+ import { registerGlobalStr } from "@/support/str";
22
+ import Facade from "@/support/facades/facade";
18
23
 
19
24
  type StartupHandler = (context: ServiceProviderContext) => void | Cleanup;
20
25
  type ServiceRegistrar = (context: ServiceProviderContext) => void | Cleanup;
21
26
 
27
+ export type ApplicationConfigureOptions = {
28
+ basePath?: string;
29
+ container?: ContainerRuntimeOptions;
30
+ config?: ConfigItems;
31
+ };
32
+
33
+ type ApplicationResolvedOptions = {
34
+ basePath: string;
35
+ container: ReturnType<typeof mergeContainerRuntimeOptions>;
36
+ config: ConfigItems;
37
+ };
38
+
22
39
  registerGlobalEnv();
23
40
  registerGlobalStr();
24
41
 
25
- export const __applicationTesting = {
26
- ...__localAdapterTesting,
27
- };
42
+ function mergeConfigureOptions(
43
+ options: ApplicationConfigureOptions = {},
44
+ ): ApplicationResolvedOptions {
45
+ return {
46
+ basePath: options.basePath ?? "./",
47
+ container: mergeContainerRuntimeOptions(options.container),
48
+ config: options.config ?? {},
49
+ };
50
+ }
28
51
 
29
52
  export class Application {
30
- protected static container: Container | null = null;
31
- protected static configCache = new Map<string, ConfigItems>();
32
- protected static fileSystem = new FileSystem(new LocalAdapter());
33
-
34
- protected providers: ServiceProvider[];
35
- protected serviceRegistrars: ServiceRegistrar[];
36
- protected startupHandlers: StartupHandler[];
37
- protected rootElementId: string;
38
- protected RootComponent: RootComponent | null;
39
- protected renderer: RendererAdapter | null;
40
- protected basePath: string;
41
-
42
- constructor(basePath = "./") {
43
- this.basePath = basePath;
44
- this.providers = this.getDefaultProviders();
45
- this.serviceRegistrars = [];
46
- this.startupHandlers = [];
47
- this.rootElementId = "root";
48
- this.RootComponent = null;
49
- this.renderer = null;
50
- }
51
-
52
- protected getDefaultProviders(): ServiceProvider[] {
53
- return [];
54
- }
55
-
56
- protected static getCachedConfigItems(basePath: string): ConfigItems {
57
- const cacheKey = __localAdapterTesting.normalizeBasePath(basePath) || ".";
58
-
59
- if (!Application.configCache.has(cacheKey)) {
60
- const loaded = {
61
- app: appConfig,
62
- ...Application.fileSystem.loadConfigItems(basePath),
63
- };
64
- Application.configCache.set(cacheKey, loaded);
53
+ protected static container: ContainerContract | null = null;
54
+
55
+ protected providers: ServiceProvider[];
56
+ protected serviceRegistrars: ServiceRegistrar[];
57
+ protected startupHandlers: StartupHandler[];
58
+ protected rootElementId: string;
59
+ protected RootComponent: RootComponent | null;
60
+ protected renderer: Contract | null;
61
+ protected options: ApplicationResolvedOptions;
62
+
63
+ constructor(options: ApplicationResolvedOptions) {
64
+ this.options = options;
65
+ this.providers = this.getDefaultProviders();
66
+ this.serviceRegistrars = [];
67
+ this.startupHandlers = [];
68
+ this.rootElementId = "root";
69
+ this.RootComponent = null;
70
+ this.renderer = null;
65
71
  }
66
72
 
67
- // Give each application instance its own mutable repository data.
68
- return cloneConfigItems(Application.configCache.get(cacheKey) as ConfigItems);
69
- }
70
-
71
- static clearConfigCache(basePath?: string): void {
72
- if (typeof basePath === "string") {
73
- const cacheKey = __localAdapterTesting.normalizeBasePath(basePath) || ".";
74
- Application.configCache.delete(cacheKey);
75
- return;
73
+ protected getDefaultProviders(): ServiceProvider[] {
74
+ return [];
76
75
  }
77
76
 
78
- Application.configCache.clear();
79
- }
77
+ protected getConfigItems(): ConfigItems {
78
+ // Give each application instance its own mutable repository data.
79
+ return cloneConfigItems(this.options.config);
80
+ }
80
81
 
81
- static configure(basePath = "./") {
82
- return new Application(basePath);
83
- }
82
+ static clearConfigCache(_basePath?: string): void {}
83
+
84
+ static configure(basePath?: string): Application;
85
+ static configure(options?: ApplicationConfigureOptions): Application;
86
+ static configure(
87
+ basePathOrOptions: string | ApplicationConfigureOptions = "./",
88
+ ) {
89
+ if (typeof basePathOrOptions === "string") {
90
+ return new Application(
91
+ mergeConfigureOptions({ basePath: basePathOrOptions }),
92
+ );
93
+ }
84
94
 
85
- static make<T>(identifier: Parameters<Container["get"]>[0]): T {
86
- if (!Application.container) {
87
- throw new Error("Application container is not available. Call run() first.");
95
+ return new Application(mergeConfigureOptions(basePathOrOptions));
88
96
  }
89
97
 
90
- return Application.container.get<T>(identifier);
91
- }
92
-
93
- withProviders(providers: ServiceProvider[]) {
94
- this.providers.push(...providers);
95
- return this;
96
- }
97
-
98
- withServices(registerServices: ServiceRegistrar) {
99
- this.serviceRegistrars.push(registerServices);
100
- return this;
101
- }
102
-
103
- withStartup(startupHandler: StartupHandler) {
104
- this.startupHandlers.push(startupHandler);
105
- return this;
106
- }
107
-
108
- withRoot(RootComponent: RootComponent, options: { rootElementId?: string } = {}) {
109
- this.RootComponent = RootComponent;
110
- this.rootElementId = options.rootElementId ?? this.rootElementId;
111
- return this;
112
- }
113
-
114
- withRenderer(renderer: RendererAdapter) {
115
- this.renderer = renderer;
116
- return this;
117
- }
118
-
119
- run() {
120
- const container = new Container({ defaultScope: "Singleton" });
121
- Application.container = container;
122
- Facade.clearResolvedInstances();
123
- const context = { container };
124
- const cleanupTasks: Cleanup[] = [];
125
- const configRepository = new ConfigRepository(
126
- Application.getCachedConfigItems(this.basePath),
127
- );
128
- const storageManager = StorageManager.fromConfig(configRepository);
129
- const cacheManager = CacheManager.fromConfig(configRepository);
130
-
131
- container.bind("config").toConstantValue(configRepository);
132
- container.bind(ConfigRepository).toConstantValue(configRepository);
133
- container.bind("storage").toConstantValue(storageManager);
134
- container.bind(StorageManager).toConstantValue(storageManager);
135
- container.bind("cache").toConstantValue(cacheManager);
136
- container.bind(CacheManager).toConstantValue(cacheManager);
137
-
138
- for (const provider of this.providers) {
139
- if (typeof provider.register === "function") {
140
- const cleanup = provider.register(context);
141
- if (typeof cleanup === "function") {
142
- cleanupTasks.push(cleanup);
98
+ static make<T>(identifier: ContainerIdentifier<T>): T {
99
+ if (!Application.container) {
100
+ throw new Error(
101
+ "Application container is not available. Call run() first.",
102
+ );
143
103
  }
144
- }
104
+
105
+ return Application.container.make<T>(identifier);
145
106
  }
146
107
 
147
- for (const registerServices of this.serviceRegistrars) {
148
- const cleanup = registerServices(context);
149
- if (typeof cleanup === "function") {
150
- cleanupTasks.push(cleanup);
151
- }
108
+ withProviders(providers: ServiceProvider[]) {
109
+ this.providers.push(...providers);
110
+ return this;
152
111
  }
153
112
 
154
- for (const provider of this.providers) {
155
- if (typeof provider.boot === "function") {
156
- const cleanup = provider.boot(context);
157
- if (typeof cleanup === "function") {
158
- cleanupTasks.push(cleanup);
159
- }
160
- }
113
+ withServices(registerServices: ServiceRegistrar) {
114
+ this.serviceRegistrars.push(registerServices);
115
+ return this;
161
116
  }
162
117
 
163
- for (const startupHandler of this.startupHandlers) {
164
- const cleanup = startupHandler(context);
165
- if (typeof cleanup === "function") {
166
- cleanupTasks.push(cleanup);
167
- }
118
+ withStartup(startupHandler: StartupHandler) {
119
+ this.startupHandlers.push(startupHandler);
120
+ return this;
168
121
  }
169
122
 
170
- let rendererCleanup: Cleanup = () => {};
171
- if (this.renderer) {
172
- if (!this.RootComponent) {
173
- throw new Error("Root component is required when using a custom renderer.");
174
- }
175
- rendererCleanup = this.renderer.render({
176
- ...context,
177
- RootComponent: this.RootComponent,
178
- rootElementId: this.rootElementId,
179
- }) ?? rendererCleanup;
180
- } else if (this.RootComponent) {
181
- rendererCleanup = new ReactRenderer().render({
182
- ...context,
183
- RootComponent: this.RootComponent,
184
- rootElementId: this.rootElementId,
185
- });
123
+ withRoot(
124
+ RootComponent: RootComponent,
125
+ options: { rootElementId?: string } = {},
126
+ ) {
127
+ this.RootComponent = RootComponent;
128
+ this.rootElementId = options.rootElementId ?? this.rootElementId;
129
+ return this;
186
130
  }
187
131
 
188
- const stop: Cleanup = () => {
189
- rendererCleanup();
190
- for (const cleanup of cleanupTasks.reverse()) {
191
- cleanup();
192
- }
193
- Facade.clearResolvedInstances();
194
- container.unbindAll();
195
- if (Application.container === container) {
196
- Application.container = null;
197
- }
198
- };
132
+ withRenderer(renderer: Contract) {
133
+ this.renderer = renderer;
134
+ return this;
135
+ }
199
136
 
200
- window.addEventListener("beforeunload", stop, { once: true });
137
+ protected createContainer(): ContainerContract {
138
+ return createRuntimeContainer(this.options.container);
139
+ }
201
140
 
202
- return {
203
- container,
204
- stop,
205
- };
206
- }
141
+ run() {
142
+ const container = this.createContainer();
143
+
144
+ Application.container = container;
145
+
146
+ Facade.clearResolvedInstances();
147
+
148
+ const context = { container };
149
+ const cleanupTasks: Cleanup[] = [];
150
+ const configRepository = new ConfigRepository(this.getConfigItems());
151
+ const storageManager = StorageManager.fromConfig(configRepository);
152
+ const cacheManager = CacheManager.fromConfig(configRepository);
153
+
154
+ container.instance("config", configRepository);
155
+ container.instance(ConfigRepository, configRepository);
156
+ container.instance("storage", storageManager);
157
+ container.instance(StorageManager, storageManager);
158
+ container.instance("cache", cacheManager);
159
+ container.instance(CacheManager, cacheManager);
160
+
161
+ for (const provider of this.providers) {
162
+ if (typeof provider.register === "function") {
163
+ const cleanup = provider.register(context);
164
+ if (typeof cleanup === "function") {
165
+ cleanupTasks.push(cleanup);
166
+ }
167
+ }
168
+ }
169
+
170
+ for (const registerServices of this.serviceRegistrars) {
171
+ const cleanup = registerServices(context);
172
+ if (typeof cleanup === "function") {
173
+ cleanupTasks.push(cleanup);
174
+ }
175
+ }
176
+
177
+ for (const provider of this.providers) {
178
+ if (typeof provider.boot === "function") {
179
+ const cleanup = provider.boot(context);
180
+ if (typeof cleanup === "function") {
181
+ cleanupTasks.push(cleanup);
182
+ }
183
+ }
184
+ }
185
+
186
+ for (const startupHandler of this.startupHandlers) {
187
+ const cleanup = startupHandler(context);
188
+ if (typeof cleanup === "function") {
189
+ cleanupTasks.push(cleanup);
190
+ }
191
+ }
192
+
193
+ let rendererCleanup: Cleanup = () => {};
194
+
195
+ if (this.renderer) {
196
+ if (!this.RootComponent) {
197
+ throw new Error(
198
+ "Root component is required when using a custom renderer.",
199
+ );
200
+ }
201
+
202
+ rendererCleanup =
203
+ this.renderer.render({
204
+ ...context,
205
+ RootComponent: this.RootComponent,
206
+ rootElementId: this.rootElementId,
207
+ }) ?? rendererCleanup;
208
+ } else if (this.RootComponent) {
209
+ throw new Error(
210
+ "Renderer is required when root component is set. Install a renderer feature (react/solid/svelte/vue) and call withRenderer(...).",
211
+ );
212
+ }
213
+
214
+ const stop: Cleanup = () => {
215
+ rendererCleanup();
216
+
217
+ for (const cleanup of cleanupTasks.reverse()) {
218
+ cleanup();
219
+ }
220
+
221
+ Facade.clearResolvedInstances();
222
+
223
+ container.flush();
224
+
225
+ if (Application.container === container) {
226
+ Application.container = null;
227
+ }
228
+ };
229
+
230
+ window.addEventListener("beforeunload", stop, { once: true });
231
+
232
+ return {
233
+ container,
234
+ stop,
235
+ };
236
+ }
207
237
  }
@@ -0,0 +1,31 @@
1
+ import {
2
+ mergeContainerRuntimeOptions,
3
+ packageJsonHasDependency,
4
+ readContainerFactoryRegistry,
5
+ readPackageJsonCandidates,
6
+ type ContainerRuntimeOptions,
7
+ } from "@/container/runtime";
8
+ import type { ConfigItems } from "@/config/repository";
9
+ import { localAdapterTestingHelpers } from "@/filesystem/adapters/local_test.helpers";
10
+
11
+ type ApplicationConfigureOptions = {
12
+ basePath?: string;
13
+ container?: ContainerRuntimeOptions;
14
+ config?: ConfigItems;
15
+ };
16
+
17
+ function mergeConfigureOptions(options: ApplicationConfigureOptions = {}) {
18
+ return {
19
+ basePath: options.basePath ?? "./",
20
+ container: mergeContainerRuntimeOptions(options.container),
21
+ config: options.config ?? {},
22
+ };
23
+ }
24
+
25
+ export const applicationTestingHelpers = {
26
+ ...localAdapterTestingHelpers,
27
+ mergeConfigureOptions,
28
+ readPackageJsonCandidates,
29
+ packageJsonHasDependency,
30
+ readContainerFactoryRegistry,
31
+ };
package/src/index.ts CHANGED
@@ -1,18 +1,25 @@
1
1
  /// <reference path="./config/env.global.d.ts" />
2
2
 
3
3
  export { Application } from "./foundation/application";
4
+ export type { ApplicationConfigureOptions } from "./foundation/application";
5
+ export { default as BuiltinContainer } from "./container/adapters/builtin";
6
+ export { inject as injectDependency } from "./container/adapters/builtin";
7
+ export type {
8
+ ContainerContract,
9
+ ContainerIdentifier,
10
+ ContainerFactory,
11
+ ContainerClass,
12
+ ContainerConcrete,
13
+ } from "./container/contract";
4
14
  export { default as ServiceProvider } from "./support/service-provider";
5
15
  export { DeferrableServiceProvider } from "./support/service-provider";
6
16
  export type { Cleanup, ServiceProviderContext } from "./support/service-provider";
7
- export { default as ConfigServiceProvider } from "./support/providers/config-service-provider";
8
17
 
9
- export { ApplicationProvider, useService } from "./runtimes/react";
10
18
  export type {
11
- RendererAdapter,
19
+ default as Contract,
12
20
  RendererContext,
13
21
  RootComponent,
14
- } from "./rendering/renderer";
15
- export { default as ReactRenderer } from "./rendering/adapters/react";
22
+ } from "./renderers/contract";
16
23
  export { default as ConfigRepository } from "./config/repository";
17
24
  export { env } from "./config/env";
18
25
  export { default as Str } from "./support/str";
@@ -25,9 +32,11 @@ export { default as Cache } from "./support/facades/cache";
25
32
  export { default as StorageManager } from "./storage/manager";
26
33
  export { default as StorageFacade } from "./support/facades/storage";
27
34
  export { default as Storage } from "./support/facades/storage";
28
- export type { StorageAdapter } from "./storage/storage";
35
+ export type { Adapter as StorageAdapter } from "./storage/adapters/contract";
29
36
  export { default as MemoryStorageAdapter } from "./storage/adapters/memory";
30
37
  export { default as LocalStorageAdapter } from "./storage/adapters/local-storage";
31
38
  export { default as IndexedDbAdapter } from "./storage/adapters/indexed-db";
32
39
  export { FileSystem } from "./filesystem/filesystem";
33
40
  export { default as LocalAdapter } from "./filesystem/adapters/local";
41
+ export { default as SvelteRenderer } from "./renderers/adapters/svelte";
42
+ export { default as VueRenderer } from "./renderers/adapters/vue";
package/src/react.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { ApplicationProvider, useService } from "./runtimes/react";
2
+ export { default as Renderer } from "./renderers/adapters/react";
@@ -0,0 +1,35 @@
1
+ import ReactDOM from "react-dom/client";
2
+ import { createElement } from "react";
3
+ import type { ReactElement } from "react";
4
+ import { ApplicationProvider } from "../../runtimes/react";
5
+ import type Contract from "../contract";
6
+ import type { RendererContext } from "../contract";
7
+ import type { Cleanup } from "../../support/service-provider";
8
+
9
+ type ReactRootComponent = () => ReactElement | null;
10
+
11
+ export default class ReactRenderer implements Contract {
12
+ constructor() {}
13
+
14
+ render({
15
+ RootComponent,
16
+ container,
17
+ rootElementId,
18
+ }: RendererContext): Cleanup {
19
+ const mountNode = document.getElementById(rootElementId);
20
+ if (!mountNode) {
21
+ throw new Error(`Missing mount node #${rootElementId}.`);
22
+ }
23
+
24
+ const root = ReactDOM.createRoot(mountNode);
25
+ root.render(
26
+ createElement(
27
+ ApplicationProvider,
28
+ { container },
29
+ createElement(RootComponent as ReactRootComponent),
30
+ ),
31
+ );
32
+
33
+ return () => root.unmount();
34
+ }
35
+ }
@@ -0,0 +1,32 @@
1
+ import { render } from "solid-js/web";
2
+ import { createComponent, type JSX } from "solid-js";
3
+ import type { Cleanup } from "../../support/service-provider";
4
+ import { ApplicationProvider } from "../../runtimes/solid";
5
+ import type Contract from "../contract";
6
+ import type { RendererContext } from "../contract";
7
+
8
+ type SolidRootComponent = () => JSX.Element;
9
+
10
+ export default class SolidRenderer implements Contract {
11
+ constructor() {}
12
+
13
+ render({
14
+ RootComponent,
15
+ container,
16
+ rootElementId,
17
+ }: RendererContext): Cleanup {
18
+ const mountNode = document.getElementById(rootElementId);
19
+ if (!mountNode) {
20
+ throw new Error(`Missing mount node #${rootElementId}.`);
21
+ }
22
+
23
+ return render(
24
+ () =>
25
+ createComponent(ApplicationProvider, {
26
+ container,
27
+ children: () => (RootComponent as SolidRootComponent)(),
28
+ }),
29
+ mountNode,
30
+ );
31
+ }
32
+ }
@@ -0,0 +1,44 @@
1
+ import type { Cleanup } from "../../support/service-provider";
2
+ import type Contract from "../contract";
3
+ import type { RendererContext } from "../contract";
4
+
5
+ type SvelteComponentInstance = {
6
+ $destroy?: () => void;
7
+ destroy?: () => void;
8
+ };
9
+
10
+ type SvelteComponentConstructor = new (options: {
11
+ target: Element;
12
+ props?: Record<string, unknown>;
13
+ }) => SvelteComponentInstance;
14
+
15
+ export default class SvelteRenderer implements Contract {
16
+ constructor() {}
17
+
18
+ render({
19
+ RootComponent,
20
+ container,
21
+ rootElementId,
22
+ }: RendererContext): Cleanup {
23
+ const mountNode = document.getElementById(rootElementId);
24
+ if (!mountNode) {
25
+ throw new Error(`Missing mount node #${rootElementId}.`);
26
+ }
27
+
28
+ const Component = RootComponent as SvelteComponentConstructor;
29
+ const instance = new Component({
30
+ target: mountNode,
31
+ props: { container },
32
+ });
33
+
34
+ return () => {
35
+ if (typeof instance.$destroy === "function") {
36
+ instance.$destroy();
37
+ return;
38
+ }
39
+ if (typeof instance.destroy === "function") {
40
+ instance.destroy();
41
+ }
42
+ };
43
+ }
44
+ }
@@ -0,0 +1,28 @@
1
+ import { createApp, type Component } from "vue";
2
+ import type { Cleanup } from "../../support/service-provider";
3
+ import { containerKey } from "../../runtimes/vue";
4
+ import type Contract from "../contract";
5
+ import type { RendererContext } from "../contract";
6
+
7
+ type VueRootComponent = Component;
8
+
9
+ export default class VueRenderer implements Contract {
10
+ constructor() {}
11
+
12
+ render({
13
+ RootComponent,
14
+ container,
15
+ rootElementId,
16
+ }: RendererContext): Cleanup {
17
+ const mountNode = document.getElementById(rootElementId);
18
+ if (!mountNode) {
19
+ throw new Error(`Missing mount node #${rootElementId}.`);
20
+ }
21
+
22
+ const app = createApp(RootComponent as VueRootComponent);
23
+ app.provide(containerKey, container);
24
+ app.mount(mountNode);
25
+
26
+ return () => app.unmount();
27
+ }
28
+ }
@@ -0,0 +1,15 @@
1
+ import type {
2
+ Cleanup,
3
+ ServiceProviderContext,
4
+ } from "../support/service-provider";
5
+
6
+ export type RootComponent = unknown;
7
+
8
+ export type RendererContext = ServiceProviderContext & {
9
+ RootComponent: RootComponent;
10
+ rootElementId: string;
11
+ };
12
+
13
+ export default interface Contract {
14
+ render(context: RendererContext): void | Cleanup;
15
+ }