@azure-net/kit 6.0.0-next.1 → 6.0.0-next.10
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 +335 -2
- package/dist/delivery/index.d.ts +3 -1
- package/dist/delivery/index.js +3 -1
- package/dist/delivery/injectable-dependencies/AsyncHelpers.d.ts +2 -4
- package/dist/delivery/injectable-dependencies/AsyncHelpers.js +96 -102
- package/dist/delivery/presenter/Bound.d.ts +1 -0
- package/dist/delivery/presenter/Bound.js +9 -0
- package/dist/delivery/presenter/RegisterProvider.d.ts +16 -0
- package/dist/delivery/presenter/RegisterProvider.js +99 -0
- package/dist/delivery/schema/rules/Rules.js +2 -1
- package/dist/delivery.d.ts +0 -1
- package/dist/delivery.js +0 -1
- package/dist/i18n/Internal.d.ts +15 -0
- package/dist/i18n/Internal.js +11 -0
- package/dist/i18n/TranslationManager.d.ts +19 -0
- package/dist/i18n/TranslationManager.js +26 -0
- package/dist/i18n/Translations.svelte.d.ts +20 -0
- package/dist/i18n/Translations.svelte.js +254 -0
- package/dist/i18n/Types.d.ts +30 -0
- package/dist/i18n/Types.js +1 -0
- package/dist/i18n/index.d.ts +3 -0
- package/dist/i18n/index.js +2 -0
- package/dist/infra/datasource/BaseDatasource.d.ts +19 -6
- package/dist/infra/datasource/BaseDatasource.js +4 -4
- package/dist/infra/http-service/HttpServiceInstance.d.ts +26 -16
- package/dist/infra/http-service/HttpServiceInstance.js +21 -13
- package/dist/infra/index.d.ts +2 -2
- package/dist/infra/index.js +2 -2
- package/dist/infra/query/QueryBuilder.d.ts +6 -1
- package/dist/infra/query/QueryBuilder.js +14 -12
- package/dist/plugin/AzureNetPlugin.d.ts +1 -4
- package/dist/plugin/AzureNetPlugin.js +21 -64
- package/dist/plugin/index.d.ts +1 -1
- package/dist/server/Context/Runtime.d.ts +3 -0
- package/dist/server/Context/Runtime.js +109 -0
- package/dist/shared/app/App.d.ts +47 -77
- package/dist/shared/app/App.js +80 -106
- package/dist/shared/app/index.d.ts +5 -3
- package/dist/shared/app/middleware/ClientMiddleware.d.ts +14 -8
- package/dist/shared/app/middleware/ClientMiddleware.js +32 -11
- package/dist/shared/app/middleware/ServerMiddleware.d.ts +10 -5
- package/dist/shared/app/middleware/ServerMiddleware.js +3 -3
- package/dist/shared/app/middleware/Types.d.ts +4 -0
- package/dist/shared/app/middleware/Types.js +1 -0
- package/dist/shared/app-error/ErrorRuntime.d.ts +1 -1
- package/dist/shared/app-error/ErrorRuntime.js +4 -3
- package/dist/shared/boundary-provider/CompositionRoot.d.ts +21 -0
- package/dist/shared/boundary-provider/CompositionRoot.js +11 -0
- package/dist/shared/boundary-provider/Provider.d.ts +3 -2
- package/dist/shared/boundary-provider/Provider.js +56 -40
- package/dist/shared/boundary-provider/index.d.ts +1 -0
- package/dist/shared/boundary-provider/index.js +1 -0
- package/dist/shared/cookie/UniversalCookie.js +1 -1
- package/dist/shared/index.d.ts +2 -3
- package/dist/shared/index.js +2 -3
- package/dist/shared/logger/index.js +1 -1
- package/dist/shared/request-context/Internal.d.ts +16 -0
- package/dist/shared/request-context/Internal.js +24 -0
- package/dist/shared/request-context/index.d.ts +1 -0
- package/dist/shared/request-context/index.js +2 -0
- package/dist/svelte/async-signal/AsyncSignal.svelte.d.ts +20 -4
- package/dist/svelte/async-signal/AsyncSignal.svelte.js +53 -13
- package/dist/svelte/async-signal/README.md +89 -0
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.js +1 -0
- package/dist/svelte/query/Query.svelte.d.ts +1 -1
- package/dist/svelte/query/Query.svelte.js +14 -6
- package/dist/svelte/query/SearchParamsSync.js +11 -3
- package/dist/{shared → svelte}/redirect/index.d.ts +2 -8
- package/package.json +35 -26
- package/dist/external/edges/Dev.d.ts +0 -1
- package/dist/external/edges/Dev.js +0 -1
- package/dist/external/edges/Edges.d.ts +0 -1
- package/dist/external/edges/Edges.js +0 -1
- package/dist/external/edges/ServerContext.d.ts +0 -1
- package/dist/external/edges/ServerContext.js +0 -1
- package/dist/external/edges/_edges.d.ts +0 -1
- package/dist/external/edges/_edges.js +0 -1
- package/dist/external/i18n/index.d.ts +0 -1
- package/dist/external/i18n/index.js +0 -1
- package/dist/shared/event-bus/EventBus.d.ts +0 -12
- package/dist/shared/event-bus/EventBus.js +0 -3
- package/dist/shared/event-bus/index.d.ts +0 -1
- package/dist/shared/event-bus/index.js +0 -1
- /package/dist/{shared → svelte}/redirect/index.js +0 -0
package/README.md
CHANGED
|
@@ -2,6 +2,130 @@
|
|
|
2
2
|
|
|
3
3
|
#### [docs](https://svelte.kit.azure-net.ru)
|
|
4
4
|
|
|
5
|
+
## Version 6 integration
|
|
6
|
+
|
|
7
|
+
Configure `AzureNetPlugin()` from `@azure-net/kit/plugin` alongside SvelteKit in Vite,
|
|
8
|
+
then export `{ register }` from `createApp(...)` in `src/program.ts`.
|
|
9
|
+
Call `register()` synchronously during root layout initialization. `useClient` may
|
|
10
|
+
be asynchronous: the navigation subscription is installed synchronously, and
|
|
11
|
+
middleware supplied after an `await` applies only from that point onward. Handle
|
|
12
|
+
the returned Promise if client initialization can fail. Client middleware itself
|
|
13
|
+
must remain synchronous.
|
|
14
|
+
|
|
15
|
+
The plugin provides virtual hooks only. It does not create or rewrite page/layout
|
|
16
|
+
modules. Manual `hooks.server`, `hooks.client` and `hooks` files are forbidden.
|
|
17
|
+
Initial request state is hydrated once through Svelte hydratable; later navigation is client-owned.
|
|
18
|
+
|
|
19
|
+
`program.ts` is universal: a static server-only import does not become safe merely
|
|
20
|
+
because it is used inside `useServer`. Use an explicit build-time browser guard
|
|
21
|
+
with dynamic server imports when needed, and retain SvelteKit's build checks.
|
|
22
|
+
|
|
23
|
+
### Application structure
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
src/app/
|
|
27
|
+
Contexts/{Orders,Identity,SharedKernel}/
|
|
28
|
+
Domain/{Entities,Ports,Enums}
|
|
29
|
+
Infrastructure/Repositories
|
|
30
|
+
Application/UseCases
|
|
31
|
+
Presentation/{Admin,Public,Shared}/
|
|
32
|
+
Ui/
|
|
33
|
+
Scopes/
|
|
34
|
+
Shell/
|
|
35
|
+
Platform/
|
|
36
|
+
Foundation/{Datasource,Response,Schema,DI}
|
|
37
|
+
Bootstrap/{Routes,Localization}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Without workspaces, `Ui`, `Scopes` and `Shell` sit directly under `Presentation`.
|
|
41
|
+
Central DI lives in `Platform/Foundation/DI/ApplicationProvider.ts`. Contexts may
|
|
42
|
+
import `SharedKernel`, but not each other. Presentation scopes may compose any
|
|
43
|
+
contexts. Workspaces may import `Shared`, but not each other.
|
|
44
|
+
|
|
45
|
+
Boundary providers cache their proxy, registration and services per SSR request,
|
|
46
|
+
not globally. Browser instances are cached until cleanup. Factories and `boot`
|
|
47
|
+
are synchronous. `await cleanupProvider(name)` explicitly disposes instantiated
|
|
48
|
+
services and resets the cache; request completion does not invoke it automatically.
|
|
49
|
+
|
|
50
|
+
Install the CLI separately with `npm i -D @azure-net/cli`. Its commit-tools command
|
|
51
|
+
configures Prettier without installing commit packages. `azure-net commit` commits
|
|
52
|
+
staged changes; `azure-net push` checks, stages, commits and pushes. `--no-check`
|
|
53
|
+
skips checks. Review `.gitignore` before staging.
|
|
54
|
+
|
|
55
|
+
## Class Presenters And Async Helpers
|
|
56
|
+
|
|
57
|
+
Import `createAsyncResource` and `createAsyncAction` directly from
|
|
58
|
+
`@azure-net/kit/delivery`. `createAsyncHelpers()` has been removed. The error hook
|
|
59
|
+
configured by `createApp().useAzureNetKitError` is still resolved per execution;
|
|
60
|
+
there is no per-presenter parser or shared server error state. A custom error
|
|
61
|
+
payload can be typed as the third generic: `createAsyncAction<Response, Request, CustomError>()`.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { RegisterProvider, createAsyncResource } from '@azure-net/kit/delivery';
|
|
65
|
+
import { ApplicationProvider } from '#platform/Foundation/DI';
|
|
66
|
+
import type { OrdersUseCases } from '#contexts/Orders/Application/UseCases/OrdersUseCases';
|
|
67
|
+
|
|
68
|
+
export class OrdersPresenter {
|
|
69
|
+
constructor(private readonly useCases: OrdersUseCases) {}
|
|
70
|
+
|
|
71
|
+
collection() {
|
|
72
|
+
return createAsyncResource(() => this.useCases.collection());
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const useOrdersPresenter = RegisterProvider(
|
|
77
|
+
() => {
|
|
78
|
+
const { Orders } = ApplicationProvider();
|
|
79
|
+
return new OrdersPresenter(Orders);
|
|
80
|
+
},
|
|
81
|
+
{ lifecycle: 'scoped', bindMethods: true }
|
|
82
|
+
);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Call `useOrdersPresenter()` during component initialization, not at module scope.
|
|
86
|
+
The registration itself is safe at module scope and does not resolve dependencies.
|
|
87
|
+
`RegisterProvider` returns the actual class instance, without a proxy. The optional
|
|
88
|
+
second argument is an options object: `{ lifecycle?: ..., bindMethods?: ... }`.
|
|
89
|
+
Defaults are `lifecycle: 'scoped'` and `bindMethods: false`. The old string argument
|
|
90
|
+
is no longer supported. With binding disabled, methods and property descriptors are
|
|
91
|
+
left untouched.
|
|
92
|
+
|
|
93
|
+
Use `bindMethods: true` to bind function-valued data properties, including inherited
|
|
94
|
+
methods, or `bindMethods: ['collection']` to select typed method names. Binding runs
|
|
95
|
+
only after the factory returns, once for each newly created instance. Accessors are
|
|
96
|
+
never evaluated or replaced, including Svelte rune accessors. Constructors and
|
|
97
|
+
Object.prototype methods are excluded. A selected accessor (even one returning a
|
|
98
|
+
function), missing method, or protected property throws instead of silently failing.
|
|
99
|
+
TypeScript cannot distinguish a function-returning getter from a method, so the
|
|
100
|
+
accessor check also happens at runtime.
|
|
101
|
+
|
|
102
|
+
Method references are stable after binding. Frozen/non-extensible instances cannot
|
|
103
|
+
receive inherited bound methods. Arbitrary proxies and exotic built-in objects are
|
|
104
|
+
not supported binding targets. Function-valued fields are also bound with `true`;
|
|
105
|
+
use an explicit list if the instance holds component constructors or other callable
|
|
106
|
+
dependencies. No global cache retains instances or bound functions. Binding does not
|
|
107
|
+
apply retroactively to callbacks captured during construction or methods replaced later.
|
|
108
|
+
Classes using Svelte runes such as `$state` must be placed in a `.svelte.ts` module
|
|
109
|
+
(or declared inside a Svelte component), not a regular `.ts` file.
|
|
110
|
+
|
|
111
|
+
| Lifecycle | Server | Browser |
|
|
112
|
+
| ------------------ | ----------------------------------------- | ----------------------------- |
|
|
113
|
+
| `scoped` (default) | One instance per registration per request | New instance per call |
|
|
114
|
+
| `transient` | New instance per call | New instance per call |
|
|
115
|
+
| `singleton` | One instance per registration per request | One instance per registration |
|
|
116
|
+
|
|
117
|
+
Server caching requires `AzureNetPlugin` request context and fails
|
|
118
|
+
outside it. There is no process-wide server singleton. Request caches have weak
|
|
119
|
+
keys and do not retain completed requests. Registrations do not require names,
|
|
120
|
+
Svelte context, hydration, or state serialization. Factory callbacks must be
|
|
121
|
+
synchronous; recursive construction is rejected.
|
|
122
|
+
|
|
123
|
+
This API does not own effects, subscriptions, timers, or `dispose()`. Keep
|
|
124
|
+
component-owned effects in component-lived instances; a cached browser singleton
|
|
125
|
+
must not rely on effects owned by the first component that requested it. SSR
|
|
126
|
+
isolation does not serialize state to the browser and cannot protect mutable
|
|
127
|
+
module-level state captured by user factories.
|
|
128
|
+
|
|
5
129
|
**@azure-net/kit** is a universal toolkit and library collection, developed as part of the **Azure-net** ecosystem. It is designed to speed up modern application development, reduce boilerplate code, and maintain a unified project architecture.
|
|
6
130
|
|
|
7
131
|
## ✨ What is it?
|
|
@@ -10,8 +134,8 @@
|
|
|
10
134
|
|
|
11
135
|
## 🚀 What's included?
|
|
12
136
|
|
|
13
|
-
- **
|
|
14
|
-
- **Localization and translation
|
|
137
|
+
- **Reactive state classes** — request-isolated server instances and browser singletons via `RegisterProvider`.
|
|
138
|
+
- **Localization and translation**: built-in rune-based i18n with request-scoped SSR instances. No edges or external translations dependency.
|
|
15
139
|
- **Extensive utilities** — for working with text, objects, dates, and common tasks.
|
|
16
140
|
- **Data validation** — handy rules and validators.
|
|
17
141
|
- **Reusable base abstractions** — `Repositories`, `Services`, `Providers`, and `Datasources` for quick setup and reuse.
|
|
@@ -40,3 +164,212 @@ In this guide, you will find:
|
|
|
40
164
|
## 🚀 Get started
|
|
41
165
|
|
|
42
166
|
Ready to dive in? Check out the [docs](https://svelte.kit.azure-net.ru) and start building your app faster and more reliably with **Azure-net**.
|
|
167
|
+
|
|
168
|
+
## Initial state and `useBoot`
|
|
169
|
+
|
|
170
|
+
`AzureNetPlugin()` owns a kit-local AsyncLocalStorage scope. It does not depend on
|
|
171
|
+
edges, serialize stores, inject response scripts or create virtual route modules.
|
|
172
|
+
There are no edges store/presenter exports or devtools exports in this version.
|
|
173
|
+
`RequestContext.current()` exposes only `event` and `state`, is server-only, and
|
|
174
|
+
throws outside an active request. Internal DI/error/lifecycle caches are private.
|
|
175
|
+
|
|
176
|
+
Declare transferable state in `src/app.d.ts` (prefer optional fields for CSR):
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import type { IUser } from '#contexts/Identity/Domain';
|
|
180
|
+
declare global {
|
|
181
|
+
namespace App {
|
|
182
|
+
interface RequestContextState {
|
|
183
|
+
currentUser?: IUser | null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
export {};
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { createApp } from '@azure-net/kit';
|
|
192
|
+
import { RequestContext } from '@azure-net/kit/server-context';
|
|
193
|
+
|
|
194
|
+
export const { register } = createApp((app) =>
|
|
195
|
+
app
|
|
196
|
+
.useServer(async ({ event, resolve, useMiddlewares }) => {
|
|
197
|
+
await useMiddlewares([ServerAuth]);
|
|
198
|
+
return resolve(event);
|
|
199
|
+
})
|
|
200
|
+
.useBoot(({ state }) => {
|
|
201
|
+
useCurrentUser().setUser(state.currentUser ?? null);
|
|
202
|
+
})
|
|
203
|
+
);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
In the root layout call `register()` before resolving presenters or state classes.
|
|
207
|
+
`useBoot` is synchronous and runs once per server request render and once per
|
|
208
|
+
browser app initialization, before client lifecycle callbacks. Await all server
|
|
209
|
+
state preparation before `resolve`. Do not mutate the transfer snapshot during
|
|
210
|
+
rendering. Client navigations and repeated register calls do not rehydrate state.
|
|
211
|
+
`useClientInit` runs earlier and must not depend on this snapshot. CSR without an
|
|
212
|
+
SSR snapshot receives `{}`; initialize defaults and fetch missing data explicitly.
|
|
213
|
+
Only publish safe DTOs, never tokens or the whole request/locals. Personalized HTML
|
|
214
|
+
must not be publicly cached. Classes, functions and services are not serialized.
|
|
215
|
+
|
|
216
|
+
Svelte currently requires async mode for `hydratable`:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
sveltekit({ compilerOptions: { experimental: { async: true } } });
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Keep the application's CSP correctly configured; Svelte manages the transfer
|
|
223
|
+
script and serialization. No custom HTML parser is used by the kit.
|
|
224
|
+
|
|
225
|
+
## DI naming
|
|
226
|
+
|
|
227
|
+
`createBoundaryProvider` is replaced by `CreateServiceContainer` (no compatibility alias):
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
const ApplicationProvider = CreateServiceContainer('ApplicationProvider', {
|
|
231
|
+
register: () => ({ Users: () => new UsersUseCases(repository) })
|
|
232
|
+
});
|
|
233
|
+
const { Users } = ApplicationProvider();
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The accessor remains lazy, synchronous and request-scoped on SSR. `RegisterProvider`
|
|
237
|
+
registers an individual class factory; `singleton` means one browser instance but
|
|
238
|
+
one instance per request on the server, never a server process singleton.
|
|
239
|
+
|
|
240
|
+
Built-in i18n: see [I18N.md](./I18N.md). Register a `new TranslationManager(options)` through `app.useTranslations(manager)`. Server preparation, locale hydration and client dictionary loading run automatically before the corresponding user hooks. No separate translation package or manual lifecycle calls are required.
|
|
241
|
+
|
|
242
|
+
## AsyncSignal SSR Hydration
|
|
243
|
+
|
|
244
|
+
Client-only signals remain synchronous. Opt into SSR with a stable, non-empty key:
|
|
245
|
+
|
|
246
|
+
```svelte
|
|
247
|
+
<script lang="ts">
|
|
248
|
+
import { createAsyncSignal } from '@azure-net/kit';
|
|
249
|
+
|
|
250
|
+
const result = await createAsyncSignal(() => loadProfile(), {
|
|
251
|
+
server: true,
|
|
252
|
+
key: 'profile:current-user'
|
|
253
|
+
});
|
|
254
|
+
</script>
|
|
255
|
+
|
|
256
|
+
<p>{result.response?.name}</p>
|
|
257
|
+
<button onclick={() => result.refresh()}>Refresh</button>
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Call this during component initialization with Svelte experimental async enabled.
|
|
261
|
+
Svelte's `hydratable` transfers the initial serializable result to the browser,
|
|
262
|
+
which reuses it without a duplicate request; later refreshes execute normally.
|
|
263
|
+
There is no module-global server request state. The handler must work in both
|
|
264
|
+
environments. Keys must match across SSR/hydration and distinguish request inputs.
|
|
265
|
+
|
|
266
|
+
`beforeSend` runs only for actual requests; success/error callbacks and `ready`
|
|
267
|
+
settlement apply on both sides. Request errors produce an error-state signal.
|
|
268
|
+
`immediate: false` keeps the signal idle and disables initial hydration. Batch
|
|
269
|
+
factories stay synchronous and explicitly reject `server: true` at type-check and
|
|
270
|
+
runtime; use individually awaited server signals instead.
|
|
271
|
+
|
|
272
|
+
See [AsyncSignal details](./src/lib/svelte/async-signal/README.md) for lifecycle,
|
|
273
|
+
serialization, errors, cancellation, CSP, and batch behavior.
|
|
274
|
+
|
|
275
|
+
## SvelteKit 3 hooks and class middleware
|
|
276
|
+
|
|
277
|
+
Error callbacks use the native discriminated `CaughtError` contract from
|
|
278
|
+
`@sveltejs/kit/hooks`. `status` and `message` are no longer added at the top level.
|
|
279
|
+
For `kind: 'app'`, `'framework'` or `'validation'`, read `error.status` and
|
|
280
|
+
`error.message`. For `kind: 'unknown'`, `error` remains `unknown`: narrow it before
|
|
281
|
+
logging and do not return its potentially private message to clients. Validation
|
|
282
|
+
issues are available only in the `'validation'` branch. Return `{ status, message }`
|
|
283
|
+
to override the public response, or return nothing to retain SvelteKit defaults.
|
|
284
|
+
The removed `handleValidationError` hook is not generated.
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
app.useServerError(({ kind, error, useLogger }) => {
|
|
288
|
+
if (kind === 'unknown') {
|
|
289
|
+
useLogger(error, {});
|
|
290
|
+
return { status: 500, message: 'Internal Error' };
|
|
291
|
+
}
|
|
292
|
+
return error;
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Middleware arrays now contain **constructors**, not functions or existing instances.
|
|
297
|
+
Constructors must accept no required arguments. A fresh instance is constructed
|
|
298
|
+
only when its turn is reached, on every request/navigation. Resolve dependencies
|
|
299
|
+
inside that instance, not into module-level request-specific variables.
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
import type { IServerMiddleware, ServerMiddlewareContext, IClientMiddleware, ClientMiddlewareContext } from '@azure-net/kit';
|
|
303
|
+
|
|
304
|
+
class ServerAuth implements IServerMiddleware {
|
|
305
|
+
async execute({ event, next }: ServerMiddlewareContext) {
|
|
306
|
+
if (!event.cookies.get('session')) return next('/login', 303);
|
|
307
|
+
next();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
class ClientGuard implements IClientMiddleware {
|
|
312
|
+
execute({ next, to, navigation }: ClientMiddlewareContext): undefined {
|
|
313
|
+
if (navigation?.shallow) return next();
|
|
314
|
+
if (to.pathname === '/private') return next('/login', { replace: true });
|
|
315
|
+
return next();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
app
|
|
320
|
+
.useServer(async ({ useMiddlewares, event, resolve }) => {
|
|
321
|
+
await useMiddlewares([ServerAuth]);
|
|
322
|
+
return resolve(event);
|
|
323
|
+
})
|
|
324
|
+
.useClient(({ useMiddlewares }) => {
|
|
325
|
+
useMiddlewares([ClientGuard]);
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`IMiddleware<Context, Result>` is the common interface. Client `execute` is strictly
|
|
330
|
+
synchronous (`undefined` return, normally `return next()`); server `execute` can
|
|
331
|
+
return `void` or `Promise<void>`. Omitting `next()` blocks the chain. Neither the
|
|
332
|
+
chain nor the container automatically disposes subscriptions owned by middleware:
|
|
333
|
+
keep middleware short-lived and do not attach long-lived listeners there.
|
|
334
|
+
|
|
335
|
+
Client `next(location, options)` and `useRedirect(...).clientRedirectSettings`
|
|
336
|
+
use SvelteKit's `GotoOptions` directly: `replace`, `reset`, `refreshAll`, `shallow`,
|
|
337
|
+
`persistState`, `state` and `invalidate`. Client navigation data includes the native
|
|
338
|
+
`navigation` event. In Kit 3 shallow changes also trigger navigation hooks; choose
|
|
339
|
+
explicitly whether a guard should skip them. `goto` is for app routes, not external
|
|
340
|
+
URLs; external navigation should use browser location APIs.
|
|
341
|
+
|
|
342
|
+
## CompositionRoot
|
|
343
|
+
|
|
344
|
+
`CreateServiceContainer` remains unchanged. `CompositionRoot` is a lazy class
|
|
345
|
+
facade over it, with a unique symbol as its internal container identity. It can be created at module
|
|
346
|
+
scope; `resolve()` resolves against the current SSR request or browser
|
|
347
|
+
cache, never a request captured by the constructor.
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
import { CompositionRoot } from '@azure-net/kit';
|
|
351
|
+
|
|
352
|
+
export const Http = new CompositionRoot({
|
|
353
|
+
register: () => ({ Api: () => new ApiDatasource() })
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
export const Identity = new CompositionRoot({
|
|
357
|
+
dependsOn: { Http },
|
|
358
|
+
register: ({ Http }) => ({
|
|
359
|
+
Account: () => new AccountUseCases(new AccountRepository(Http.Api))
|
|
360
|
+
})
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// Inside a presenter factory or an active request:
|
|
364
|
+
const { Account } = Identity.resolve();
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Dependencies accept both other `CompositionRoot` instances and existing
|
|
368
|
+
`CreateServiceContainer` accessors. Service types are inferred through the chain.
|
|
369
|
+
Only registration metadata is wrapped; there is no extra proxy around services
|
|
370
|
+
and no new caching layer. Repeated resolution preserves existing container/service
|
|
371
|
+
identity. If explicit cleanup is needed, use `await cleanupProvider(Identity.name)`.
|
|
372
|
+
Declare roots once, rather than creating new roots per component render.
|
|
373
|
+
`name` is now a symbol, not a serializable string. Its description is for diagnostics
|
|
374
|
+
only; equal descriptions never share a cache. Existing string keys passed directly
|
|
375
|
+
to `CreateServiceContainer` and `cleanupProvider` remain supported.
|
package/dist/delivery/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { createAsyncAction, createAsyncResource, type AsyncActionSettings, type AsyncHelperRetry, type AsyncResourceSettings, type AsyncActionResponse } from './injectable-dependencies/index.js';
|
|
2
|
+
export { RegisterProvider, type ProviderLifecycle, type RegisterProviderOptions, type ProviderMethodKey } from './presenter/RegisterProvider.js';
|
|
3
|
+
export { Bound } from './presenter/Bound.js';
|
|
2
4
|
export { createSchemaFactory, schema, type Schema, type RequestErrors, type ISchemaError, type ValidationResult, type ValidationRuleResult, type ValidationParams, type ValidationMessage, type ValidationErrorsMap } from './schema/index.js';
|
|
3
5
|
export { createRules, validationMessagesRu, validationMessagesI18n, validationMessagesEn, type ValidationRuleParams, type BaseValidationMessages } from './schema/rules/index.js';
|
package/dist/delivery/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { createAsyncAction, createAsyncResource } from './injectable-dependencies/index.js';
|
|
2
|
+
export { RegisterProvider } from './presenter/RegisterProvider.js';
|
|
3
|
+
export { Bound } from './presenter/Bound.js';
|
|
2
4
|
export { createSchemaFactory, schema } from './schema/index.js';
|
|
3
5
|
export { createRules, validationMessagesRu, validationMessagesI18n, validationMessagesEn } from './schema/rules/index.js';
|
|
@@ -18,7 +18,5 @@ export interface AsyncActionSettings<Res = never, Req = never, ErrorResult = App
|
|
|
18
18
|
export interface AsyncResourceSettings<Res = never, Req = never, ErrorResult = AppError<Error>> extends AsyncActionSettings<Res, Req, ErrorResult> {
|
|
19
19
|
reject?: boolean;
|
|
20
20
|
}
|
|
21
|
-
export declare const
|
|
22
|
-
|
|
23
|
-
createAsyncResource: <Res = never, Req_1 = never>(action: Action<Res>, args?: AsyncResourceSettings<Res, Req_1, ErrorResult>) => Promise<Res>;
|
|
24
|
-
};
|
|
21
|
+
export declare const createAsyncAction: <Res = never, Req = never, ErrorResult extends object = Record<never, never>>(action: Action<Res>, args?: AsyncActionSettings<Res, Req, ErrorResult>) => Promise<AsyncActionResponse<Res, Req, ErrorResult>>;
|
|
22
|
+
export declare const createAsyncResource: <Res = never, Req = never, ErrorResult extends object = Record<never, never>>(action: Action<Res>, args?: AsyncResourceSettings<Res, Req, ErrorResult>) => Promise<Res>;
|
|
@@ -1,115 +1,109 @@
|
|
|
1
1
|
import { AsyncHelperError } from '../../shared/app-error/AppError.js';
|
|
2
2
|
import { resolveAzureNetKitError } from '../../shared/app-error/ErrorRuntime.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
const parsedError = await resolveAzureNetKitError(normalizeError(error), {
|
|
18
|
-
can: !!retry,
|
|
19
|
-
call: retry ? () => retryFunc() : undefined
|
|
20
|
-
});
|
|
21
|
-
return { error: parsedError, retryResult };
|
|
22
|
-
};
|
|
23
|
-
const beforeSendResolver = async (settings) => {
|
|
24
|
-
if (!settings?.beforeSend)
|
|
25
|
-
return undefined;
|
|
26
|
-
let abortReason;
|
|
27
|
-
let aborted = false;
|
|
28
|
-
await new Promise((resolve) => {
|
|
29
|
-
let settled = false;
|
|
30
|
-
const beforeSend = settings.beforeSend;
|
|
31
|
-
const guardTimer = setTimeout(() => {
|
|
32
|
-
settle(true);
|
|
33
|
-
}, 30000);
|
|
34
|
-
const settle = (isAbort, reason) => {
|
|
35
|
-
if (settled)
|
|
36
|
-
return;
|
|
37
|
-
settled = true;
|
|
38
|
-
clearTimeout(guardTimer);
|
|
39
|
-
aborted = isAbort;
|
|
40
|
-
abortReason = reason;
|
|
41
|
-
resolve();
|
|
42
|
-
};
|
|
43
|
-
const next = () => settle(false);
|
|
44
|
-
const abort = (reason) => settle(true, reason);
|
|
45
|
-
try {
|
|
46
|
-
const maybePromise = beforeSend({ next, abort });
|
|
47
|
-
if (maybePromise && typeof maybePromise.then === 'function') {
|
|
48
|
-
maybePromise.then(() => settle(false), (err) => settle(true, normalizeError(err)));
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
settle(false);
|
|
52
|
-
}
|
|
53
|
-
catch (err) {
|
|
54
|
-
settle(true, normalizeError(err));
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
if (!aborted)
|
|
58
|
-
return undefined;
|
|
59
|
-
const { error } = await prepareErrors(abortReason ?? new AsyncHelperError('Aborted in beforeSend'));
|
|
60
|
-
return {
|
|
61
|
-
success: false,
|
|
62
|
-
error,
|
|
63
|
-
response: undefined
|
|
64
|
-
};
|
|
3
|
+
const normalizeError = (err) => (err instanceof Error ? err : new Error(String(err)));
|
|
4
|
+
const prepareErrors = async (error, retry) => {
|
|
5
|
+
let retryResult = undefined;
|
|
6
|
+
const retryFunc = async () => {
|
|
7
|
+
if (retry) {
|
|
8
|
+
return await retry()
|
|
9
|
+
.then((res) => {
|
|
10
|
+
retryResult = res;
|
|
11
|
+
return undefined;
|
|
12
|
+
})
|
|
13
|
+
.catch((err) => normalizeError(err));
|
|
14
|
+
}
|
|
65
15
|
};
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return result;
|
|
92
|
-
}
|
|
93
|
-
catch (err) {
|
|
94
|
-
return handleFailure(err);
|
|
95
|
-
}
|
|
16
|
+
const parsedError = await resolveAzureNetKitError(normalizeError(error), {
|
|
17
|
+
can: !!retry,
|
|
18
|
+
call: retry ? () => retryFunc() : undefined
|
|
19
|
+
});
|
|
20
|
+
return { error: parsedError, retryResult };
|
|
21
|
+
};
|
|
22
|
+
const beforeSendResolver = async (settings) => {
|
|
23
|
+
if (!settings?.beforeSend)
|
|
24
|
+
return undefined;
|
|
25
|
+
let abortReason;
|
|
26
|
+
let aborted = false;
|
|
27
|
+
await new Promise((resolve) => {
|
|
28
|
+
let settled = false;
|
|
29
|
+
const beforeSend = settings.beforeSend;
|
|
30
|
+
const guardTimer = setTimeout(() => {
|
|
31
|
+
settle(true);
|
|
32
|
+
}, 30000);
|
|
33
|
+
const settle = (isAbort, reason) => {
|
|
34
|
+
if (settled)
|
|
35
|
+
return;
|
|
36
|
+
settled = true;
|
|
37
|
+
clearTimeout(guardTimer);
|
|
38
|
+
aborted = isAbort;
|
|
39
|
+
abortReason = reason;
|
|
40
|
+
resolve();
|
|
96
41
|
};
|
|
42
|
+
const next = () => settle(false);
|
|
43
|
+
const abort = (reason) => settle(true, reason);
|
|
97
44
|
try {
|
|
98
|
-
|
|
45
|
+
const maybePromise = beforeSend({ next, abort });
|
|
46
|
+
if (maybePromise && typeof maybePromise.then === 'function') {
|
|
47
|
+
maybePromise.then(() => settle(false), (err) => settle(true, normalizeError(err)));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
settle(false);
|
|
99
51
|
}
|
|
100
52
|
catch (err) {
|
|
101
|
-
|
|
53
|
+
settle(true, normalizeError(err));
|
|
102
54
|
}
|
|
55
|
+
});
|
|
56
|
+
if (!aborted)
|
|
57
|
+
return undefined;
|
|
58
|
+
const { error } = await prepareErrors(abortReason ?? new AsyncHelperError('Aborted in beforeSend'));
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
error,
|
|
62
|
+
response: undefined
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
export const createAsyncAction = async (action, args) => {
|
|
66
|
+
const beforeSendResult = await beforeSendResolver(args);
|
|
67
|
+
if (beforeSendResult)
|
|
68
|
+
return beforeSendResult;
|
|
69
|
+
const executeAction = async () => {
|
|
70
|
+
const response = await Promise.resolve(action());
|
|
71
|
+
return { response, success: true };
|
|
103
72
|
};
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
107
|
-
|
|
73
|
+
const handleFailure = async (err, retry) => {
|
|
74
|
+
const { error, retryResult } = await prepareErrors(normalizeError(err), retry);
|
|
75
|
+
if (retryResult) {
|
|
76
|
+
return completeSuccess(retryResult);
|
|
77
|
+
}
|
|
78
|
+
const result = { error, response: args?.fallbackResponse, success: false };
|
|
79
|
+
try {
|
|
80
|
+
await args?.onError?.(result);
|
|
81
|
+
}
|
|
82
|
+
catch (onErrorException) {
|
|
83
|
+
throw await resolveAzureNetKitError(new AsyncHelperError('onError caught exception', { cause: normalizeError(onErrorException) }));
|
|
108
84
|
}
|
|
109
|
-
return result
|
|
85
|
+
return result;
|
|
110
86
|
};
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
87
|
+
const completeSuccess = async (result) => {
|
|
88
|
+
try {
|
|
89
|
+
await args?.onSuccess?.(result);
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
return handleFailure(err);
|
|
94
|
+
}
|
|
114
95
|
};
|
|
96
|
+
try {
|
|
97
|
+
return completeSuccess(await executeAction());
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
return handleFailure(err, executeAction);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
export const createAsyncResource = async (action, args) => {
|
|
104
|
+
const result = await createAsyncAction(action, args);
|
|
105
|
+
if (!result.success && args?.reject) {
|
|
106
|
+
throw result.error;
|
|
107
|
+
}
|
|
108
|
+
return result.response;
|
|
115
109
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function Bound<This, Args extends unknown[], Return>(method: (this: This, ...args: Args) => Return, context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type ProviderLifecycle = 'scoped' | 'transient' | 'singleton';
|
|
2
|
+
export type ProviderMethodKey<T> = Exclude<{
|
|
3
|
+
[K in keyof T]-?: T[K] extends (...args: never[]) => unknown ? K : never;
|
|
4
|
+
}[keyof T], 'constructor'>;
|
|
5
|
+
export interface RegisterProviderOptions<T> {
|
|
6
|
+
lifecycle?: ProviderLifecycle;
|
|
7
|
+
bindMethods?: boolean | readonly ProviderMethodKey<T>[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Registration is safe at module scope; the factory is called only by the returned accessor.
|
|
11
|
+
* scoped: per request on the server, per call in the browser.
|
|
12
|
+
* singleton: per request on the server, per registration in the browser.
|
|
13
|
+
* transient: a new instance on every call.
|
|
14
|
+
* No hydration, component context, effects or disposal are managed by this function.
|
|
15
|
+
*/
|
|
16
|
+
export declare function RegisterProvider<T extends object>(factory: () => T extends PromiseLike<unknown> ? never : T, options?: RegisterProviderOptions<NoInfer<T>>): () => T;
|