@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
package/README.md ADDED
@@ -0,0 +1,162 @@
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
+ - Automatic config loading from your project `config` files
16
+
17
+ If you are familiar with Laravel service providers and container bindings, this should feel familiar.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ bun add @raubjo/architect-core inversify reflect-metadata
23
+ ```
24
+
25
+ For framework-specific runtime helpers, import from subpaths like `@raubjo/architect-core/react`.
26
+
27
+ ## Quick start
28
+
29
+ ```ts
30
+ import "reflect-metadata";
31
+ import { Application } from "@raubjo/architect-core";
32
+
33
+ const { container, stop } = Application.configure("./")
34
+ .withProviders([])
35
+ .run();
36
+
37
+ // Resolve anywhere after run()
38
+ const config = container.get("config");
39
+
40
+ // Call on teardown if needed
41
+ stop();
42
+ ```
43
+
44
+ ## Service provider example
45
+
46
+ Use providers to define business rules and service construction once.
47
+
48
+ ```ts
49
+ import { injectable } from "inversify";
50
+ import {
51
+ ServiceProvider,
52
+ type ServiceProviderContext,
53
+ } from "@raubjo/architect-core";
54
+
55
+ @injectable()
56
+ class PricingService {
57
+ constructor(private readonly taxRate: number) {}
58
+
59
+ quote(subtotal: number) {
60
+ return subtotal + subtotal * this.taxRate;
61
+ }
62
+ }
63
+
64
+ class PricingServiceProvider extends ServiceProvider {
65
+ register({ container }: ServiceProviderContext) {
66
+ container.bind("services.pricing").toDynamicValue((ctx) => {
67
+ const config = ctx.container.get("config") as {
68
+ float: (key: string, defaultValue?: number) => number;
69
+ };
70
+
71
+ const taxRate = config.float("pricing.taxRate", 0.07);
72
+ return new PricingService(taxRate);
73
+ });
74
+ }
75
+
76
+ boot({ container }: ServiceProviderContext) {
77
+ // Optional startup work after all providers register.
78
+ // Example: warm caches, subscribe to events, etc.
79
+ void container;
80
+ }
81
+ }
82
+ ```
83
+
84
+ ## Application configuration example
85
+
86
+ ```ts
87
+ import "reflect-metadata";
88
+ import { Application } from "@raubjo/architect-core";
89
+ import { ReactRenderer, useService } from "@raubjo/architect-core/react";
90
+ import PricingServiceProvider from "./providers/pricing-service-provider";
91
+
92
+ function QuotePanel() {
93
+ const pricing = useService<{ quote: (subtotal: number) => number }>(
94
+ "services.pricing",
95
+ );
96
+
97
+ return <div>Total: {pricing.quote(100)}</div>;
98
+ }
99
+
100
+ const app = Application.configure("./")
101
+ .withProviders([new PricingServiceProvider()])
102
+ .withServices(({ container }) => {
103
+ // Optional inline registrations if you do not want a dedicated provider.
104
+ container.bind("featureFlags.checkout").toConstantValue(true);
105
+ })
106
+ .withRoot(QuotePanel)
107
+ .withRenderer(new ReactRenderer());
108
+
109
+ app.run();
110
+ ```
111
+
112
+ ## Config auto-loading from `basePath/config`
113
+
114
+ `Application.configure(basePath)` controls where config files are discovered.
115
+
116
+ When `run()` is called, config is assembled from:
117
+
118
+ 1. Built-in default `app` config.
119
+ 2. Any default exports found in:
120
+ - `${basePath}/config/*.{js,mjs,cjs,ts,mts,cts}`
121
+ - `${basePath}/src/config/*.{js,mjs,cjs,ts,mts,cts}`
122
+
123
+ Each file becomes a top-level config key based on the filename:
124
+
125
+ - `config/cache.ts` -> `config.get("cache")`
126
+ - `src/config/pricing.ts` -> `config.get("pricing")`
127
+
128
+ Example:
129
+
130
+ ```ts
131
+ // ./config/pricing.ts
132
+ export default {
133
+ taxRate: 0.0825,
134
+ currency: "USD",
135
+ };
136
+ ```
137
+
138
+ ```ts
139
+ const app = Application.configure("./").run();
140
+ const config = app.container.get("config") as {
141
+ get: <T = unknown>(key: string, defaultValue?: T) => T;
142
+ };
143
+
144
+ config.get("pricing.taxRate"); // 0.0825
145
+ ```
146
+
147
+ ## Lifecycle order
148
+
149
+ `run()` executes in this order:
150
+
151
+ 1. Provider `register()`
152
+ 2. `.withServices(...)` callbacks
153
+ 3. Provider `boot()`
154
+ 4. `.withStartup(...)` callbacks
155
+ 5. Renderer mount (if configured)
156
+
157
+ Cleanup runs in reverse order when `stop()` is called.
158
+
159
+ ## Notes
160
+
161
+ - `Application.make(...)` is available after `run()` and resolves from the active container.
162
+ - Config source files are cached by `basePath`; use `Application.clearConfigCache()` in tests when needed.
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@raubjo/architect-core",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -8,18 +8,27 @@
8
8
  },
9
9
  "scripts": {
10
10
  "test": "bun test",
11
- "test:coverage": "bun test --coverage"
11
+ "test:coverage": "bun test --coverage",
12
+ "fix": "prettier -w ./src/**/*"
12
13
  },
13
14
  "exports": {
14
15
  ".": "./src/index.ts",
16
+ "./react": "./src/react.ts",
17
+ "./solid": "./src/solid.ts",
18
+ "./svelte": "./src/svelte.ts",
19
+ "./vue": "./src/vue.ts",
15
20
  "./application": "./src/foundation/application.ts",
16
- "./config/app": "./src/config/app.ts",
21
+ "./container/contract": "./src/container/contract.ts",
22
+ "./container/adapters/inversify": "./src/container/adapters/inversify.ts",
23
+ "./container/adapters/builtin": "./src/container/adapters/builtin.ts",
24
+ "./config/adapters/esm": "./src/config/adapters/esm.ts",
17
25
  "./config/env": "./src/config/env.ts",
18
26
  "./config/repository": "./src/config/repository.ts",
19
27
  "./cache/manager": "./src/cache/manager.ts",
20
28
  "./cache/cache": "./src/cache/cache.ts",
21
29
  "./facades/cache": "./src/support/facades/cache.ts",
22
30
  "./storage/manager": "./src/storage/manager.ts",
31
+ "./storage/adapters/contract": "./src/storage/adapters/contract.ts",
23
32
  "./storage/storage": "./src/storage/storage.ts",
24
33
  "./storage/adapters/memory": "./src/storage/adapters/memory.ts",
25
34
  "./storage/adapters/local-storage": "./src/storage/adapters/local-storage.ts",
@@ -27,17 +36,56 @@
27
36
  "./facades/storage": "./src/support/facades/storage.ts",
28
37
  "./filesystem/filesystem": "./src/filesystem/filesystem.ts",
29
38
  "./filesystem/local-adapter": "./src/filesystem/adapters/local.ts",
30
- "./rendering/renderer": "./src/rendering/renderer.ts",
31
- "./rendering/react": "./src/rendering/adapters/react.tsx",
39
+ "./renderers/contract": "./src/renderers/contract.ts",
40
+ "./renderers/react": "./src/renderers/adapters/react.tsx",
41
+ "./renderers/solid": "./src/renderers/adapters/solid.tsx",
42
+ "./renderers/svelte": "./src/renderers/adapters/svelte.ts",
43
+ "./renderers/vue": "./src/renderers/adapters/vue.ts",
32
44
  "./facades/config": "./src/support/facades/config.ts",
33
45
  "./providers/config-service-provider": "./src/support/providers/config-service-provider.ts",
34
46
  "./runtimes/react": "./src/runtimes/react.tsx",
47
+ "./runtimes/solid": "./src/runtimes/solid.tsx",
48
+ "./runtimes/svelte": "./src/runtimes/svelte.ts",
49
+ "./runtimes/vue": "./src/runtimes/vue.ts",
35
50
  "./str": "./src/support/str.ts",
36
51
  "./service-provider": "./src/support/service-provider.ts",
37
52
  "./facade": "./src/support/facades/facade.ts"
38
53
  },
54
+ "peerDependencies": {
55
+ "inversify": "^7.11.0",
56
+ "reflect-metadata": "~0.2.2",
57
+ "react": "^19.0.0",
58
+ "react-dom": "^19.0.0",
59
+ "svelte": "",
60
+ "solid-js": "",
61
+ "vue": "^3.0.0"
62
+ },
63
+ "peerDependenciesMeta": {
64
+ "react": {
65
+ "optional": true
66
+ },
67
+ "react-dom": {
68
+ "optional": true
69
+ },
70
+ "solid-js": {
71
+ "optional": true
72
+ },
73
+ "svelte": {
74
+ "optional": true
75
+ },
76
+ "vue": {
77
+ "optional": true
78
+ },
79
+ "inversify": {
80
+ "optional": true
81
+ }
82
+ },
39
83
  "devDependencies": {
40
84
  "@types/react": "^19.2.14",
41
- "@types/react-dom": "^19.2.3"
85
+ "@types/react-dom": "^19.2.3",
86
+ "bun-types": "^1.3.9",
87
+ "prettier": "^3.8.1",
88
+ "solid-js": "^1.9.0",
89
+ "vue": "^3.5.13"
42
90
  }
43
91
  }
@@ -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
+ }
@@ -1,9 +1,9 @@
1
- import type { ConfigItems } from "./repository";
1
+ import type { ConfigItems } from "@/config/repository";
2
2
 
3
3
  export function cloneConfigItems(items: ConfigItems): ConfigItems {
4
- if (typeof structuredClone === "function") {
5
- return structuredClone(items) as ConfigItems;
6
- }
4
+ if (typeof structuredClone === "function") {
5
+ return structuredClone(items) as ConfigItems;
6
+ }
7
7
 
8
- return JSON.parse(JSON.stringify(items)) as ConfigItems;
8
+ return JSON.parse(JSON.stringify(items)) as ConfigItems;
9
9
  }
@@ -1,5 +1,6 @@
1
1
  declare global {
2
- function env<T = unknown>(key: string, defaultValue?: T | null): T | unknown | null;
2
+ function env(key: string): import("./env").EnvValue | undefined;
3
+ function env<T>(key: string, defaultValue: T): import("./env").EnvValue | T;
3
4
  }
4
5
 
5
6
  export {};