@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.
- package/README.md +216 -0
- package/bun.lock +82 -1
- package/package.json +55 -6
- package/src/cache/cache.ts +2 -2
- package/src/cache/manager.ts +93 -83
- package/src/config/adapters/esm.ts +26 -0
- package/src/config/clone.ts +5 -5
- package/src/config/env.global.d.ts +2 -1
- package/src/config/env.ts +53 -55
- package/src/config/env_test.helpers.ts +58 -0
- package/src/config/repository.ts +180 -142
- package/src/container/adapters/builtin.ts +347 -0
- package/src/container/adapters/inversify.ts +123 -0
- package/src/container/contract.ts +58 -0
- package/src/container/runtime.ts +149 -0
- package/src/filesystem/adapters/local.ts +92 -83
- package/src/filesystem/adapters/local_test.helpers.ts +50 -0
- package/src/filesystem/filesystem.ts +11 -11
- package/src/foundation/application.ts +205 -175
- package/src/foundation/application_test.helpers.ts +31 -0
- package/src/index.ts +15 -6
- package/src/react.ts +2 -0
- package/src/renderers/adapters/react.tsx +35 -0
- package/src/renderers/adapters/solid.tsx +32 -0
- package/src/renderers/adapters/svelte.ts +70 -0
- package/src/renderers/adapters/vue.ts +28 -0
- package/src/renderers/contract.ts +15 -0
- package/src/runtimes/react.tsx +24 -12
- package/src/runtimes/solid.tsx +30 -0
- package/src/runtimes/svelte.ts +23 -0
- package/src/runtimes/vue.ts +20 -0
- package/src/solid.ts +2 -0
- package/src/storage/adapters/contract.ts +10 -0
- package/src/storage/adapters/indexed-db.ts +170 -156
- package/src/storage/adapters/local-storage.ts +34 -34
- package/src/storage/adapters/memory.ts +25 -25
- package/src/storage/manager.ts +65 -61
- package/src/storage/storage.ts +1 -8
- package/src/support/facades/cache.ts +40 -40
- package/src/support/facades/config.ts +78 -48
- package/src/support/facades/facade.ts +43 -28
- package/src/support/facades/storage.ts +41 -41
- package/src/support/service-provider.ts +11 -11
- package/src/support/str.ts +94 -90
- package/src/support/str_test.helpers.ts +26 -0
- package/src/svelte.ts +2 -0
- package/src/vue.ts +2 -0
- package/tsconfig.json +16 -0
- package/coverage/lcov.info +0 -1078
- package/src/config/app.ts +0 -5
- package/src/rendering/adapters/react.tsx +0 -27
- package/src/rendering/renderer.ts +0 -13
- package/src/support/providers/config-service-provider.ts +0 -19
- package/tests/application.test.ts +0 -236
- package/tests/cache-facade.test.ts +0 -45
- package/tests/cache.test.ts +0 -68
- package/tests/config-clone.test.ts +0 -31
- package/tests/config-env.test.ts +0 -88
- package/tests/config-facade.test.ts +0 -96
- package/tests/config-repository.test.ts +0 -124
- package/tests/facade-base.test.ts +0 -80
- package/tests/filesystem.test.ts +0 -81
- package/tests/runtime-react.test.tsx +0 -37
- package/tests/service-provider.test.ts +0 -23
- package/tests/storage-facade.test.ts +0 -46
- package/tests/storage.test.ts +0 -264
- 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": "@
|
|
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.
|
|
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
|
-
"./
|
|
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
|
-
"./
|
|
31
|
-
"./
|
|
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
|
}
|
package/src/cache/cache.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Adapter } from "@/storage/adapters/contract";
|
|
2
2
|
|
|
3
|
-
export type CacheStore =
|
|
3
|
+
export type CacheStore = Adapter;
|
package/src/cache/manager.ts
CHANGED
|
@@ -1,115 +1,125 @@
|
|
|
1
|
-
import type ConfigRepository from "
|
|
2
|
-
import IndexedDbAdapter from "
|
|
3
|
-
import LocalStorageAdapter from "
|
|
4
|
-
import MemoryStorageAdapter from "
|
|
5
|
-
import type {
|
|
6
|
-
import type { CacheStore } from "
|
|
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
|
-
|
|
9
|
+
driver?: string;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
13
|
-
|
|
13
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export default class CacheManager implements
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
export default class CacheManager implements Adapter {
|
|
17
|
+
protected stores: Record<string, CacheStore>;
|
|
18
|
+
protected active: string;
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
30
|
-
|
|
26
|
+
static fromConfig(config: ConfigRepository): CacheManager {
|
|
27
|
+
const stores = CacheManager.storesFromConfig(config);
|
|
28
|
+
const active = config.string("cache.default", "memory");
|
|
31
29
|
|
|
32
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
return this;
|
|
73
|
-
}
|
|
75
|
+
return this.stores[target];
|
|
76
|
+
}
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
use(name: string): this {
|
|
79
|
+
this.active = this.store(name) ? name : this.active;
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
get<T = unknown>(key: string): Promise<T | null> {
|
|
84
|
+
return this.store().get<T>(key);
|
|
85
|
+
}
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
set<T = unknown>(key: string, value: T): Promise<void> {
|
|
88
|
+
return this.store().set<T>(key, value);
|
|
89
|
+
}
|
|
86
90
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
has(key: string): Promise<boolean> {
|
|
92
|
+
return this.store().has(key);
|
|
93
|
+
}
|
|
90
94
|
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
109
|
+
if ("memory" in stores) {
|
|
110
|
+
return "memory";
|
|
111
|
+
}
|
|
104
112
|
|
|
105
|
-
|
|
113
|
+
return Object.keys(stores)[0];
|
|
106
114
|
}
|
|
107
115
|
|
|
108
116
|
function resolveDriver(value: unknown, fallback: string): string {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
if (!isRecord(value)) {
|
|
118
|
+
return fallback;
|
|
119
|
+
}
|
|
112
120
|
|
|
113
|
-
|
|
114
|
-
|
|
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
|
+
}
|