@raubjo/architect-core 0.1.0 → 0.1.2

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 +216 -0
  2. package/bun.lock +82 -1
  3. package/package.json +55 -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 +70 -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
package/README.md ADDED
@@ -0,0 +1,216 @@
1
+ # @raubjo/architect-core
2
+
3
+ A Laravel-inspired application container for reactive frontends.
4
+
5
+ The goal is simple: define and configure business rules in providers, then resolve services where you need them without rebuilding wiring at each call site.
6
+
7
+ You keep inversion of control, lifecycle hooks, and runtime reactivity.
8
+
9
+ ## Why this package
10
+
11
+ - Centralized service registration via providers
12
+ - Predictable lifecycle (`register` then `boot`)
13
+ - Framework runtime helpers (`react`, `solid`, `svelte`, `vue`)
14
+ - Config repository and facades
15
+
16
+ If you are familiar with Laravel service providers and container bindings, this should feel familiar.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ bun add @raubjo/architect-core
22
+ ```
23
+
24
+ If you want the Inversify adapter:
25
+
26
+ ```bash
27
+ bun add inversify reflect-metadata
28
+ ```
29
+
30
+ For framework-specific runtime helpers, import from subpaths like `@raubjo/architect-core/react`.
31
+
32
+ ## Quick start
33
+
34
+ ```ts
35
+ import { Application } from "@raubjo/architect-core";
36
+
37
+ const { container, stop } = Application.configure({
38
+ config: {
39
+ app: { name: "IOC Application" },
40
+ },
41
+ })
42
+ .withProviders([])
43
+ .run();
44
+
45
+ // Resolve anywhere after run()
46
+ const config = container.get("config");
47
+
48
+ // Call on teardown if needed
49
+ stop();
50
+ ```
51
+
52
+ ## Container adapters
53
+
54
+ `Application` supports three container adapter modes:
55
+
56
+ - `builtin`: always use the built-in container implementation.
57
+ - `inversify`: require and use the registered Inversify adapter.
58
+ - `auto`: use Inversify when available, otherwise fallback to `builtin`.
59
+
60
+ ### Builtin container
61
+
62
+ Builtin is the default/fallback container and supports:
63
+
64
+ - Constructor injection via `design:paramtypes` metadata
65
+ - `@inject(...)` parameter token overrides
66
+ - `singleton`, `transient`, and `instance` registration
67
+ - Fluent `bind(...).to(...)/toDynamicValue(...)/toConstantValue(...)`
68
+
69
+ ```ts
70
+ import "reflect-metadata";
71
+ import { Application, injectDependency } from "@raubjo/architect-core";
72
+
73
+ class Logger {}
74
+
75
+ class Service {
76
+ constructor(
77
+ public readonly logger: Logger,
78
+ @injectDependency("config.appName") public readonly appName: string,
79
+ ) {}
80
+ }
81
+
82
+ const { container } = Application.configure({
83
+ container: { adapter: "builtin" },
84
+ config: {
85
+ appName: "IOC Application",
86
+ },
87
+ })
88
+ .withServices(({ container }) => {
89
+ container.singleton(Logger, Logger);
90
+ container.transient(Service, Service);
91
+ container.instance("config.appName", "IOC Application");
92
+ })
93
+ .run();
94
+
95
+ const service = container.make(Service);
96
+ ```
97
+
98
+ ### Inversify container
99
+
100
+ To use Inversify, install `inversify` and `reflect-metadata`, then register a factory on `globalThis.__iocContainerFactoryRegistry`.
101
+
102
+ ```ts
103
+ import "reflect-metadata";
104
+ import { Application } from "@raubjo/architect-core";
105
+ import InversifyContainer from "@raubjo/architect-core/container/adapters/inversify";
106
+
107
+ globalThis.__iocContainerFactoryRegistry = {
108
+ inversify: () => new InversifyContainer(),
109
+ };
110
+
111
+ const running = Application.configure({
112
+ container: { adapter: "inversify" },
113
+ config: {
114
+ app: { name: "IOC Application" },
115
+ },
116
+ }).run();
117
+ ```
118
+
119
+ You can also keep `adapter: "auto"` and let runtime detection pick Inversify when `package.json` includes it.
120
+
121
+ ## Service provider example
122
+
123
+ Use providers to define business rules and service construction once.
124
+
125
+ ```ts
126
+ import { injectable } from "inversify";
127
+ import {
128
+ ServiceProvider,
129
+ type ServiceProviderContext,
130
+ } from "@raubjo/architect-core";
131
+
132
+ @injectable()
133
+ class PricingService {
134
+ constructor(private readonly taxRate: number) {}
135
+
136
+ quote(subtotal: number) {
137
+ return subtotal + subtotal * this.taxRate;
138
+ }
139
+ }
140
+
141
+ class PricingServiceProvider extends ServiceProvider {
142
+ register({ container }: ServiceProviderContext) {
143
+ container.bind("services.pricing").toDynamicValue((ctx) => {
144
+ const config = ctx.container.get("config") as {
145
+ float: (key: string, defaultValue?: number) => number;
146
+ };
147
+
148
+ const taxRate = config.float("pricing.taxRate", 0.07);
149
+ return new PricingService(taxRate);
150
+ });
151
+ }
152
+
153
+ boot({ container }: ServiceProviderContext) {
154
+ // Optional startup work after all providers register.
155
+ // Example: warm caches, subscribe to events, etc.
156
+ void container;
157
+ }
158
+ }
159
+ ```
160
+
161
+ ## Application configuration example
162
+
163
+ ```ts
164
+ import "reflect-metadata";
165
+ import { Application } from "@raubjo/architect-core";
166
+ import { Renderer as ReactRenderer, useService } from "@raubjo/architect-core/react";
167
+ import PricingServiceProvider from "./providers/pricing-service-provider";
168
+
169
+ function QuotePanel() {
170
+ const pricing = useService<{ quote: (subtotal: number) => number }>(
171
+ "services.pricing",
172
+ );
173
+
174
+ return <div>Total: {pricing.quote(100)}</div>;
175
+ }
176
+
177
+ const app = Application.configure("./")
178
+ .withProviders([new PricingServiceProvider()])
179
+ .withServices(({ container }) => {
180
+ // Optional inline registrations if you do not want a dedicated provider.
181
+ container.bind("featureFlags.checkout").toConstantValue(true);
182
+ })
183
+ .withRoot(QuotePanel)
184
+ .withRenderer(new ReactRenderer());
185
+
186
+ app.run();
187
+ ```
188
+
189
+ ## Config
190
+
191
+ `Application` uses config passed directly to `configure`:
192
+
193
+ ```ts
194
+ const app = Application.configure({
195
+ config: {
196
+ pricing: { taxRate: 0.0825, currency: "USD" },
197
+ },
198
+ }).run();
199
+ ```
200
+
201
+ ## Lifecycle order
202
+
203
+ `run()` executes in this order:
204
+
205
+ 1. Provider `register()`
206
+ 2. `.withServices(...)` callbacks
207
+ 3. Provider `boot()`
208
+ 4. `.withStartup(...)` callbacks
209
+ 5. Renderer mount (if configured)
210
+
211
+ Cleanup runs in reverse order when `stop()` is called.
212
+
213
+ ## Notes
214
+
215
+ - `Application.make(...)` is available after `run()` and resolves from the active container.
216
+ - `Application.configure(basePath)` is still supported for compatibility, but config values should be passed via `configure({ config })`.
package/bun.lock CHANGED
@@ -3,18 +3,99 @@
3
3
  "configVersion": 1,
4
4
  "workspaces": {
5
5
  "": {
6
- "name": "@ioc/application",
6
+ "name": "@raubjo/architect-core",
7
7
  "devDependencies": {
8
8
  "@types/react": "^19.2.14",
9
9
  "@types/react-dom": "^19.2.3",
10
+ "bun-types": "^1.3.9",
11
+ "prettier": "^3.8.1",
12
+ "solid-js": "^1.9.0",
13
+ "vue": "^3.5.13",
10
14
  },
15
+ "peerDependencies": {
16
+ "inversify": "^7.11.0",
17
+ "react": "^19.0.0",
18
+ "react-dom": "^19.0.0",
19
+ "reflect-metadata": "~0.2.2",
20
+ "solid-js": "",
21
+ "svelte": "",
22
+ "vue": "^3.0.0",
23
+ },
24
+ "optionalPeers": [
25
+ "inversify",
26
+ "react",
27
+ "react-dom",
28
+ "solid-js",
29
+ "svelte",
30
+ "vue",
31
+ ],
11
32
  },
12
33
  },
13
34
  "packages": {
35
+ "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
36
+
37
+ "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
38
+
39
+ "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
40
+
41
+ "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
42
+
43
+ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
44
+
45
+ "@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="],
46
+
14
47
  "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
15
48
 
16
49
  "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
17
50
 
51
+ "@vue/compiler-core": ["@vue/compiler-core@3.5.28", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.28", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ=="],
52
+
53
+ "@vue/compiler-dom": ["@vue/compiler-dom@3.5.28", "", { "dependencies": { "@vue/compiler-core": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA=="],
54
+
55
+ "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.28", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.28", "@vue/compiler-dom": "3.5.28", "@vue/compiler-ssr": "3.5.28", "@vue/shared": "3.5.28", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g=="],
56
+
57
+ "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.28", "", { "dependencies": { "@vue/compiler-dom": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g=="],
58
+
59
+ "@vue/reactivity": ["@vue/reactivity@3.5.28", "", { "dependencies": { "@vue/shared": "3.5.28" } }, "sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw=="],
60
+
61
+ "@vue/runtime-core": ["@vue/runtime-core@3.5.28", "", { "dependencies": { "@vue/reactivity": "3.5.28", "@vue/shared": "3.5.28" } }, "sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ=="],
62
+
63
+ "@vue/runtime-dom": ["@vue/runtime-dom@3.5.28", "", { "dependencies": { "@vue/reactivity": "3.5.28", "@vue/runtime-core": "3.5.28", "@vue/shared": "3.5.28", "csstype": "^3.2.3" } }, "sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA=="],
64
+
65
+ "@vue/server-renderer": ["@vue/server-renderer@3.5.28", "", { "dependencies": { "@vue/compiler-ssr": "3.5.28", "@vue/shared": "3.5.28" }, "peerDependencies": { "vue": "3.5.28" } }, "sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg=="],
66
+
67
+ "@vue/shared": ["@vue/shared@3.5.28", "", {}, "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ=="],
68
+
69
+ "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
70
+
18
71
  "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
72
+
73
+ "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
74
+
75
+ "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
76
+
77
+ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
78
+
79
+ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
80
+
81
+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
82
+
83
+ "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
84
+
85
+ "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="],
86
+
87
+ "reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="],
88
+
89
+ "seroval": ["seroval@1.5.0", "", {}, "sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw=="],
90
+
91
+ "seroval-plugins": ["seroval-plugins@1.5.0", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA=="],
92
+
93
+ "solid-js": ["solid-js@1.9.11", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.0", "seroval-plugins": "~1.5.0" } }, "sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q=="],
94
+
95
+ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
96
+
97
+ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
98
+
99
+ "vue": ["vue@3.5.28", "", { "dependencies": { "@vue/compiler-dom": "3.5.28", "@vue/compiler-sfc": "3.5.28", "@vue/runtime-dom": "3.5.28", "@vue/server-renderer": "3.5.28", "@vue/shared": "3.5.28" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg=="],
19
100
  }
20
101
  }
package/package.json CHANGED
@@ -1,25 +1,35 @@
1
1
  {
2
2
  "name": "@raubjo/architect-core",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
+ "license": "MIT",
5
6
  "type": "module",
6
7
  "publishConfig": {
7
8
  "access": "public"
8
9
  },
9
10
  "scripts": {
10
11
  "test": "bun test",
11
- "test:coverage": "bun test --coverage"
12
+ "test:coverage": "bun test --coverage",
13
+ "fix": "prettier -w ./src/**/*"
12
14
  },
13
15
  "exports": {
14
16
  ".": "./src/index.ts",
17
+ "./react": "./src/react.ts",
18
+ "./solid": "./src/solid.ts",
19
+ "./svelte": "./src/svelte.ts",
20
+ "./vue": "./src/vue.ts",
15
21
  "./application": "./src/foundation/application.ts",
16
- "./config/app": "./src/config/app.ts",
22
+ "./container/contract": "./src/container/contract.ts",
23
+ "./container/adapters/inversify": "./src/container/adapters/inversify.ts",
24
+ "./container/adapters/builtin": "./src/container/adapters/builtin.ts",
25
+ "./config/adapters/esm": "./src/config/adapters/esm.ts",
17
26
  "./config/env": "./src/config/env.ts",
18
27
  "./config/repository": "./src/config/repository.ts",
19
28
  "./cache/manager": "./src/cache/manager.ts",
20
29
  "./cache/cache": "./src/cache/cache.ts",
21
30
  "./facades/cache": "./src/support/facades/cache.ts",
22
31
  "./storage/manager": "./src/storage/manager.ts",
32
+ "./storage/adapters/contract": "./src/storage/adapters/contract.ts",
23
33
  "./storage/storage": "./src/storage/storage.ts",
24
34
  "./storage/adapters/memory": "./src/storage/adapters/memory.ts",
25
35
  "./storage/adapters/local-storage": "./src/storage/adapters/local-storage.ts",
@@ -27,17 +37,56 @@
27
37
  "./facades/storage": "./src/support/facades/storage.ts",
28
38
  "./filesystem/filesystem": "./src/filesystem/filesystem.ts",
29
39
  "./filesystem/local-adapter": "./src/filesystem/adapters/local.ts",
30
- "./rendering/renderer": "./src/rendering/renderer.ts",
31
- "./rendering/react": "./src/rendering/adapters/react.tsx",
40
+ "./renderers/contract": "./src/renderers/contract.ts",
41
+ "./renderers/react": "./src/renderers/adapters/react.tsx",
42
+ "./renderers/solid": "./src/renderers/adapters/solid.tsx",
43
+ "./renderers/svelte": "./src/renderers/adapters/svelte.ts",
44
+ "./renderers/vue": "./src/renderers/adapters/vue.ts",
32
45
  "./facades/config": "./src/support/facades/config.ts",
33
46
  "./providers/config-service-provider": "./src/support/providers/config-service-provider.ts",
34
47
  "./runtimes/react": "./src/runtimes/react.tsx",
48
+ "./runtimes/solid": "./src/runtimes/solid.tsx",
49
+ "./runtimes/svelte": "./src/runtimes/svelte.ts",
50
+ "./runtimes/vue": "./src/runtimes/vue.ts",
35
51
  "./str": "./src/support/str.ts",
36
52
  "./service-provider": "./src/support/service-provider.ts",
37
53
  "./facade": "./src/support/facades/facade.ts"
38
54
  },
55
+ "peerDependencies": {
56
+ "inversify": "^7.11.0",
57
+ "reflect-metadata": "~0.2.2",
58
+ "react": "^19.0.0",
59
+ "react-dom": "^19.0.0",
60
+ "svelte": "",
61
+ "solid-js": "",
62
+ "vue": "^3.0.0"
63
+ },
64
+ "peerDependenciesMeta": {
65
+ "react": {
66
+ "optional": true
67
+ },
68
+ "react-dom": {
69
+ "optional": true
70
+ },
71
+ "solid-js": {
72
+ "optional": true
73
+ },
74
+ "svelte": {
75
+ "optional": true
76
+ },
77
+ "vue": {
78
+ "optional": true
79
+ },
80
+ "inversify": {
81
+ "optional": true
82
+ }
83
+ },
39
84
  "devDependencies": {
40
85
  "@types/react": "^19.2.14",
41
- "@types/react-dom": "^19.2.3"
86
+ "@types/react-dom": "^19.2.3",
87
+ "bun-types": "^1.3.9",
88
+ "prettier": "^3.8.1",
89
+ "solid-js": "^1.9.0",
90
+ "vue": "^3.5.13"
42
91
  }
43
92
  }
@@ -1,3 +1,3 @@
1
- import type { StorageAdapter } from "../storage/storage";
1
+ import type { Adapter } from "@/storage/adapters/contract";
2
2
 
3
- export type CacheStore = StorageAdapter;
3
+ export type CacheStore = Adapter;
@@ -1,115 +1,125 @@
1
- import type ConfigRepository from "../config/repository";
2
- import IndexedDbAdapter from "../storage/adapters/indexed-db";
3
- import LocalStorageAdapter from "../storage/adapters/local-storage";
4
- import MemoryStorageAdapter from "../storage/adapters/memory";
5
- import type { StorageAdapter } from "../storage/storage";
6
- import type { CacheStore } from "./cache";
1
+ import type ConfigRepository from "@/config/repository";
2
+ import IndexedDbAdapter from "@/storage/adapters/indexed-db";
3
+ import LocalStorageAdapter from "@/storage/adapters/local-storage";
4
+ import MemoryStorageAdapter from "@/storage/adapters/memory";
5
+ import type { Adapter } from "@/storage/adapters/contract";
6
+ import type { CacheStore } from "@/cache/cache";
7
7
 
8
8
  type CacheStoreConfig = {
9
- driver?: string;
9
+ driver?: string;
10
10
  };
11
11
 
12
12
  function isRecord(value: unknown): value is Record<string, unknown> {
13
- return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14
14
  }
15
15
 
16
- export default class CacheManager implements StorageAdapter {
17
- protected stores: Record<string, CacheStore>;
18
- protected active: string;
16
+ export default class CacheManager implements Adapter {
17
+ protected stores: Record<string, CacheStore>;
18
+ protected active: string;
19
19
 
20
- constructor(stores: Record<string, CacheStore>, active = "memory") {
21
- this.stores = stores;
22
- this.active = active in this.stores ? active : firstStoreName(this.stores);
23
- }
24
-
25
- static fromConfig(config: ConfigRepository): CacheManager {
26
- const stores = CacheManager.storesFromConfig(config);
27
- const active = config.string("cache.default", "memory");
20
+ constructor(stores: Record<string, CacheStore>, active = "memory") {
21
+ this.stores = stores;
22
+ this.active =
23
+ active in this.stores ? active : firstStoreName(this.stores);
24
+ }
28
25
 
29
- return new CacheManager(stores, active);
30
- }
26
+ static fromConfig(config: ConfigRepository): CacheManager {
27
+ const stores = CacheManager.storesFromConfig(config);
28
+ const active = config.string("cache.default", "memory");
31
29
 
32
- protected static storesFromConfig(config: ConfigRepository): Record<string, CacheStore> {
33
- const baseDrivers = CacheManager.defaultDrivers();
34
- const configured = config.get<Record<string, unknown>>("cache.stores", {}) ?? {};
35
- if (!isRecord(configured) || Object.keys(configured).length === 0) {
36
- return baseDrivers;
30
+ return new CacheManager(stores, active);
37
31
  }
38
32
 
39
- const stores: Record<string, CacheStore> = {};
40
- for (const [name, storeConfig] of Object.entries(configured)) {
41
- const driver = resolveDriver(storeConfig, name);
42
- stores[name] = baseDrivers[driver] ?? baseDrivers.memory;
33
+ protected static storesFromConfig(
34
+ config: ConfigRepository,
35
+ ): Record<string, CacheStore> {
36
+ const baseDrivers = CacheManager.defaultDrivers();
37
+ const configured =
38
+ config.get<Record<string, unknown>>("cache.stores", {}) ?? {};
39
+ if (!isRecord(configured) || Object.keys(configured).length === 0) {
40
+ return baseDrivers;
41
+ }
42
+
43
+ const stores: Record<string, CacheStore> = {};
44
+ for (const [name, storeConfig] of Object.entries(configured)) {
45
+ const driver = resolveDriver(storeConfig, name);
46
+ stores[name] = baseDrivers[driver] ?? baseDrivers.memory;
47
+ }
48
+
49
+ return stores;
43
50
  }
44
51
 
45
- return stores;
46
- }
47
-
48
- protected static defaultDrivers(): Record<string, CacheStore> {
49
- const memory = new MemoryStorageAdapter();
50
- const hasWindow = typeof window !== "undefined";
51
- const hasLocal = hasWindow && typeof window.localStorage !== "undefined";
52
- const hasIndexed = typeof globalThis.indexedDB !== "undefined";
53
-
54
- return {
55
- memory,
56
- local: hasLocal ? new LocalStorageAdapter(window.localStorage) : memory,
57
- indexed: hasIndexed ? new IndexedDbAdapter() : memory,
58
- };
59
- }
60
-
61
- store(name?: string): CacheStore {
62
- const target = typeof name === "string" ? name : this.active;
63
- if (!(target in this.stores)) {
64
- throw new Error(`Cache store [${target}] is not defined.`);
52
+ protected static defaultDrivers(): Record<string, CacheStore> {
53
+ const memory = new MemoryStorageAdapter();
54
+ const hasWindow = typeof window !== "undefined";
55
+ const hasLocal =
56
+ hasWindow && typeof window.localStorage !== "undefined";
57
+ const hasIndexed = typeof globalThis.indexedDB !== "undefined";
58
+
59
+ return {
60
+ memory,
61
+ local:
62
+ hasLocal ?
63
+ new LocalStorageAdapter(window.localStorage)
64
+ : memory,
65
+ indexed: hasIndexed ? new IndexedDbAdapter() : memory,
66
+ };
65
67
  }
66
68
 
67
- return this.stores[target];
68
- }
69
+ store(name?: string): CacheStore {
70
+ const target = typeof name === "string" ? name : this.active;
71
+ if (!(target in this.stores)) {
72
+ throw new Error(`Cache store [${target}] is not defined.`);
73
+ }
69
74
 
70
- use(name: string): this {
71
- this.active = this.store(name) ? name : this.active;
72
- return this;
73
- }
75
+ return this.stores[target];
76
+ }
74
77
 
75
- get<T = unknown>(key: string): Promise<T | null> {
76
- return this.store().get<T>(key);
77
- }
78
+ use(name: string): this {
79
+ this.active = this.store(name) ? name : this.active;
80
+ return this;
81
+ }
78
82
 
79
- set<T = unknown>(key: string, value: T): Promise<void> {
80
- return this.store().set<T>(key, value);
81
- }
83
+ get<T = unknown>(key: string): Promise<T | null> {
84
+ return this.store().get<T>(key);
85
+ }
82
86
 
83
- has(key: string): Promise<boolean> {
84
- return this.store().has(key);
85
- }
87
+ set<T = unknown>(key: string, value: T): Promise<void> {
88
+ return this.store().set<T>(key, value);
89
+ }
86
90
 
87
- delete(key: string): Promise<void> {
88
- return this.store().delete(key);
89
- }
91
+ has(key: string): Promise<boolean> {
92
+ return this.store().has(key);
93
+ }
90
94
 
91
- clear(): Promise<void> {
92
- return this.store().clear();
93
- }
95
+ delete(key: string): Promise<void> {
96
+ return this.store().delete(key);
97
+ }
98
+
99
+ clear(): Promise<void> {
100
+ return this.store().clear();
101
+ }
94
102
 
95
- keys(): Promise<string[]> {
96
- return this.store().keys();
97
- }
103
+ keys(): Promise<string[]> {
104
+ return this.store().keys();
105
+ }
98
106
  }
99
107
 
100
108
  function firstStoreName(stores: Record<string, CacheStore>): string {
101
- if ("memory" in stores) {
102
- return "memory";
103
- }
109
+ if ("memory" in stores) {
110
+ return "memory";
111
+ }
104
112
 
105
- return Object.keys(stores)[0];
113
+ return Object.keys(stores)[0];
106
114
  }
107
115
 
108
116
  function resolveDriver(value: unknown, fallback: string): string {
109
- if (!isRecord(value)) {
110
- return fallback;
111
- }
117
+ if (!isRecord(value)) {
118
+ return fallback;
119
+ }
112
120
 
113
- const storeConfig = value as CacheStoreConfig;
114
- return typeof storeConfig.driver === "string" ? storeConfig.driver : fallback;
121
+ const storeConfig = value as CacheStoreConfig;
122
+ return typeof storeConfig.driver === "string" ?
123
+ storeConfig.driver
124
+ : fallback;
115
125
  }
@@ -0,0 +1,26 @@
1
+ import type { ConfigItems } from "@/config/repository";
2
+
3
+ type ConfigModule = { default?: unknown };
4
+
5
+ function fileNameWithoutExtension(path: string): string {
6
+ const file = path.split("/").pop() ?? path;
7
+ return file.replace(/\.[^/.]+$/, "");
8
+ }
9
+
10
+ export function loadConfig(modules: Record<string, unknown>): ConfigItems {
11
+ const discovered: ConfigItems = {};
12
+
13
+ for (const [path, loaded] of Object.entries(modules)) {
14
+ const key = fileNameWithoutExtension(path);
15
+ if (key === "index") {
16
+ continue;
17
+ }
18
+
19
+ const module = loaded as ConfigModule;
20
+ if (module && "default" in module && module.default !== undefined) {
21
+ discovered[key] = module.default;
22
+ }
23
+ }
24
+
25
+ return discovered;
26
+ }