@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.
Files changed (85) hide show
  1. package/README.md +335 -2
  2. package/dist/delivery/index.d.ts +3 -1
  3. package/dist/delivery/index.js +3 -1
  4. package/dist/delivery/injectable-dependencies/AsyncHelpers.d.ts +2 -4
  5. package/dist/delivery/injectable-dependencies/AsyncHelpers.js +96 -102
  6. package/dist/delivery/presenter/Bound.d.ts +1 -0
  7. package/dist/delivery/presenter/Bound.js +9 -0
  8. package/dist/delivery/presenter/RegisterProvider.d.ts +16 -0
  9. package/dist/delivery/presenter/RegisterProvider.js +99 -0
  10. package/dist/delivery/schema/rules/Rules.js +2 -1
  11. package/dist/delivery.d.ts +0 -1
  12. package/dist/delivery.js +0 -1
  13. package/dist/i18n/Internal.d.ts +15 -0
  14. package/dist/i18n/Internal.js +11 -0
  15. package/dist/i18n/TranslationManager.d.ts +19 -0
  16. package/dist/i18n/TranslationManager.js +26 -0
  17. package/dist/i18n/Translations.svelte.d.ts +20 -0
  18. package/dist/i18n/Translations.svelte.js +254 -0
  19. package/dist/i18n/Types.d.ts +30 -0
  20. package/dist/i18n/Types.js +1 -0
  21. package/dist/i18n/index.d.ts +3 -0
  22. package/dist/i18n/index.js +2 -0
  23. package/dist/infra/datasource/BaseDatasource.d.ts +19 -6
  24. package/dist/infra/datasource/BaseDatasource.js +4 -4
  25. package/dist/infra/http-service/HttpServiceInstance.d.ts +26 -16
  26. package/dist/infra/http-service/HttpServiceInstance.js +21 -13
  27. package/dist/infra/index.d.ts +2 -2
  28. package/dist/infra/index.js +2 -2
  29. package/dist/infra/query/QueryBuilder.d.ts +6 -1
  30. package/dist/infra/query/QueryBuilder.js +14 -12
  31. package/dist/plugin/AzureNetPlugin.d.ts +1 -4
  32. package/dist/plugin/AzureNetPlugin.js +21 -64
  33. package/dist/plugin/index.d.ts +1 -1
  34. package/dist/server/Context/Runtime.d.ts +3 -0
  35. package/dist/server/Context/Runtime.js +109 -0
  36. package/dist/shared/app/App.d.ts +47 -77
  37. package/dist/shared/app/App.js +80 -106
  38. package/dist/shared/app/index.d.ts +5 -3
  39. package/dist/shared/app/middleware/ClientMiddleware.d.ts +14 -8
  40. package/dist/shared/app/middleware/ClientMiddleware.js +32 -11
  41. package/dist/shared/app/middleware/ServerMiddleware.d.ts +10 -5
  42. package/dist/shared/app/middleware/ServerMiddleware.js +3 -3
  43. package/dist/shared/app/middleware/Types.d.ts +4 -0
  44. package/dist/shared/app/middleware/Types.js +1 -0
  45. package/dist/shared/app-error/ErrorRuntime.d.ts +1 -1
  46. package/dist/shared/app-error/ErrorRuntime.js +4 -3
  47. package/dist/shared/boundary-provider/CompositionRoot.d.ts +21 -0
  48. package/dist/shared/boundary-provider/CompositionRoot.js +11 -0
  49. package/dist/shared/boundary-provider/Provider.d.ts +3 -2
  50. package/dist/shared/boundary-provider/Provider.js +56 -40
  51. package/dist/shared/boundary-provider/index.d.ts +1 -0
  52. package/dist/shared/boundary-provider/index.js +1 -0
  53. package/dist/shared/cookie/UniversalCookie.js +1 -1
  54. package/dist/shared/index.d.ts +2 -3
  55. package/dist/shared/index.js +2 -3
  56. package/dist/shared/logger/index.js +1 -1
  57. package/dist/shared/request-context/Internal.d.ts +16 -0
  58. package/dist/shared/request-context/Internal.js +24 -0
  59. package/dist/shared/request-context/index.d.ts +1 -0
  60. package/dist/shared/request-context/index.js +2 -0
  61. package/dist/svelte/async-signal/AsyncSignal.svelte.d.ts +20 -4
  62. package/dist/svelte/async-signal/AsyncSignal.svelte.js +53 -13
  63. package/dist/svelte/async-signal/README.md +89 -0
  64. package/dist/svelte/index.d.ts +1 -0
  65. package/dist/svelte/index.js +1 -0
  66. package/dist/svelte/query/Query.svelte.d.ts +1 -1
  67. package/dist/svelte/query/Query.svelte.js +14 -6
  68. package/dist/svelte/query/SearchParamsSync.js +11 -3
  69. package/dist/{shared → svelte}/redirect/index.d.ts +2 -8
  70. package/package.json +35 -26
  71. package/dist/external/edges/Dev.d.ts +0 -1
  72. package/dist/external/edges/Dev.js +0 -1
  73. package/dist/external/edges/Edges.d.ts +0 -1
  74. package/dist/external/edges/Edges.js +0 -1
  75. package/dist/external/edges/ServerContext.d.ts +0 -1
  76. package/dist/external/edges/ServerContext.js +0 -1
  77. package/dist/external/edges/_edges.d.ts +0 -1
  78. package/dist/external/edges/_edges.js +0 -1
  79. package/dist/external/i18n/index.d.ts +0 -1
  80. package/dist/external/i18n/index.js +0 -1
  81. package/dist/shared/event-bus/EventBus.d.ts +0 -12
  82. package/dist/shared/event-bus/EventBus.js +0 -3
  83. package/dist/shared/event-bus/index.d.ts +0 -1
  84. package/dist/shared/event-bus/index.js +0 -1
  85. /package/dist/{shared → svelte}/redirect/index.js +0 -0
@@ -0,0 +1,99 @@
1
+ import { RequestContext } from '../../shared/request-context/Internal.js';
2
+ import { BROWSER } from '../../external/tools/index.js';
3
+ const bindInstanceMethods = (instance, selection) => {
4
+ const descriptors = new Map();
5
+ const selected = selection === true ? undefined : new Set(selection);
6
+ const seen = new Set();
7
+ let target = instance;
8
+ while (target && target !== Object.prototype && target !== Function.prototype) {
9
+ for (const key of selected ?? Reflect.ownKeys(target)) {
10
+ if (key === 'constructor' || seen.has(key))
11
+ continue;
12
+ const descriptor = Object.getOwnPropertyDescriptor(target, key);
13
+ if (!descriptor)
14
+ continue;
15
+ seen.add(key);
16
+ // Never evaluate accessors, including getters returning functions or Svelte state.
17
+ if ('value' in descriptor && typeof descriptor.value === 'function')
18
+ descriptors.set(key, descriptor);
19
+ else if (selected)
20
+ throw new TypeError(`[RegisterProvider] Cannot bind non-method ${String(key)}.`);
21
+ }
22
+ if (selected && seen.size === selected.size)
23
+ break;
24
+ target = Object.getPrototypeOf(target);
25
+ }
26
+ if (selected) {
27
+ for (const key of selected) {
28
+ if (!descriptors.has(key))
29
+ throw new TypeError(`[RegisterProvider] Method ${String(key)} was not found or cannot be bound.`);
30
+ }
31
+ }
32
+ // Validate every write before changing ordinary instances, avoiding partial binding on errors.
33
+ for (const key of descriptors.keys()) {
34
+ const own = Object.getOwnPropertyDescriptor(instance, key);
35
+ if (own ? !own.configurable && !own.writable : !Object.isExtensible(instance)) {
36
+ throw new TypeError(`[RegisterProvider] Cannot bind protected method ${String(key)}.`);
37
+ }
38
+ }
39
+ for (const [key, descriptor] of descriptors) {
40
+ Object.defineProperty(instance, key, { ...descriptor, value: Function.prototype.bind.call(descriptor.value, instance) });
41
+ }
42
+ };
43
+ /**
44
+ * Registration is safe at module scope; the factory is called only by the returned accessor.
45
+ * scoped: per request on the server, per call in the browser.
46
+ * singleton: per request on the server, per registration in the browser.
47
+ * transient: a new instance on every call.
48
+ * No hydration, component context, effects or disposal are managed by this function.
49
+ */
50
+ export function RegisterProvider(factory, options = {}) {
51
+ if (!options || typeof options !== 'object' || Array.isArray(options))
52
+ throw new TypeError('[RegisterProvider] Options must be an object.');
53
+ const { lifecycle = 'scoped', bindMethods = false } = options;
54
+ if (!['scoped', 'transient', 'singleton'].includes(lifecycle)) {
55
+ throw new TypeError(`[RegisterProvider] Unknown lifecycle: ${lifecycle}`);
56
+ }
57
+ if (typeof bindMethods !== 'boolean' && !Array.isArray(bindMethods))
58
+ throw new TypeError('[RegisterProvider] bindMethods must be a boolean or method list.');
59
+ const selection = Array.isArray(bindMethods) ? [...bindMethods] : bindMethods;
60
+ const requests = new WeakMap();
61
+ let clientInstance;
62
+ let constructing = false;
63
+ const create = () => {
64
+ if (constructing)
65
+ throw new Error('[RegisterProvider] Circular presenter dependency.');
66
+ constructing = true;
67
+ try {
68
+ const instance = factory();
69
+ if (!instance || (typeof instance !== 'object' && typeof instance !== 'function')) {
70
+ throw new TypeError('[RegisterProvider] Factory must return an instance.');
71
+ }
72
+ if ('then' in instance && typeof instance.then === 'function') {
73
+ // Consume a rejected async factory without allowing an unhandled rejection.
74
+ void Promise.resolve(instance).catch(() => undefined);
75
+ throw new TypeError('[RegisterProvider] Factory must be synchronous.');
76
+ }
77
+ if (selection === true || (Array.isArray(selection) && selection.length > 0))
78
+ bindInstanceMethods(instance, selection);
79
+ return instance;
80
+ }
81
+ finally {
82
+ constructing = false;
83
+ }
84
+ };
85
+ return () => {
86
+ if (lifecycle === 'transient')
87
+ return create();
88
+ if (BROWSER)
89
+ return lifecycle === 'singleton' ? (clientInstance ??= create()) : create();
90
+ // Weak keys do not retain finished requests or their presenter graphs.
91
+ const context = RequestContext.current();
92
+ let instance = requests.get(context);
93
+ if (!instance) {
94
+ instance = create();
95
+ requests.set(context, instance);
96
+ }
97
+ return instance;
98
+ };
99
+ }
@@ -176,7 +176,8 @@ export const createRules = (validationMessages) => {
176
176
  return message.base();
177
177
  }
178
178
  if (typeof maxDigitsAfterDot === 'number') {
179
- const digitsAfterDot = normalizedValue.split('.')[1]?.length ?? 0;
179
+ const [coefficient, exponent = '0'] = normalizedValue.split(/e/i);
180
+ const digitsAfterDot = Math.max(0, (coefficient.split('.')[1]?.length ?? 0) - Number(exponent));
180
181
  if (digitsAfterDot > maxDigitsAfterDot) {
181
182
  return message.maxDigitsAfterDot(maxDigitsAfterDot);
182
183
  }
@@ -1,2 +1 @@
1
- export * from './external/edges/Edges.js';
2
1
  export * from './delivery/index.js';
package/dist/delivery.js CHANGED
@@ -1,2 +1 @@
1
- export * from './external/edges/Edges.js';
2
1
  export * from './delivery/index.js';
@@ -0,0 +1,15 @@
1
+ export interface TranslationLifecycle {
2
+ preloadTranslation(): Promise<void>;
3
+ initializeClient(): Promise<void>;
4
+ syncFromState(state: App.RequestContextState): void;
5
+ transformHtml(html: string): string;
6
+ }
7
+ export declare const translationManager: unique symbol;
8
+ export interface TranslationManagerHandle {
9
+ readonly [translationManager]: true;
10
+ }
11
+ export type TranslationManagerFactory = (() => object) & TranslationManagerHandle;
12
+ export type TranslationManagerSource = TranslationManagerFactory | TranslationManagerHandle;
13
+ export declare const bindTranslationLifecycle: (manager: object, lifecycle: TranslationLifecycle) => void;
14
+ export declare const getTranslationLifecycle: (manager: object) => TranslationLifecycle;
15
+ export declare const getTranslationManagerLifecycle: (manager: TranslationManagerSource) => TranslationLifecycle;
@@ -0,0 +1,11 @@
1
+ const lifecycles = new WeakMap();
2
+ export const bindTranslationLifecycle = (manager, lifecycle) => {
3
+ lifecycles.set(manager, lifecycle);
4
+ };
5
+ export const getTranslationLifecycle = (manager) => {
6
+ const lifecycle = lifecycles.get(manager);
7
+ if (!lifecycle)
8
+ throw new Error('[AzureNetKit:i18n] Expected a TranslationManager or a manager returned by createTranslations().');
9
+ return lifecycle;
10
+ };
11
+ export const getTranslationManagerLifecycle = (manager) => getTranslationLifecycle(typeof manager === 'function' ? manager() : manager);
@@ -0,0 +1,19 @@
1
+ import { createTranslations } from './Translations.svelte.js';
2
+ import { type translationManager, type TranslationManagerHandle } from './Internal.js';
3
+ import type { MessageLoader, Path, TranslationVariables } from './Types.js';
4
+ export type TranslationManagerOptions<TMessages extends Record<string, MessageLoader>, TInitLocale extends Extract<keyof TMessages, string>> = Parameters<typeof createTranslations<TMessages, TInitLocale>>[0];
5
+ /** A module-safe facade. Only the provider owns request-local reactive state. */
6
+ export declare class TranslationManager<const TMessages extends Record<string, MessageLoader>, const TInitLocale extends Extract<keyof TMessages, string>> implements TranslationManagerHandle {
7
+ #private;
8
+ readonly [translationManager]: true;
9
+ constructor(options: TranslationManagerOptions<TMessages, TInitLocale>);
10
+ readonly t: (translation?: Path<Awaited<ReturnType<TMessages[TInitLocale]>>> | string | {
11
+ key: Path<Awaited<ReturnType<TMessages[TInitLocale]>>> | string;
12
+ vars?: TranslationVariables;
13
+ }, vars?: TranslationVariables) => string;
14
+ readonly switchLocale: (locale: Extract<keyof TMessages, string>) => Promise<void>;
15
+ readonly applyHtmlLocaleAttr: (html: string) => string;
16
+ readonly subscribeLocaleChangeEvent: (listener: (locale: Extract<keyof TMessages, string>) => void) => (() => void);
17
+ get locale(): Extract<keyof TMessages, string>;
18
+ get locales(): Readonly<Record<Extract<keyof TMessages, string>, Extract<keyof TMessages, string>>>;
19
+ }
@@ -0,0 +1,26 @@
1
+ import { createTranslations } from './Translations.svelte.js';
2
+ import { bindTranslationLifecycle, getTranslationLifecycle } from './Internal.js';
3
+ /** A module-safe facade. Only the provider owns request-local reactive state. */
4
+ export class TranslationManager {
5
+ #factory;
6
+ constructor(options) {
7
+ this.#factory = createTranslations(options);
8
+ // Resolve on every lifecycle invocation, never capture a server request here.
9
+ bindTranslationLifecycle(this, {
10
+ preloadTranslation: () => getTranslationLifecycle(this.#factory()).preloadTranslation(),
11
+ initializeClient: () => getTranslationLifecycle(this.#factory()).initializeClient(),
12
+ syncFromState: (state) => getTranslationLifecycle(this.#factory()).syncFromState(state),
13
+ transformHtml: (html) => getTranslationLifecycle(this.#factory()).transformHtml(html)
14
+ });
15
+ }
16
+ t = (translation, vars) => this.#factory().t(translation, vars);
17
+ switchLocale = (locale) => this.#factory().switchLocale(locale);
18
+ applyHtmlLocaleAttr = (html) => this.#factory().applyHtmlLocaleAttr(html);
19
+ subscribeLocaleChangeEvent = (listener) => this.#factory().subscribeLocaleChangeEvent(listener);
20
+ get locale() {
21
+ return this.#factory().locale;
22
+ }
23
+ get locales() {
24
+ return this.#factory().locales;
25
+ }
26
+ }
@@ -0,0 +1,20 @@
1
+ import { type TranslationManagerFactory } from './Internal.js';
2
+ import type { Path, TranslationCookieOptions, MessageLoader, TranslationVariables } from './Types.js';
3
+ export declare const createTranslations: <const TMessages extends Record<string, MessageLoader>, const TInitLocale extends Extract<keyof TMessages, string>>(params: {
4
+ messages: TMessages;
5
+ initLang: TInitLocale;
6
+ cookieName?: string;
7
+ cookieOptions?: TranslationCookieOptions;
8
+ initLangFromAcceptLanguage?: boolean;
9
+ prefix?: string;
10
+ }) => (() => {
11
+ switchLocale: (requestedLocale: Extract<keyof TMessages, string>) => Promise<void>;
12
+ applyHtmlLocaleAttr: (html: string) => string;
13
+ subscribeLocaleChangeEvent: (listener: (locale: Extract<keyof TMessages, string>) => void) => (() => void);
14
+ t: (translation?: string | {
15
+ key: string | Path<Awaited<ReturnType<TMessages[TInitLocale]>>, "", []>;
16
+ vars?: TranslationVariables;
17
+ } | Path<Awaited<ReturnType<TMessages[TInitLocale]>>, "", []>, vars?: TranslationVariables) => string;
18
+ readonly locale: Extract<keyof TMessages, string>;
19
+ locales: Readonly<Record<Extract<keyof TMessages, string>, Extract<keyof TMessages, string>>>;
20
+ }) & TranslationManagerFactory;
@@ -0,0 +1,254 @@
1
+ import { bindTranslationLifecycle } from './Internal.js';
2
+ import { RegisterProvider } from '../delivery/presenter/RegisterProvider.js';
3
+ import { Cookies, TextUtil, BROWSER } from '../external/tools/index.js';
4
+ import { RequestContext } from '../shared/request-context/Internal.js';
5
+ const PLURAL_PATTERN = /{{\s*(\w+)\s*\|\s*plural\s*:(.*?)}}/g;
6
+ const VARIABLE_PATTERN = /{{\s*(\w+)\s*}}/g;
7
+ const normalizeLocale = (locale) => locale.trim().replaceAll('_', '-').toLowerCase();
8
+ const parseAcceptLanguage = (value) => {
9
+ if (!value)
10
+ return [];
11
+ return value
12
+ .split(',')
13
+ .map((part, index) => {
14
+ const [locale = '', ...parameters] = part.trim().split(';');
15
+ const qualityParameter = parameters.find((parameter) => parameter.trim().toLowerCase().startsWith('q='));
16
+ const quality = qualityParameter ? Number(qualityParameter.split('=')[1]) : 1;
17
+ return { locale: locale.trim(), quality: Number.isFinite(quality) ? quality : 0, index };
18
+ })
19
+ .filter(({ locale, quality }) => locale.length > 0 && quality > 0)
20
+ .sort((left, right) => right.quality - left.quality || left.index - right.index)
21
+ .map(({ locale }) => locale);
22
+ };
23
+ export const createTranslations = (params) => {
24
+ const snapshotKey = params.prefix ?? 'default';
25
+ const availableLocales = Object.keys(params.messages);
26
+ const locales = Object.freeze(Object.fromEntries(availableLocales.map((locale) => [locale, locale])));
27
+ if (!Object.hasOwn(params.messages, params.initLang))
28
+ throw new Error('[AzureNetKit:i18n] initLang must exist in messages.');
29
+ const normalizedLocales = new Map();
30
+ const localeVariants = new Map();
31
+ for (const locale of availableLocales) {
32
+ const normalized = normalizeLocale(locale);
33
+ const base = normalized.split('-')[0];
34
+ normalizedLocales.set(normalized, locale);
35
+ if (base && !localeVariants.has(base))
36
+ localeVariants.set(base, locale);
37
+ }
38
+ const resolveLocale = (locale) => {
39
+ if (!locale)
40
+ return undefined;
41
+ const normalized = normalizeLocale(locale);
42
+ if (!normalized)
43
+ return undefined;
44
+ const exact = normalizedLocales.get(normalized);
45
+ if (exact)
46
+ return exact;
47
+ const base = normalized.split('-')[0];
48
+ return base ? (normalizedLocales.get(base) ?? localeVariants.get(base)) : undefined;
49
+ };
50
+ const factory = RegisterProvider(() => {
51
+ const translations = new Map();
52
+ const pendingLoads = new Map();
53
+ const localeListeners = new Set();
54
+ let locale = $state.raw(params.initLang);
55
+ let activeMessages = $state.raw({});
56
+ let synchronized = false;
57
+ let initializationPromise;
58
+ let switchRevision = 0;
59
+ const loadTranslation = (requestedLocale) => {
60
+ const resolvedLocale = resolveLocale(requestedLocale);
61
+ if (!resolvedLocale) {
62
+ return Promise.reject(new Error(`[AzureNetKit:i18n] Locale "${requestedLocale}" is not present in messages.`));
63
+ }
64
+ const cached = translations.get(resolvedLocale);
65
+ if (cached)
66
+ return Promise.resolve(cached);
67
+ const pending = pendingLoads.get(resolvedLocale);
68
+ if (pending)
69
+ return pending;
70
+ const request = Promise.resolve()
71
+ .then(() => params.messages[resolvedLocale]())
72
+ .then((loaded) => {
73
+ // Imported dictionaries are module-cached; request state must not expose that shared object.
74
+ const messages = BROWSER ? loaded : structuredClone(loaded);
75
+ translations.set(resolvedLocale, messages);
76
+ return messages;
77
+ })
78
+ .finally(() => {
79
+ if (pendingLoads.get(resolvedLocale) === request)
80
+ pendingLoads.delete(resolvedLocale);
81
+ });
82
+ pendingLoads.set(resolvedLocale, request);
83
+ return request;
84
+ };
85
+ const getLocaleFromRequest = () => {
86
+ const event = RequestContext.current().event;
87
+ if (!event)
88
+ throw new Error('[AzureNetKit:i18n] RequestContext has no RequestEvent.');
89
+ const cookieLocale = params.cookieName ? resolveLocale(event.cookies.get(params.cookieName)) : undefined;
90
+ if (cookieLocale)
91
+ return cookieLocale;
92
+ if (params.initLangFromAcceptLanguage) {
93
+ for (const requestedLocale of parseAcceptLanguage(event.request.headers.get('accept-language'))) {
94
+ if (requestedLocale === '*')
95
+ return params.initLang;
96
+ const resolvedLocale = resolveLocale(requestedLocale);
97
+ if (resolvedLocale)
98
+ return resolvedLocale;
99
+ }
100
+ }
101
+ return params.initLang;
102
+ };
103
+ const preloadTranslation = async (resolver) => {
104
+ if (BROWSER)
105
+ throw new Error('[AzureNetKit:i18n] preloadTranslation() is server-only.');
106
+ const revision = ++switchRevision;
107
+ const requestedLocale = resolver ? await resolver() : getLocaleFromRequest();
108
+ const resolvedLocale = resolveLocale(requestedLocale) ?? params.initLang;
109
+ const messages = await loadTranslation(resolvedLocale);
110
+ if (revision !== switchRevision)
111
+ return;
112
+ locale = resolvedLocale;
113
+ activeMessages = messages;
114
+ const state = RequestContext.current().state;
115
+ state.currLocale = resolvedLocale;
116
+ const snapshots = (state.i18n ??= {});
117
+ Object.defineProperty(snapshots, snapshotKey, { value: { locale }, enumerable: true, configurable: true, writable: true });
118
+ };
119
+ const getTranslation = (messageKey) => {
120
+ let current = activeMessages;
121
+ for (const key of messageKey.split('.')) {
122
+ if (typeof current === 'string' || !current || !Object.hasOwn(current, key))
123
+ return undefined;
124
+ current = current[key];
125
+ }
126
+ return typeof current === 'string' ? current : undefined;
127
+ };
128
+ const translate = (messageKey, vars = {}) => {
129
+ let result = getTranslation(messageKey);
130
+ if (result === undefined)
131
+ return messageKey;
132
+ if (!result.includes('{{'))
133
+ return result;
134
+ result = result.replace(PLURAL_PATTERN, (_, variableName, titlesString) => {
135
+ const count = Number(vars[variableName]);
136
+ if (Number.isNaN(count)) {
137
+ console.warn(`[AzureNetKit:i18n] Plural variable "${variableName}" is not numeric.`);
138
+ return '';
139
+ }
140
+ return TextUtil.pluralize(count, titlesString.split(',').map((title) => title.trim()));
141
+ });
142
+ return result.replace(VARIABLE_PATTERN, (_, key) => (vars[key] === undefined ? '' : String(vars[key])));
143
+ };
144
+ const t = (translation, vars) => {
145
+ if (!translation)
146
+ return '';
147
+ const extended = typeof translation === 'object' ? translation : undefined;
148
+ return translate(String(extended?.key ?? translation), extended?.vars ?? vars);
149
+ };
150
+ const syncFromState = (state) => {
151
+ if (synchronized)
152
+ return;
153
+ const snapshot = state.i18n && Object.hasOwn(state.i18n, snapshotKey) ? state.i18n[snapshotKey] : undefined;
154
+ const nextLocale = resolveLocale(snapshot?.locale ?? state.currLocale);
155
+ if (snapshot && !nextLocale)
156
+ throw new Error('[AzureNetKit:i18n] Unsupported boot locale.');
157
+ synchronized = true;
158
+ ++switchRevision;
159
+ initializationPromise = undefined;
160
+ if (nextLocale)
161
+ locale = nextLocale;
162
+ activeMessages = translations.get(locale) ?? {};
163
+ };
164
+ const updateHtmlLocale = (currentLocale) => {
165
+ document.documentElement.lang = currentLocale;
166
+ };
167
+ const initializeClient = () => {
168
+ if (!BROWSER)
169
+ return Promise.resolve();
170
+ if (initializationPromise)
171
+ return initializationPromise;
172
+ // SSR marker is available before SvelteKit starts hydration; boot runs later.
173
+ const serverLocale = document.documentElement.getAttribute('data-azure-net-locale');
174
+ locale =
175
+ resolveLocale(serverLocale ?? undefined) ??
176
+ resolveLocale((params.cookieName ? Cookies.get(params.cookieName) : undefined) ?? undefined) ??
177
+ (params.initLangFromAcceptLanguage ? navigator.languages.map(resolveLocale).find(Boolean) : undefined) ??
178
+ locale;
179
+ const revision = switchRevision;
180
+ const request = loadTranslation(locale)
181
+ .then((messages) => {
182
+ if (revision !== switchRevision) {
183
+ if (initializationPromise === request)
184
+ initializationPromise = undefined;
185
+ return;
186
+ }
187
+ activeMessages = messages;
188
+ updateHtmlLocale(locale);
189
+ })
190
+ .catch((error) => {
191
+ if (initializationPromise === request)
192
+ initializationPromise = undefined;
193
+ throw error;
194
+ });
195
+ initializationPromise = request;
196
+ return request;
197
+ };
198
+ const switchLocale = async (requestedLocale) => {
199
+ if (!BROWSER)
200
+ throw new Error('[AzureNetKit:i18n] switchLocale() is client-only.');
201
+ const resolvedLocale = resolveLocale(requestedLocale);
202
+ if (!resolvedLocale)
203
+ throw new Error(`[AzureNetKit:i18n] Locale "${requestedLocale}" is not present in messages.`);
204
+ const revision = ++switchRevision;
205
+ const messages = await loadTranslation(resolvedLocale);
206
+ if (revision !== switchRevision)
207
+ return;
208
+ locale = resolvedLocale;
209
+ activeMessages = messages;
210
+ if (params.cookieName) {
211
+ Cookies.set(params.cookieName, resolvedLocale, {
212
+ path: '/',
213
+ sameSite: 'Lax',
214
+ ...params.cookieOptions
215
+ });
216
+ }
217
+ updateHtmlLocale(resolvedLocale);
218
+ for (const listener of localeListeners)
219
+ listener(resolvedLocale);
220
+ };
221
+ const subscribeLocaleChangeEvent = (listener) => {
222
+ if (!BROWSER)
223
+ throw new Error('[AzureNetKit:i18n] Locale change events are client-only.');
224
+ localeListeners.add(listener);
225
+ return () => localeListeners.delete(listener);
226
+ };
227
+ const applyHtmlLocaleAttr = (html) => html.replaceAll('%lang%', () => locale);
228
+ const manager = {
229
+ switchLocale,
230
+ applyHtmlLocaleAttr,
231
+ subscribeLocaleChangeEvent,
232
+ t,
233
+ get locale() {
234
+ return locale;
235
+ },
236
+ locales
237
+ };
238
+ const escapeAttribute = (value) => value.replaceAll('&', '&amp;').replaceAll('"', '&quot;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');
239
+ bindTranslationLifecycle(manager, {
240
+ preloadTranslation,
241
+ initializeClient,
242
+ syncFromState,
243
+ transformHtml: (html) => applyHtmlLocaleAttr(html).replace(/<html\b([^>]*)>/i, (_, attrs) => '<html' +
244
+ attrs.replace(/\s+(?:data-azure-net-locale|lang)\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, '') +
245
+ ' lang="' +
246
+ escapeAttribute(locale) +
247
+ '" data-azure-net-locale="' +
248
+ escapeAttribute(locale) +
249
+ '">')
250
+ });
251
+ return manager;
252
+ }, { lifecycle: 'singleton' });
253
+ return factory;
254
+ };
@@ -0,0 +1,30 @@
1
+ export interface TranslationSnapshot {
2
+ locale: string;
3
+ }
4
+ declare global {
5
+ namespace App {
6
+ interface RequestContextState {
7
+ currLocale?: string;
8
+ i18n?: Record<string, TranslationSnapshot>;
9
+ }
10
+ }
11
+ }
12
+ export type Translation = string | {
13
+ [key: string]: Translation;
14
+ };
15
+ export type TranslationShape<T> = T extends string ? string : T extends Record<string, unknown> ? {
16
+ [K in keyof T]: TranslationShape<T[K]>;
17
+ } : never;
18
+ export type Path<T, Prefix extends string = '', Depth extends unknown[] = []> = T extends string ? never : T extends Record<string, unknown> ? {
19
+ [K in keyof T & string]: T[K] extends string ? `${Prefix}${K}` : Depth['length'] extends 12 ? `${Prefix}${K}.${string}` : Path<T[K], `${Prefix}${K}.`, [...Depth, unknown]>;
20
+ }[keyof T & string] : never;
21
+ export interface TranslationCookieOptions {
22
+ expires?: Date | number;
23
+ maxAge?: number;
24
+ path?: string;
25
+ domain?: string;
26
+ secure?: boolean;
27
+ sameSite?: 'Strict' | 'Lax' | 'None';
28
+ }
29
+ export type MessageLoader = () => Promise<Translation>;
30
+ export type TranslationVariables = Record<string, unknown>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export { createTranslations } from './Translations.svelte.js';
2
+ export { TranslationManager, type TranslationManagerOptions } from './TranslationManager.js';
3
+ export type { Translation, TranslationShape, TranslationSnapshot, TranslationCookieOptions, Path } from './Types.js';
@@ -0,0 +1,2 @@
1
+ export { createTranslations } from './Translations.svelte.js';
2
+ export { TranslationManager } from './TranslationManager.js';
@@ -1,16 +1,29 @@
1
- import { type IHttpServiceInstance, type IHttpServiceResponse } from '../http-service/HttpServiceInstance.js';
2
- import { type IQueryBuilderInstance } from '../query/index.js';
1
+ import { type IHttpInstanceOptions, type IHttpServiceInstance, type IHttpServiceResponse } from '../http-service/HttpServiceInstance.js';
2
+ import { type IQueryBuilderInstance, type IQueryBuilderSettings } from '../query/index.js';
3
3
  export type CreateRequestCallbackType<T> = (params: {
4
4
  http: IHttpServiceInstance;
5
5
  query: IQueryBuilderInstance;
6
6
  }) => Promise<IHttpServiceResponse<T>>;
7
+ type HttpClientParams = {
8
+ http: IHttpServiceInstance;
9
+ httpOptions?: never;
10
+ } | {
11
+ http?: never;
12
+ httpOptions?: IHttpInstanceOptions;
13
+ };
14
+ type QueryBuilderParams = {
15
+ query: IQueryBuilderInstance;
16
+ queryOptions?: never;
17
+ } | {
18
+ query?: never;
19
+ queryOptions?: IQueryBuilderSettings;
20
+ };
21
+ type DatasourceParams = HttpClientParams & QueryBuilderParams;
7
22
  export declare class BaseHttpDatasource {
8
23
  protected readonly httpClient: IHttpServiceInstance;
9
24
  protected readonly query: IQueryBuilderInstance;
10
- constructor(params: {
11
- http?: IHttpServiceInstance;
12
- query?: IQueryBuilderInstance;
13
- });
25
+ constructor(params: DatasourceParams);
14
26
  private _createRawRequest;
15
27
  readonly createRawRequest: <T>(callback: CreateRequestCallbackType<T>) => Promise<IHttpServiceResponse<T>>;
16
28
  }
29
+ export {};
@@ -1,11 +1,11 @@
1
- import { createHttpServiceInstance } from '../http-service/HttpServiceInstance.js';
2
- import { createQueryInstance } from '../query/index.js';
1
+ import { HttpServiceInstance } from '../http-service/HttpServiceInstance.js';
2
+ import { QueryBuilder } from '../query/index.js';
3
3
  export class BaseHttpDatasource {
4
4
  httpClient;
5
5
  query;
6
6
  constructor(params) {
7
- this.httpClient = params.http ?? createHttpServiceInstance();
8
- this.query = params.query ?? createQueryInstance();
7
+ this.httpClient = params.http ?? new HttpServiceInstance(params?.httpOptions);
8
+ this.query = params.query ?? new QueryBuilder(params?.queryOptions);
9
9
  }
10
10
  _createRawRequest(callback) {
11
11
  return callback({ http: this.httpClient, query: this.query });
@@ -47,7 +47,7 @@ export declare class HttpServiceError<T> extends Error implements IHttpServiceEr
47
47
  response?: Response;
48
48
  constructor({ status, data, message, original, type, response }: Partial<IHttpServiceError<T>>);
49
49
  }
50
- export interface HttpInstanceNormalizedRequest {
50
+ export interface IHttpInstanceNormalizedRequest {
51
51
  url: string | URL;
52
52
  method: HttpInstanceFetchMethods;
53
53
  headers: Headers;
@@ -60,9 +60,9 @@ export interface HttpInstanceNormalizedRequest {
60
60
  fetch: typeof fetch;
61
61
  requestInit: Omit<RequestInit, 'method' | 'headers' | 'body'>;
62
62
  }
63
- export type HttpInstanceOnRequest = (request: HttpInstanceNormalizedRequest) => void | Promise<void>;
64
- export type HttpInstanceOnError = (error: HttpServiceError<unknown>, request: HttpInstanceNormalizedRequest) => void | Promise<void>;
65
- export type HttpInstanceDoFetch = <T = unknown>(request: HttpInstanceNormalizedRequest) => Promise<IHttpServiceResponse<T>>;
63
+ export type IHttpInstanceOnRequest = (request: IHttpInstanceNormalizedRequest) => void | Promise<void>;
64
+ export type IHttpInstanceOnError = (error: HttpServiceError<unknown>, request: IHttpInstanceNormalizedRequest) => void | Promise<void>;
65
+ export type IHttpInstanceDoFetch = <T = unknown>(request: IHttpInstanceNormalizedRequest) => Promise<IHttpServiceResponse<T>>;
66
66
  export interface IHttpInstanceOptions<T = unknown> extends Omit<RequestInit, 'headers' | 'method'> {
67
67
  headers?: NonNullable<RequestInit['headers']> | Record<string, string | undefined>;
68
68
  json?: unknown;
@@ -73,19 +73,29 @@ export interface IHttpInstanceOptions<T = unknown> extends Omit<RequestInit, 'he
73
73
  retry?: number;
74
74
  includeRaw?: boolean;
75
75
  responseFormat?: 'json' | 'blob' | 'text' | 'arrayBuffer' | 'body';
76
- onRequest?: HttpInstanceOnRequest;
77
- onError?: HttpInstanceOnError;
76
+ onRequest?: IHttpInstanceOnRequest;
77
+ onError?: IHttpInstanceOnError;
78
78
  onResponse?: (response: IHttpServiceResponse<T>) => void | Promise<void>;
79
- doFetch?: HttpInstanceDoFetch;
79
+ doFetch?: IHttpInstanceDoFetch;
80
80
  }
81
- export type HttpInstanceRequestMethod = <T = unknown>(url: string | URL, options?: IHttpInstanceOptions<T>) => Promise<IHttpServiceResponse<T>>;
81
+ export type IHttpInstanceRequestMethod = <T = unknown>(url: string | URL, options?: IHttpInstanceOptions<T>) => Promise<IHttpServiceResponse<T>>;
82
82
  export interface IHttpServiceInstance {
83
- get: HttpInstanceRequestMethod;
84
- post: HttpInstanceRequestMethod;
85
- put: HttpInstanceRequestMethod;
86
- delete: HttpInstanceRequestMethod;
87
- patch: HttpInstanceRequestMethod;
88
- head: HttpInstanceRequestMethod;
89
- options: HttpInstanceRequestMethod;
83
+ get: IHttpInstanceRequestMethod;
84
+ post: IHttpInstanceRequestMethod;
85
+ put: IHttpInstanceRequestMethod;
86
+ delete: IHttpInstanceRequestMethod;
87
+ patch: IHttpInstanceRequestMethod;
88
+ head: IHttpInstanceRequestMethod;
89
+ options: IHttpInstanceRequestMethod;
90
+ }
91
+ export declare class HttpServiceInstance implements IHttpServiceInstance {
92
+ private readonly defaults;
93
+ readonly get: IHttpInstanceRequestMethod;
94
+ readonly post: IHttpInstanceRequestMethod;
95
+ readonly put: IHttpInstanceRequestMethod;
96
+ readonly delete: IHttpInstanceRequestMethod;
97
+ readonly patch: IHttpInstanceRequestMethod;
98
+ readonly head: IHttpInstanceRequestMethod;
99
+ readonly options: IHttpInstanceRequestMethod;
100
+ constructor(config?: IHttpInstanceOptions);
90
101
  }
91
- export declare const createHttpServiceInstance: (config?: IHttpInstanceOptions) => IHttpServiceInstance;