@azure-net/kit

中文文档
6.0.0发布于 2026/11/01MITTypeScriptES Module近 30 天下载 3248

Universal toolkit and library collection, developed as part of the Azure-net ecosystem

原始使用文档
文档目录14 节
  1. Version 6 integration
  2. Application structure
  3. Class Presenters And Async Helpers
  4. ✨ What is it?
  5. 🚀 What's included?
  6. 🎯 Why use it?
  7. 📚 Learn more
  8. 🚀 Get started
  9. Initial state and useBoot
  10. DI naming
  11. AsyncSignal SSR Hydration
  12. SvelteKit 3 hooks and class middleware
  13. CompositionRoot
  14. HTTP Retries

Welcome to @azure-net/kit

docs

Version 6 integration

Configure AzureNetPlugin() from @azure-net/kit/plugin alongside SvelteKit in Vite, then export { register } from createApp(...) in src/program.ts. Call register() synchronously during root layout initialization. useClient may be asynchronous: the navigation subscription is installed synchronously, and middleware supplied after an await applies only from that point onward. Handle the returned Promise if client initialization can fail. Client middleware itself must remain synchronous.

The plugin provides virtual hooks only. It does not create or rewrite page/layout modules. Manual hooks.server, hooks.client and hooks files are forbidden. Initial request state is hydrated once through Svelte hydratable; later navigation is client-owned.

program.ts is universal: a static server-only import does not become safe merely because it is used inside useServer. Use an explicit build-time browser guard with dynamic server imports when needed, and retain SvelteKit's build checks.

Application structure

src/app/
  Contexts/{Orders,Identity,SharedKernel}/
    Domain/{Entities,Ports,Enums}
    Infrastructure/Repositories
    Application/UseCases
  Presentation/{Admin,Public,Shared}/
    Ui/
    Scopes/
    Shell/
  Platform/
    Foundation/{Datasource,Response,Schema,DI}
    Bootstrap/{Routes,Localization}

Without workspaces, Ui, Scopes and Shell sit directly under Presentation. Central DI lives in Platform/Foundation/DI/ApplicationProvider.ts. Contexts may import SharedKernel, but not each other. Presentation scopes may compose any contexts. Workspaces may import Shared, but not each other.

Boundary providers cache their proxy, registration and services per SSR request, not globally. Browser instances are cached until cleanup. Factories and boot are synchronous. await cleanupProvider(name) explicitly disposes instantiated services and resets the cache; request completion does not invoke it automatically.

Install the CLI separately with npm i -D @azure-net/cli. Its commit-tools command configures Prettier without installing commit packages. azure-net commit commits staged changes; azure-net push checks, stages, commits and pushes. --no-check skips checks. Review .gitignore before staging.

Class Presenters And Async Helpers

Import createAsyncResource and createAsyncAction directly from @azure-net/kit/delivery. Helpers execute the action once and support beforeSend, onSuccess, onError and fallbackResponse. Resources additionally support reject. Schema failures are converted locally to error.validation. There is no global error resolver, useAzureNetKitError, or helper-level retry. Exceptions thrown by onSuccess or onError propagate unchanged, without replaying the action.

import { RegisterProvider, createAsyncResource } from '@azure-net/kit/delivery';
import { ApplicationProvider } from '#platform/Foundation/DI';
import type { OrdersUseCases } from '#contexts/Orders/Application/UseCases/OrdersUseCases';

export class OrdersPresenter {
    constructor(private readonly useCases: OrdersUseCases) {}

    collection() {
        return createAsyncResource(() => this.useCases.collection());
    }
}

export const useOrdersPresenter = RegisterProvider(
    () => {
        const { Orders } = ApplicationProvider();
        return new OrdersPresenter(Orders);
    },
    { lifecycle: 'scoped', bindMethods: true };
});

Call useOrdersPresenter() during component initialization, not at module scope. The registration itself is safe at module scope and does not resolve dependencies. RegisterProvider returns the actual class instance, without a proxy. The optional second argument is an options object: { lifecycle?: ..., bindMethods?: ... }. Defaults are lifecycle: 'scoped' and bindMethods: false. The old string argument is no longer supported. With binding disabled, methods and property descriptors are left untouched.

Use bindMethods: true to bind function-valued data properties, including inherited methods, or bindMethods: ['collection'] to select typed method names. Binding runs only after the factory returns, once for each newly created instance. Accessors are never evaluated or replaced, including Svelte rune accessors. Constructors and Object.prototype methods are excluded. A selected accessor (even one returning a function), missing method, or protected property throws instead of silently failing. TypeScript cannot distinguish a function-returning getter from a method, so the accessor check also happens at runtime.

Method references are stable after binding. Frozen/non-extensible instances cannot receive inherited bound methods. Arbitrary proxies and exotic built-in objects are not supported binding targets. Function-valued fields are also bound with true; use an explicit list if the instance holds component constructors or other callable dependencies. No global cache retains instances or bound functions. Binding does not apply retroactively to callbacks captured during construction or methods replaced later. Classes using Svelte runes such as $state must be placed in a .svelte.ts module (or declared inside a Svelte component), not a regular .ts file.

LifecycleServerBrowser
scoped (default)One instance per registration per requestNew instance per call
transientNew instance per callNew instance per call
singletonOne instance per registration per requestOne instance per registration

Server caching requires AzureNetPlugin request context and fails outside it. There is no process-wide server singleton. Request caches have weak keys and do not retain completed requests. Registrations do not require names, Svelte context, hydration, or state serialization. Factory callbacks must be synchronous; recursive construction is rejected.

This API does not own effects, subscriptions, timers, or dispose(). Keep component-owned effects in component-lived instances; a cached browser singleton must not rely on effects owned by the first component that requested it. SSR isolation does not serialize state to the browser and cannot protect mutable module-level state captured by user factories.

@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.

✨ What is it?

@azure-net/kit is much more than just a collection of utilities or components. It is a complete architecture (Azure-net Architecture) based on DDD (Domain-Driven Design) principles, helping you build scalable and maintainable projects with a clear structure.

🚀 What's included?

  • Reactive state classes — request-isolated server instances and browser singletons via RegisterProvider.
  • Localization and translation: built-in rune-based i18n with request-scoped SSR instances. No edges or external translations dependency.
  • Extensive utilities — for working with text, objects, dates, and common tasks.
  • Data validation — handy rules and validators.
  • Reusable base abstractions — Repositories, Services, Providers, and Datasources for quick setup and reuse.
  • Unified architecture approach — the package provides a solid project structure and helps enforce consistent development standards.

🎯 Why use it?

By using @azure-net/kit, you get a ready-to-use project framework that:

  • reduces boilerplate code,
  • accelerates development,
  • simplifies testing,
  • supports a clear and predictable architecture.

This is especially useful for teamwork on medium to large-scale applications.

📚 Learn more

In this guide, you will find:

  • Step-by-step instructions for setup and configuration.
  • Detailed explanations of architectural patterns.
  • Examples of using the built-in store and translation.
  • Recommendations for organizing code with Repositories, Services, and Datasources.

🚀 Get started

Ready to dive in? Check out the docs and start building your app faster and more reliably with Azure-net.

Initial state and useBoot

AzureNetPlugin() owns a kit-local AsyncLocalStorage scope. It does not depend on edges, serialize stores, inject response scripts or create virtual route modules. There are no edges store/presenter exports or devtools exports in this version. RequestContext.current() exposes only event and state, is server-only, and throws outside an active request. Internal DI/error/lifecycle caches are private.

Declare transferable state in src/app.d.ts (prefer optional fields for CSR):

import type { IUser } from '#contexts/Identity/Domain';
declare global {
    namespace App {
        interface RequestContextState {
            currentUser?: IUser | null;
        }
    }
}
export {};
import { createApp, ServerMiddlewareManager } from '@azure-net/kit';
import { RequestContext } from '@azure-net/kit/server-context';

export const { register } = createApp((app) => {
 app.useServer(async ({ event, resolve }) => {
            await new ServerMiddlewareManager([ServerAuth]).execute();
            return resolve(event);
        });
 app.useBoot(({ state }) => {
            useCurrentUser().setUser(state.currentUser ?? null);
        })
);

The configuration callback is synchronous. Each app.useX(...) returns void; register hooks with separate statements, without chaining or return app. Duplicate registrations and registrations after the callback completes throw. Hook execution still follows the framework lifecycle, not registration order. Hooks that support async work keep their existing async contracts.

In the root layout call register() before resolving presenters or state classes. useBoot is synchronous and runs once per server request render and once per browser app initialization, before client lifecycle callbacks. Await all server state preparation before resolve. Do not mutate the transfer snapshot during rendering. Client navigations and repeated register calls do not rehydrate state. useClientInit runs earlier and must not depend on this snapshot. CSR without an SSR snapshot receives {}; initialize defaults and fetch missing data explicitly. Only publish safe DTOs, never tokens or the whole request/locals. Personalized HTML must not be publicly cached. Classes, functions and services are not serialized.

Svelte currently requires async mode for hydratable:

sveltekit({ compilerOptions: { experimental: { async: true } } });

Keep the application's CSP correctly configured; Svelte manages the transfer script and serialization. No custom HTML parser is used by the kit.

DI naming

createBoundaryProvider is replaced by CreateServiceContainer (no compatibility alias):

const ApplicationProvider = CreateServiceContainer('ApplicationProvider', {
    register: () => ({ Users: () => new UsersUseCases(repository) })
});
const { Users } = ApplicationProvider();

The accessor remains lazy, synchronous and request-scoped on SSR. RegisterProvider registers an individual class factory; singleton means one browser instance but one instance per request on the server, never a server process singleton.

Built-in i18n: see 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.

AsyncSignal SSR Hydration

Client-only signals remain synchronous. Opt into SSR with a stable, non-empty key:

<script lang="ts">
    import { createAsyncSignal } from '@azure-net/kit';

    const result = await createAsyncSignal(() => loadProfile(), {
        server: true,
        key: 'profile:current-user'
    });
</script>

<p>{result.response?.name}</p>
<button onclick={() => result.refresh()}>Refresh</button>

Call this during component initialization with Svelte experimental async enabled. Svelte's hydratable transfers the initial serializable result to the browser, which reuses it without a duplicate request; later refreshes execute normally. There is no module-global server request state. The handler must work in both environments. Keys must match across SSR/hydration and distinguish request inputs.

beforeSend runs only for actual requests; success/error callbacks and ready settlement apply on both sides. Request errors produce an error-state signal. immediate: false keeps the signal idle and disables initial hydration. Batch factories stay synchronous and explicitly reject server: true at type-check and runtime; use individually awaited server signals instead.

See AsyncSignal details for lifecycle, serialization, errors, cancellation, CSP, and batch behavior.

SvelteKit 3 hooks and class middleware

Error callbacks use the native discriminated CaughtError contract from @sveltejs/kit/hooks. status and message are no longer added at the top level. For kind: 'app', 'framework' or 'validation', read error.status and error.message. For kind: 'unknown', error remains unknown: narrow it before logging and do not return its potentially private message to clients. Validation issues are available only in the 'validation' branch. Return { status, message } to override the public response, or return nothing to retain SvelteKit defaults. The removed handleValidationError hook is not generated.

import { RemoteLogger } from '@azure-net/kit';

app.useServerError(({ kind, error }) => {
    if (kind === 'unknown') {
        RemoteLogger.log(error, {});
        return { status: 500, message: 'Internal Error' };
    }
    return error;
});

Middleware arrays now contain constructors, not functions or existing instances. Constructors must accept no required arguments. A fresh instance is constructed only when its turn is reached, on every request/navigation. Resolve dependencies inside that instance, not into module-level request-specific variables.

import {
    ServerMiddlewareManager,
    ClientMiddlewareManager,
    type IServerMiddleware,
    type ServerMiddlewareContext,
    type IClientMiddleware,
    type ClientMiddlewareContext
} from '@azure-net/kit';
import { beforeNavigate } from '$app/navigation';

class ServerAuth implements IServerMiddleware {
    async execute({ event, next }: ServerMiddlewareContext) {
        if (!event.cookies.get('session')) return next('/login', 303);
        next();
    }
}

class ClientGuard implements IClientMiddleware {
    execute({ next, to, navigation }: ClientMiddlewareContext): undefined {
        if (navigation?.shallow) return next();
        if (to.pathname === '/private') return next('/login', { replace: true });
        return next();
    }
}

const serverMiddlewares = new ServerMiddlewareManager([ServerAuth]);
const clientMiddlewares = new ClientMiddlewareManager([ClientGuard]);

app
    .useServer(async ({ event, resolve }) => {
        await serverMiddlewares.execute();
        return resolve(event);
    })
    .useClient(() => {
        beforeNavigate((navigation) => clientMiddlewares.execute(navigation));
    });

// Run the same checks without navigating or registering another subscription.
clientMiddlewares.execute();

Managers snapshot the constructor list and retain no request data or middleware instances, so they can be declared at module scope. ServerMiddlewareManager.execute() resolves the current request each time. ClientMiddlewareManager.execute() uses the current page URL; from and navigation are undefined in a manual run. Client execution is a no-op during SSR.

createApp no longer injects useMiddlewares or subscribes to navigation itself. Register beforeNavigate synchronously inside useClient, before any await. A manual run does not create a navigation to cancel: omitting next() only stops that run. Avoid calling a manager from middleware that it executes, including indirectly through HTTP recovery. Re-running a full chain also re-runs analytics; use a smaller manager if only auth state must be synchronized.

IMiddleware<Context, Result> is the common interface. Client execute is strictly synchronous (undefined return, normally return next()); server execute can return void or Promise<void>. Omitting next() blocks the chain. Neither the chain nor the container automatically disposes subscriptions owned by middleware: keep middleware short-lived and do not attach long-lived listeners there.

Client next(location, options) and useRedirect(...).clientRedirectSettings use SvelteKit's GotoOptions directly: replace, reset, refreshAll, shallow, persistState, state and invalidate. Client navigation data includes the native navigation event. In Kit 3 shallow changes also trigger navigation hooks; choose explicitly whether a guard should skip them. goto is for app routes, not external URLs; external navigation should use browser location APIs.

CompositionRoot

CreateServiceContainer remains unchanged. CompositionRoot is a lazy class facade over it, with a unique symbol as its internal container identity. It can be created at module scope; resolve() resolves against the current SSR request or browser cache, never a request captured by the constructor.

import { CompositionRoot } from '@azure-net/kit';

export const Http = new CompositionRoot({
    register: () => ({ Api: () => new ApiDatasource() })
});

export const Identity = new CompositionRoot({
    dependsOn: { Http },
    register: ({ Http }) => ({
        Account: () => new AccountUseCases(new AccountRepository(Http.Api))
    })
});

// Inside a presenter factory or an active request:
const { Account } = Identity.resolve();

Dependencies accept both other CompositionRoot instances and existing CreateServiceContainer accessors. Service types are inferred through the chain. Only registration metadata is wrapped; there is no extra proxy around services and no new caching layer. Repeated resolution preserves existing container/service identity. If explicit cleanup is needed, use await cleanupProvider(Identity.name). Declare roots once, rather than creating new roots per component render. name is now a symbol, not a serializable string. Its description is for diagnostics only; equal descriptions never share a cache. Existing string keys passed directly to CreateServiceContainer and cleanupProvider remain supported.

HTTP Retries

Retries belong to HttpServiceInstance, including calls made directly from use cases. With no retry option, a failed request is not repeated. limit counts additional attempts: limit: 1 means at most two HTTP attempts.

// A separate session client: no authentication recovery hooks.
const sessionHttp = new HttpServiceInstance({ prefixUrl: apiUrl });

const http = new HttpServiceInstance({
    prefixUrl: apiUrl,
    onRequest(request) {
        const token = readAccessToken();
        if (token) request.headers.set('Authorization', `Bearer ${token}`);
    },
    retry: {
        limit: 1,
        statuses: [401],
        async shouldRetry({ error }) {
            if (!isExpiredAccessToken(error.data)) return false;
            const refreshed = await sessionHttp.post('/api/auth/refresh', {
                json: { refreshToken: readRefreshToken() }
            });
            saveTokens(refreshed.data);
            return true;
        }
    }
});

Token helpers above are application-defined. Read/write tokens within the current request on SSR, never in shared module-level user state. Coordinate concurrent refreshes per session/request as appropriate; the generic HTTP client does not implement token rotation or cross-tab coordination.

  • { limit: N } retries any failed HTTP attempt, including network failures.
  • statuses restricts retries to those error statuses; [] allows none.
  • shouldRetry runs only after the status filter and only while budget remains. Both synchronous and asynchronous booleans are accepted.
  • Per-request retry replaces the entire default policy. Use { limit: 0 } to disable it. A separate session client with no retry policy does not inherit the application client's retries or hooks.
  • onRequest runs before every attempt so refreshed credentials are read again. onResponse runs once on success; onError runs once on terminal HTTP failure, not for intermediate failures that recover successfully.
  • Exceptions from hooks (including shouldRetry) propagate without further recovery. They are not treated as another failed transport attempt.
  • Cancelled requests and streaming request bodies are never replayed. Cancellation also stops waiting for recovery; the callback must manage cancellation of its own underlying work.
  • limit must be a finite non-negative safe integer. There is no implicit retry delay/backoff. Timeout is applied separately to each HTTP attempt.

Retries are opt-in for all HTTP methods. Do not blindly retry writes after network errors: the server may already have applied them. Use a narrow status/code policy or backend-supported idempotency keys. Numerical retry: 3 is no longer supported; write retry: { limit: 3 } instead.

HTTP hooks use these signatures:

retry: {
 limit: 1,
 shouldRetry: async ({ error }) => {
  // Delegate refresh to your session service with a separate HTTP client.
  return false;
 }
},
onError: ({ error, request }) => {
 // Terminal failure notification / validation mapping. No http is exposed.
}

This is a breaking change from positional onError(error, request, http). Request-level hook overrides use the same signatures. Nested requests use instance settings plus their own options, not the outer request's body or signal.

AbortSignal stops waiting for hooks as well as transport operations. Arbitrary user promises cannot be forcibly terminated: hooks must cooperate to stop their own side effects. Hooks have no implicit timeout; supply and abort a signal to bound their waiting time. The per-attempt timeout covers transport and body parsing, including custom doFetch. With responseFormat: 'body', it ends when the stream is returned; reading/cancelling that stream is then the caller's job. Timers/listeners are removed on success, failure, timeout and cancellation.

JSON serialization errors now reject before sending rather than silently sending an empty body. The default JSON serializer does not support cyclic values or BigInt; provide stringifyJson if a custom wire format is required.

Removing http from onError does not prevent recursion through a client captured in a closure. Do not initiate the same failing operation from its error handler. Neither shouldRetry nor onError exposes the HTTP client. Use a separate session client without recovery hooks for refresh/logout. Limits apply per HTTP call, not to arbitrary nested user code.

版本与发布记录

@azure-net/kit 共发布 144 个版本,首次发布于 2025/06/22;过去 12 个月发布了 63 个版本。

版本发布日期距今
6.0.0latest2026/11/016 天前
6.0.0-next.172026/11/016 天前
6.0.0-next.162026/11/016 天前
6.0.0-next.152026/11/017 天前
6.0.0-next.142026/10/318 天前
6.0.0-next.132026/10/308 天前

关于 @azure-net/kit 的常见问题

@azure-net/kit 是什么?

Universal toolkit and library collection, developed as part of the Azure-net ecosystem

如何安装 @azure-net/kit?

在项目目录运行 npm install @azure-net/kit,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:svelte(>=5.57.1)、@sveltejs/kit(>=3.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@azure-net/kit 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@azure-net/kit 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@azure-net/kit 可以商用吗?

@azure-net/kit 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@azure-net/kit 的下载量有多少?

根据 npm 官方下载统计,@azure-net/kit 在 2026/10/08 – 2026/11/06 这 30 天内被下载 3,248 次,日均约 108 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@azure-net/kit 还在维护吗?

最新版本 6.0.0 发布于 2026/11/01(6 天前);至今共发布 144 个版本,其中过去 12 个月发布了 63 个;首次发布于 2025/06/22。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。