@zucker-framework/i18n 1.0.0

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/dist/index.mjs ADDED
@@ -0,0 +1,636 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/message-source.ts
5
+ import { readFileSync, existsSync } from "fs";
6
+ import { join } from "path";
7
+ import { Logger } from "@nestjs/common";
8
+ var FileMessageSource = class _FileMessageSource {
9
+ static {
10
+ __name(this, "FileMessageSource");
11
+ }
12
+ basePath;
13
+ logger = new Logger(_FileMessageSource.name);
14
+ messages = /* @__PURE__ */ new Map();
15
+ defaultLocale;
16
+ constructor(basePath, options) {
17
+ this.basePath = basePath;
18
+ this.defaultLocale = options?.defaultLocale ?? "en";
19
+ }
20
+ getMessage(code, args, defaultMessage, locale) {
21
+ const loc = locale ?? this.defaultLocale;
22
+ const messages = this.loadLocale(loc);
23
+ let message = messages[code];
24
+ if (message === void 0 && loc !== this.defaultLocale) {
25
+ const defaultMessages = this.loadLocale(this.defaultLocale);
26
+ message = defaultMessages[code];
27
+ }
28
+ if (message === void 0) {
29
+ return defaultMessage ?? code;
30
+ }
31
+ return this.interpolate(message, args);
32
+ }
33
+ loadLocale(locale) {
34
+ if (this.messages.has(locale)) {
35
+ return this.messages.get(locale);
36
+ }
37
+ const filePath = join(this.basePath, `${locale}.json`);
38
+ if (!existsSync(filePath)) {
39
+ this.messages.set(locale, {});
40
+ return {};
41
+ }
42
+ try {
43
+ const content = readFileSync(filePath, "utf-8");
44
+ const data = JSON.parse(content);
45
+ const flat = this.flatten(data);
46
+ this.messages.set(locale, flat);
47
+ return flat;
48
+ } catch (err) {
49
+ this.logger.warn(`Failed to load i18n messages for locale "${locale}": ${err instanceof Error ? err.message : String(err)}`);
50
+ this.messages.set(locale, {});
51
+ return {};
52
+ }
53
+ }
54
+ flatten(obj, prefix = "") {
55
+ const result = {};
56
+ for (const [key, value] of Object.entries(obj)) {
57
+ const fullKey = prefix ? `${prefix}.${key}` : key;
58
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
59
+ Object.assign(result, this.flatten(value, fullKey));
60
+ } else {
61
+ result[fullKey] = String(value);
62
+ }
63
+ }
64
+ return result;
65
+ }
66
+ interpolate(message, args) {
67
+ if (!args || args.length === 0) return message;
68
+ return message.replace(/\{(\d+)\}/g, (match, index) => {
69
+ const idx = parseInt(index, 10);
70
+ return idx < args.length ? String(args[idx]) : match;
71
+ });
72
+ }
73
+ clearCache() {
74
+ this.messages.clear();
75
+ }
76
+ };
77
+ var CompositeMessageSource = class {
78
+ static {
79
+ __name(this, "CompositeMessageSource");
80
+ }
81
+ sources;
82
+ constructor(sources) {
83
+ this.sources = sources;
84
+ }
85
+ getMessage(code, args, defaultMessage, locale) {
86
+ for (const source of this.sources) {
87
+ const message = source.getMessage(code, args, void 0, locale);
88
+ if (message !== code) {
89
+ return message;
90
+ }
91
+ }
92
+ return defaultMessage ?? code;
93
+ }
94
+ addSource(source) {
95
+ this.sources.push(source);
96
+ }
97
+ /**
98
+ * Add multiple sources at once.
99
+ */
100
+ addSources(sources) {
101
+ this.sources.push(...sources);
102
+ }
103
+ /**
104
+ * Get the number of registered sources.
105
+ */
106
+ getSourceCount() {
107
+ return this.sources.length;
108
+ }
109
+ };
110
+
111
+ // src/locale-context.ts
112
+ import { AsyncLocalStorage } from "async_hooks";
113
+ import { Injectable } from "@nestjs/common";
114
+ import { Observable } from "rxjs";
115
+ function _ts_decorate(decorators, target, key, desc) {
116
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
117
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
118
+ r = Reflect.decorate(decorators, target, key, desc);
119
+ } else {
120
+ for (var i = decorators.length - 1; i >= 0; i--) {
121
+ if (d = decorators[i]) {
122
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
123
+ }
124
+ }
125
+ }
126
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
127
+ }
128
+ __name(_ts_decorate, "_ts_decorate");
129
+ function _ts_metadata(metadataKey, metadataValue) {
130
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
131
+ return Reflect.metadata(metadataKey, metadataValue);
132
+ }
133
+ }
134
+ __name(_ts_metadata, "_ts_metadata");
135
+ var localeStorage = new AsyncLocalStorage();
136
+ var LocaleContext = class {
137
+ static {
138
+ __name(this, "LocaleContext");
139
+ }
140
+ static defaultLocale = "en";
141
+ static setDefault(locale) {
142
+ this.defaultLocale = locale;
143
+ }
144
+ static getCurrent() {
145
+ return localeStorage.getStore() ?? this.defaultLocale;
146
+ }
147
+ static run(locale, fn) {
148
+ return localeStorage.run(locale, fn);
149
+ }
150
+ static runAsync(locale, fn) {
151
+ return localeStorage.run(locale, fn);
152
+ }
153
+ };
154
+ var LocaleInterceptor = class {
155
+ static {
156
+ __name(this, "LocaleInterceptor");
157
+ }
158
+ supportedLocales;
159
+ defaultLocale;
160
+ constructor(options) {
161
+ this.supportedLocales = new Set(options?.supportedLocales ?? [
162
+ "en",
163
+ "zh"
164
+ ]);
165
+ this.defaultLocale = options?.defaultLocale ?? "en";
166
+ }
167
+ intercept(context, next) {
168
+ const request = context.switchToHttp().getRequest();
169
+ const acceptLanguage = request.headers?.["accept-language"] ?? "";
170
+ const locale = this.parseLocale(acceptLanguage);
171
+ return new Observable((subscriber) => {
172
+ LocaleContext.runAsync(locale, async () => {
173
+ return new Promise((resolve, reject) => {
174
+ next.handle().subscribe({
175
+ next: /* @__PURE__ */ __name((value) => subscriber.next(value), "next"),
176
+ error: /* @__PURE__ */ __name((err) => {
177
+ subscriber.error(err);
178
+ reject(err);
179
+ }, "error"),
180
+ complete: /* @__PURE__ */ __name(() => {
181
+ subscriber.complete();
182
+ resolve();
183
+ }, "complete")
184
+ });
185
+ });
186
+ }).catch((err) => subscriber.error(err));
187
+ });
188
+ }
189
+ parseLocale(header) {
190
+ if (!header) return this.defaultLocale;
191
+ const parts = header.split(",").map((part) => {
192
+ const [lang, q] = part.trim().split(";q=");
193
+ return {
194
+ lang: lang.trim().split("-")[0].toLowerCase(),
195
+ quality: q ? parseFloat(q) : 1
196
+ };
197
+ });
198
+ parts.sort((a, b) => b.quality - a.quality);
199
+ for (const { lang } of parts) {
200
+ if (this.supportedLocales.has(lang)) {
201
+ return lang;
202
+ }
203
+ }
204
+ return this.defaultLocale;
205
+ }
206
+ };
207
+ LocaleInterceptor = _ts_decorate([
208
+ Injectable(),
209
+ _ts_metadata("design:type", Function),
210
+ _ts_metadata("design:paramtypes", [
211
+ Object
212
+ ])
213
+ ], LocaleInterceptor);
214
+
215
+ // src/i18n-exception.ts
216
+ import { BusinessException } from "@zucker-framework/core";
217
+ var I18nException = class extends BusinessException {
218
+ static {
219
+ __name(this, "I18nException");
220
+ }
221
+ i18nCode;
222
+ i18nArgs;
223
+ constructor(code, args = [], fallbackMessage) {
224
+ super(fallbackMessage ?? code, code);
225
+ this.i18nCode = code;
226
+ this.i18nArgs = args;
227
+ }
228
+ };
229
+
230
+ // src/i18n-spec.ts
231
+ var I18nSpec = class _I18nSpec {
232
+ static {
233
+ __name(this, "I18nSpec");
234
+ }
235
+ /** i18n message code; if empty, defaultMessage is returned as-is */
236
+ code;
237
+ /** Fallback message when code is not found or not provided */
238
+ defaultMessage;
239
+ /** Nested i18n-aware arguments */
240
+ args;
241
+ constructor(code, defaultMessage, args = []) {
242
+ this.code = code;
243
+ this.defaultMessage = defaultMessage;
244
+ this.args = args;
245
+ }
246
+ /**
247
+ * Create an I18nSpec with optional nested args.
248
+ * Args can be strings (treated as plain defaults) or I18nSpec instances.
249
+ */
250
+ static of(code, defaultMessage, ...args) {
251
+ const specArgs = args.map((arg) => {
252
+ if (typeof arg === "string") {
253
+ return new _I18nSpec(void 0, arg);
254
+ }
255
+ return arg;
256
+ });
257
+ return new _I18nSpec(code, defaultMessage, specArgs);
258
+ }
259
+ /**
260
+ * Add a nested i18n argument.
261
+ */
262
+ withArg(code, defaultMessage, ...args) {
263
+ this.args.push(_I18nSpec.of(code, defaultMessage, ...args));
264
+ return this;
265
+ }
266
+ /**
267
+ * Add an existing I18nSpec as a nested argument.
268
+ */
269
+ withArgSpec(spec) {
270
+ this.args.push(spec);
271
+ return this;
272
+ }
273
+ /**
274
+ * Resolve this spec to a final message using the given MessageSource.
275
+ * Recursively resolves nested args first.
276
+ */
277
+ resolve(messageSource, locale) {
278
+ const currentLocale = locale ?? LocaleContext.getCurrent();
279
+ if (this.args.length === 0) {
280
+ return this.resolveMessage(messageSource, currentLocale);
281
+ }
282
+ const resolvedArgs = this.args.map((arg) => arg.resolve(messageSource, currentLocale));
283
+ return this.resolveMessage(messageSource, currentLocale, resolvedArgs);
284
+ }
285
+ /**
286
+ * Create a shallow copy of this spec.
287
+ */
288
+ copy() {
289
+ return new _I18nSpec(this.code, this.defaultMessage, this.args.map((a) => a.copy()));
290
+ }
291
+ resolveMessage(messageSource, locale, resolvedArgs) {
292
+ if (!this.code) {
293
+ return this.defaultMessage ?? "";
294
+ }
295
+ return messageSource.getMessage(this.code, resolvedArgs, this.defaultMessage, locale);
296
+ }
297
+ };
298
+
299
+ // src/locale-utils.ts
300
+ function formatMessage(template, params) {
301
+ if (Array.isArray(params)) {
302
+ return template.replace(/\{(\d+)\}/g, (match, index) => {
303
+ const idx = parseInt(index, 10);
304
+ return idx < params.length ? String(params[idx]) : match;
305
+ });
306
+ }
307
+ return template.replace(/\{(\w+)\}/g, (match, key) => {
308
+ return key in params ? String(params[key]) : match;
309
+ });
310
+ }
311
+ __name(formatMessage, "formatMessage");
312
+ function pluralize(count, forms, locale) {
313
+ let form;
314
+ if (count === 0 && forms.zero !== void 0) {
315
+ form = forms.zero;
316
+ } else if (count === 1 && forms.one !== void 0) {
317
+ form = forms.one;
318
+ } else {
319
+ form = forms.other;
320
+ }
321
+ const loc = locale ?? LocaleContext.getCurrent();
322
+ const formatted = new Intl.NumberFormat(loc).format(count);
323
+ return form.replace(/\{count\}/g, formatted);
324
+ }
325
+ __name(pluralize, "pluralize");
326
+ function formatDate(date, style = "medium", locale) {
327
+ const loc = locale ?? LocaleContext.getCurrent();
328
+ const d = typeof date === "number" ? new Date(date) : date;
329
+ return new Intl.DateTimeFormat(loc, {
330
+ dateStyle: style
331
+ }).format(d);
332
+ }
333
+ __name(formatDate, "formatDate");
334
+ function formatNumber(value, style = "decimal", options, locale) {
335
+ const loc = locale ?? LocaleContext.getCurrent();
336
+ const opts = {
337
+ style
338
+ };
339
+ if (style === "currency") {
340
+ opts.currency = options?.currency ?? "USD";
341
+ }
342
+ return new Intl.NumberFormat(loc, opts).format(value);
343
+ }
344
+ __name(formatNumber, "formatNumber");
345
+ function formatRelativeTime(date, locale) {
346
+ const loc = locale ?? LocaleContext.getCurrent();
347
+ const d = typeof date === "number" ? new Date(date) : date;
348
+ const diffMs = d.getTime() - Date.now();
349
+ const units = [
350
+ [
351
+ "year",
352
+ 365 * 24 * 60 * 60 * 1e3
353
+ ],
354
+ [
355
+ "month",
356
+ 30 * 24 * 60 * 60 * 1e3
357
+ ],
358
+ [
359
+ "week",
360
+ 7 * 24 * 60 * 60 * 1e3
361
+ ],
362
+ [
363
+ "day",
364
+ 24 * 60 * 60 * 1e3
365
+ ],
366
+ [
367
+ "hour",
368
+ 60 * 60 * 1e3
369
+ ],
370
+ [
371
+ "minute",
372
+ 60 * 1e3
373
+ ],
374
+ [
375
+ "second",
376
+ 1e3
377
+ ]
378
+ ];
379
+ const rtf = new Intl.RelativeTimeFormat(loc, {
380
+ numeric: "auto"
381
+ });
382
+ for (const [unit, ms] of units) {
383
+ if (Math.abs(diffMs) >= ms) {
384
+ const value = Math.round(diffMs / ms);
385
+ return rtf.format(value, unit);
386
+ }
387
+ }
388
+ return rtf.format(0, "second");
389
+ }
390
+ __name(formatRelativeTime, "formatRelativeTime");
391
+ var LocaleUtils = class {
392
+ static {
393
+ __name(this, "LocaleUtils");
394
+ }
395
+ static messageSource = null;
396
+ /**
397
+ * Set the global message source used by resolve helpers.
398
+ */
399
+ static setMessageSource(source) {
400
+ this.messageSource = source;
401
+ }
402
+ /**
403
+ * Get the currently configured global message source.
404
+ */
405
+ static getMessageSource() {
406
+ return this.messageSource;
407
+ }
408
+ /**
409
+ * Resolve a message using the current locale context.
410
+ */
411
+ static resolveMessage(code, ...args) {
412
+ return this.resolveMessageWithDefault(code, code, ...args);
413
+ }
414
+ /**
415
+ * Resolve a message with a default fallback using the current locale context.
416
+ */
417
+ static resolveMessageWithDefault(code, defaultMessage, ...args) {
418
+ if (!this.messageSource) {
419
+ return defaultMessage;
420
+ }
421
+ const locale = LocaleContext.getCurrent();
422
+ return this.messageSource.getMessage(code, args, defaultMessage, locale);
423
+ }
424
+ /**
425
+ * Resolve a message using a specific MessageSource and the current locale.
426
+ */
427
+ static resolveMessageFrom(source, code, defaultMessage, ...args) {
428
+ const locale = LocaleContext.getCurrent();
429
+ return source.getMessage(code, args, defaultMessage, locale);
430
+ }
431
+ /**
432
+ * Resolve a message using a specific locale (ignoring the context locale).
433
+ */
434
+ static resolveMessageForLocale(code, locale, defaultMessage, ...args) {
435
+ if (!this.messageSource) {
436
+ return defaultMessage ?? code;
437
+ }
438
+ return this.messageSource.getMessage(code, args, defaultMessage, locale);
439
+ }
440
+ /**
441
+ * Execute a function within a specific locale context.
442
+ */
443
+ static doWith(locale, fn) {
444
+ return LocaleContext.run(locale, fn);
445
+ }
446
+ /**
447
+ * Execute an async function within a specific locale context.
448
+ */
449
+ static doWithAsync(locale, fn) {
450
+ return LocaleContext.runAsync(locale, fn);
451
+ }
452
+ /**
453
+ * Get the current locale.
454
+ */
455
+ static current() {
456
+ return LocaleContext.getCurrent();
457
+ }
458
+ };
459
+
460
+ // src/decorators.ts
461
+ var i18nRegistry = /* @__PURE__ */ new Map();
462
+ function makeKey(target, propertyKey) {
463
+ const ctorName = target.constructor?.name ?? "Unknown";
464
+ return `${ctorName}.${String(propertyKey)}`;
465
+ }
466
+ __name(makeKey, "makeKey");
467
+ function I18n(code) {
468
+ return (target, propertyKey) => {
469
+ i18nRegistry.set(makeKey(target, propertyKey), code);
470
+ };
471
+ }
472
+ __name(I18n, "I18n");
473
+ function getI18nCode(target, propertyKey) {
474
+ return i18nRegistry.get(makeKey(target, propertyKey));
475
+ }
476
+ __name(getI18nCode, "getI18nCode");
477
+ function hasI18n(target, propertyKey) {
478
+ return i18nRegistry.has(makeKey(target, propertyKey));
479
+ }
480
+ __name(hasI18n, "hasI18n");
481
+
482
+ // src/i18n.module.ts
483
+ import { Module, Global } from "@nestjs/common";
484
+ function _ts_decorate2(decorators, target, key, desc) {
485
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
486
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
487
+ r = Reflect.decorate(decorators, target, key, desc);
488
+ } else {
489
+ for (var i = decorators.length - 1; i >= 0; i--) {
490
+ if (d = decorators[i]) {
491
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
492
+ }
493
+ }
494
+ }
495
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
496
+ }
497
+ __name(_ts_decorate2, "_ts_decorate");
498
+ var MESSAGE_SOURCE = "MESSAGE_SOURCE";
499
+ var I18N_MODULE_OPTIONS = /* @__PURE__ */ Symbol("I18N_MODULE_OPTIONS");
500
+ var ZuckerI18nModule = class _ZuckerI18nModule {
501
+ static {
502
+ __name(this, "ZuckerI18nModule");
503
+ }
504
+ static forRoot(options = {}) {
505
+ return {
506
+ module: _ZuckerI18nModule,
507
+ global: true,
508
+ providers: _ZuckerI18nModule.buildProviders(options),
509
+ exports: [
510
+ MESSAGE_SOURCE,
511
+ LocaleInterceptor
512
+ ]
513
+ };
514
+ }
515
+ static forRootAsync(options) {
516
+ const asyncProviders = this.createAsyncProviders(options);
517
+ return {
518
+ module: _ZuckerI18nModule,
519
+ global: true,
520
+ imports: options.imports ?? [],
521
+ providers: [
522
+ ...asyncProviders,
523
+ {
524
+ provide: MESSAGE_SOURCE,
525
+ useFactory: /* @__PURE__ */ __name((opts) => {
526
+ const sources = [];
527
+ if (opts.basePath) {
528
+ sources.push(new FileMessageSource(opts.basePath, {
529
+ defaultLocale: opts.defaultLocale
530
+ }));
531
+ }
532
+ if (opts.messageSources) {
533
+ sources.push(...opts.messageSources);
534
+ }
535
+ return sources.length === 1 ? sources[0] : new CompositeMessageSource(sources);
536
+ }, "useFactory"),
537
+ inject: [
538
+ I18N_MODULE_OPTIONS
539
+ ]
540
+ },
541
+ {
542
+ provide: LocaleInterceptor,
543
+ useFactory: /* @__PURE__ */ __name((opts) => new LocaleInterceptor({
544
+ supportedLocales: opts.supportedLocales,
545
+ defaultLocale: opts.defaultLocale
546
+ }), "useFactory"),
547
+ inject: [
548
+ I18N_MODULE_OPTIONS
549
+ ]
550
+ }
551
+ ],
552
+ exports: [
553
+ MESSAGE_SOURCE,
554
+ LocaleInterceptor
555
+ ]
556
+ };
557
+ }
558
+ static buildProviders(options) {
559
+ const providers = [];
560
+ const sources = [];
561
+ if (options.basePath) {
562
+ sources.push(new FileMessageSource(options.basePath, {
563
+ defaultLocale: options.defaultLocale
564
+ }));
565
+ }
566
+ if (options.messageSources) {
567
+ sources.push(...options.messageSources);
568
+ }
569
+ const messageSource = sources.length === 1 ? sources[0] : new CompositeMessageSource(sources);
570
+ providers.push({
571
+ provide: MESSAGE_SOURCE,
572
+ useValue: messageSource
573
+ });
574
+ providers.push({
575
+ provide: LocaleInterceptor,
576
+ useValue: new LocaleInterceptor({
577
+ supportedLocales: options.supportedLocales,
578
+ defaultLocale: options.defaultLocale
579
+ })
580
+ });
581
+ return providers;
582
+ }
583
+ static createAsyncProviders(options) {
584
+ if (options.useFactory) {
585
+ return [
586
+ {
587
+ provide: I18N_MODULE_OPTIONS,
588
+ useFactory: options.useFactory,
589
+ inject: options.inject ?? []
590
+ }
591
+ ];
592
+ }
593
+ const optionsProvider = {
594
+ provide: I18N_MODULE_OPTIONS,
595
+ useFactory: /* @__PURE__ */ __name(async (factory) => factory.createI18nOptions(), "useFactory"),
596
+ inject: [
597
+ options.useClass ?? options.useExisting
598
+ ]
599
+ };
600
+ if (options.useClass) {
601
+ return [
602
+ {
603
+ provide: options.useClass,
604
+ useClass: options.useClass
605
+ },
606
+ optionsProvider
607
+ ];
608
+ }
609
+ return [
610
+ optionsProvider
611
+ ];
612
+ }
613
+ };
614
+ ZuckerI18nModule = _ts_decorate2([
615
+ Global(),
616
+ Module({})
617
+ ], ZuckerI18nModule);
618
+ export {
619
+ CompositeMessageSource,
620
+ FileMessageSource,
621
+ I18n,
622
+ I18nException,
623
+ I18nSpec,
624
+ LocaleContext,
625
+ LocaleInterceptor,
626
+ LocaleUtils,
627
+ MESSAGE_SOURCE,
628
+ ZuckerI18nModule,
629
+ formatDate,
630
+ formatMessage,
631
+ formatNumber,
632
+ formatRelativeTime,
633
+ getI18nCode,
634
+ hasI18n,
635
+ pluralize
636
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@zucker-framework/i18n",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "module": "dist/index.mjs",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "LICENSE",
10
+ "NOTICE",
11
+ "THIRD_PARTY_NOTICES"
12
+ ],
13
+ "dependencies": {
14
+ "@zucker-framework/core": "1.0.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "^20.19.43"
18
+ },
19
+ "peerDependencies": {
20
+ "@nestjs/common": "^12.0.0",
21
+ "@nestjs/core": "^12.0.0",
22
+ "rxjs": "^7.0.0"
23
+ },
24
+ "license": "Apache-2.0",
25
+ "author": "Lind",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/Lind-pro/zucker-framework.git",
29
+ "directory": "packages/i18n"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public",
33
+ "registry": "https://registry.npmjs.org/"
34
+ },
35
+ "scripts": {
36
+ "build": "tsup src/index.ts --format cjs,esm --dts",
37
+ "clean": "rm -rf dist",
38
+ "lint": "eslint src/"
39
+ }
40
+ }