@yimingliao/cms 0.0.20 → 0.0.22

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.
@@ -0,0 +1,30 @@
1
+ import { R as Result } from '../types-DHlRoJwv.js';
2
+ import { Logger } from 'logry';
3
+
4
+ interface FetchContext {
5
+ input: string;
6
+ init: RequestInit;
7
+ meta: {
8
+ baseUrl?: string;
9
+ url?: string;
10
+ startTime?: number;
11
+ };
12
+ }
13
+ type RequestInterceptor = (ctx: FetchContext) => Promise<FetchContext> | FetchContext;
14
+ type ResponseInterceptor = <T>(response: Response, ctx: FetchContext) => Promise<Result<T>>;
15
+
16
+ declare function createSmartFetch({ requestInterceptor, responseInterceptor, logger, }: {
17
+ requestInterceptor: RequestInterceptor;
18
+ responseInterceptor: ResponseInterceptor;
19
+ logger: Logger;
20
+ }): <T>(input: string, init?: RequestInit) => Promise<Result<T>>;
21
+
22
+ declare function createRequestInterceptor({ baseUrl }: {
23
+ baseUrl: string;
24
+ }): RequestInterceptor;
25
+
26
+ declare function createResponseInterceptor({ logger }: {
27
+ logger: Logger;
28
+ }): ResponseInterceptor;
29
+
30
+ export { createRequestInterceptor, createResponseInterceptor, createSmartFetch };
@@ -0,0 +1,68 @@
1
+ // src/client/infrastructure/smart-fetch/smart-fetch.ts
2
+ function createSmartFetch({
3
+ requestInterceptor,
4
+ responseInterceptor,
5
+ logger
6
+ }) {
7
+ const smartFetch = async (input, init = {}) => {
8
+ try {
9
+ let ctx = {
10
+ input,
11
+ init,
12
+ meta: {}
13
+ };
14
+ ctx = await requestInterceptor(ctx);
15
+ const response = await fetch(ctx.input, ctx.init);
16
+ return await responseInterceptor(response, ctx);
17
+ } catch (error) {
18
+ logger.error("smartFetch error", { error });
19
+ throw error instanceof Error ? error : new Error("Network Error");
20
+ }
21
+ };
22
+ return smartFetch;
23
+ }
24
+
25
+ // src/client/infrastructure/smart-fetch/request-interceptor.ts
26
+ function createRequestInterceptor({ baseUrl }) {
27
+ const requestInterceptor = (ctx) => {
28
+ const normalizedBase = baseUrl.replace(/\/+$/, "");
29
+ const normalizedPath = ctx.input.replace(/^\/+/, "");
30
+ const isAbsolute = /^[a-zA-Z]+:\/\//.test(ctx.input);
31
+ const input = isAbsolute ? ctx.input : `${normalizedBase}/${normalizedPath}`;
32
+ return {
33
+ ...ctx,
34
+ input,
35
+ meta: {
36
+ ...ctx.meta,
37
+ baseUrl,
38
+ url: ctx.input,
39
+ startTime: Date.now()
40
+ }
41
+ };
42
+ };
43
+ return requestInterceptor;
44
+ }
45
+
46
+ // src/client/infrastructure/smart-fetch/response-interceptor.ts
47
+ function createResponseInterceptor({ logger }) {
48
+ const responseInterceptor = async (response, ctx) => {
49
+ const duration = Date.now() - (ctx.meta.startTime ?? 0);
50
+ if (!response.ok) {
51
+ logger.warn("HTTP error", {
52
+ url: ctx.meta.url,
53
+ status: response.status,
54
+ duration
55
+ });
56
+ throw new Error(`HTTP Error ${response.status}`);
57
+ }
58
+ const data = await response.json();
59
+ logger.debug("Fetch success", {
60
+ url: ctx.meta.url,
61
+ duration
62
+ });
63
+ return data;
64
+ };
65
+ return responseInterceptor;
66
+ }
67
+
68
+ export { createRequestInterceptor, createResponseInterceptor, createSmartFetch };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { F as FolderFull } from './base-DbGnfZr6.js';
2
2
  export { A as ADMIN_ROLES, a as Admin, b as AdminCard, c as AdminFull, d as AdminRefreshToken, e as AdminRole, f as AdminSafe, g as AdminTranslation, h as Alternate, B as BaseTranslation, D as DeviceInfo, E as ExternalLink, i as FILE_TYPES, j as Faq, k as File, l as FileCard, m as FileFull, n as FileTranslation, o as FileType, p as Folder, M as MultiItems, P as POST_TYPES, q as Post, r as PostFull, s as PostListCard, t as PostTranslation, u as PostType, S as SeoMetadata, v as SingleItem, T as TocItem, w as Translation } from './base-DbGnfZr6.js';
3
3
  export { B as BlobFile } from './types-0oS1A2K5.js';
4
+ import { E as ErrorDetail, S as SuccessResult, a as ErrorResult } from './types-DHlRoJwv.js';
5
+ export { R as Result } from './types-DHlRoJwv.js';
4
6
 
5
7
  declare const ROOT_FOLDER_ID = "01ARZ3NDEKTSV4RRFFQ69G5FAV";
6
8
  declare const ROOT_FOLDER_NAME = "ROOT";
@@ -21,25 +23,6 @@ declare const getMediaInfo: (file: Blob) => Promise<MediaInfo>;
21
23
 
22
24
  declare const formatFileSize: (size: number, decimals?: number) => string;
23
25
 
24
- interface SuccessResult<D = unknown> {
25
- success: true;
26
- message?: string;
27
- data?: D;
28
- meta?: Record<string, unknown>;
29
- }
30
- interface ErrorResult {
31
- success: false;
32
- message?: string;
33
- errors?: ErrorDetail[];
34
- code?: string;
35
- }
36
- interface ErrorDetail {
37
- field?: string;
38
- message?: string;
39
- code?: string;
40
- }
41
- type Result<D = unknown> = SuccessResult<D> | ErrorResult;
42
-
43
26
  interface SuccessResultParams<D = unknown> {
44
27
  message?: string;
45
28
  data?: D;
@@ -61,4 +44,4 @@ declare const result: {
61
44
  error: typeof error;
62
45
  };
63
46
 
64
- export { type ErrorDetail, type ErrorResult, type ErrorResultParams, FolderFull, ROOT_FOLDER, ROOT_FOLDER_ID, ROOT_FOLDER_NAME, type Result, SIMPLE_UPLOAD_FOLDER_KEY, SIMPLE_UPLOAD_FOLDER_NAME, type SuccessResult, type SuccessResultParams, classifyFileType, formatFileSize, getMediaInfo, mimeToExtension, result };
47
+ export { ErrorDetail, ErrorResult, type ErrorResultParams, FolderFull, ROOT_FOLDER, ROOT_FOLDER_ID, ROOT_FOLDER_NAME, SIMPLE_UPLOAD_FOLDER_KEY, SIMPLE_UPLOAD_FOLDER_NAME, SuccessResult, type SuccessResultParams, classifyFileType, formatFileSize, getMediaInfo, mimeToExtension, result };
@@ -4,6 +4,8 @@ import { cookies } from 'next/headers';
4
4
  import Keyv from 'keyv';
5
5
  import { Logger } from 'logry';
6
6
  import { e as AdminRole, v as SingleItem, B as BaseTranslation, a as Admin, c as AdminFull, f as AdminSafe, D as DeviceInfo, d as AdminRefreshToken, k as File, m as FileFull, o as FileType, p as Folder, F as FolderFull, u as PostType, M as MultiItems, E as ExternalLink, j as Faq, T as TocItem, q as Post, s as PostListCard, t as PostTranslation, r as PostFull, S as SeoMetadata, g as AdminTranslation, n as FileTranslation, h as Alternate } from '../base-DbGnfZr6.js';
7
+ import { BaseTranslator, LocaleMessages } from 'intor';
8
+ import { S as SuccessResult, R as Result, E as ErrorDetail } from '../types-DHlRoJwv.js';
7
9
 
8
10
  interface CreateJwtServiceOptions {
9
11
  defaultSecret: string;
@@ -596,4 +598,37 @@ declare const POST_ORDER_BY: ({
596
598
  index: "asc";
597
599
  })[];
598
600
 
599
- export { ADMIN_ORDER_BY, ORDER_BY, POST_ORDER_BY, type RawCacheKey, createAdminCommandRepository, createAdminQueryRepository, createAdminRefreshTokenCommandRepository, createAdminRefreshTokenQueryRepository, createArgon2Service, createCache, createCacheResult, createCookieService, createCryptoService, createFileCommandRepository, createFileQueryRepository, createFolderCommandRepository, createFolderQueryRepository, createIpRateLimiter, createJwtService, createPostCommandRepository, createPostQueryRepository, createSeoMetadataCommandRepository, normalizeCacheKey };
601
+ type Action<D> = (translator: BaseTranslator<LocaleMessages>) => Promise<Omit<SuccessResult<D>, "success"> & {
602
+ i18nKey?: string;
603
+ }>;
604
+ interface CreateServerActionOptions {
605
+ initI18n: () => Promise<BaseTranslator<LocaleMessages>>;
606
+ cacheResult: <T>({ key, ttl, load }: CacheResultOptions<T>) => Promise<T>;
607
+ cache: Keyv<unknown>;
608
+ logger: Logger;
609
+ }
610
+ interface ServerActionOptions {
611
+ type?: "command" | "query";
612
+ key?: RawCacheKey;
613
+ ttl?: number;
614
+ }
615
+ declare function createExecuteAction({ initI18n, cacheResult, cache, logger, }: CreateServerActionOptions): Promise<(<D = void>(fn: Action<D>, options?: ServerActionOptions) => Promise<Result<D>>)>;
616
+
617
+ declare const normalizeError: (error: unknown, translator: BaseTranslator<LocaleMessages>) => {
618
+ message: string;
619
+ errors?: ErrorDetail[];
620
+ statusCode: number;
621
+ isInternal?: boolean;
622
+ };
623
+
624
+ declare class ServerError extends Error {
625
+ readonly i18nKey?: string;
626
+ readonly statusCode?: number;
627
+ constructor({ message, i18nKey, statusCode, }: {
628
+ message?: string;
629
+ i18nKey?: string;
630
+ statusCode?: number;
631
+ });
632
+ }
633
+
634
+ export { ADMIN_ORDER_BY, ORDER_BY, POST_ORDER_BY, type RawCacheKey, ServerError, createAdminCommandRepository, createAdminQueryRepository, createAdminRefreshTokenCommandRepository, createAdminRefreshTokenQueryRepository, createArgon2Service, createCache, createCacheResult, createCookieService, createCryptoService, createExecuteAction, createFileCommandRepository, createFileQueryRepository, createFolderCommandRepository, createFolderQueryRepository, createIpRateLimiter, createJwtService, createPostCommandRepository, createPostQueryRepository, createSeoMetadataCommandRepository, normalizeCacheKey, normalizeError };
@@ -1,5 +1,5 @@
1
1
  import { ADMIN_ROLES, ROOT_FOLDER_ID } from '../chunk-ZCOYQ5BG.js';
2
- import { mimeToExtension, classifyFileType } from '../chunk-YX7IPIGU.js';
2
+ import { result, mimeToExtension, classifyFileType } from '../chunk-YX7IPIGU.js';
3
3
  import jwt from 'jsonwebtoken';
4
4
  import argon2 from 'argon2';
5
5
  import crypto, { timingSafeEqual } from 'crypto';
@@ -7,6 +7,7 @@ import { headers } from 'next/headers';
7
7
  import KeyvRedis from '@keyv/redis';
8
8
  import Keyv from 'keyv';
9
9
  import { ulid } from 'ulid';
10
+ import { ZodError } from 'zod';
10
11
 
11
12
  function createJwtService({
12
13
  defaultSecret,
@@ -1421,4 +1422,84 @@ function createSeoMetadataCommandRepository(prisma) {
1421
1422
  };
1422
1423
  }
1423
1424
 
1424
- export { ADMIN_ORDER_BY, ORDER_BY, POST_ORDER_BY, createAdminCommandRepository, createAdminQueryRepository, createAdminRefreshTokenCommandRepository, createAdminRefreshTokenQueryRepository, createArgon2Service, createCache, createCacheResult, createCookieService, createCryptoService, createFileCommandRepository, createFileQueryRepository, createFolderCommandRepository, createFolderQueryRepository, createIpRateLimiter, createJwtService, createPostCommandRepository, createPostQueryRepository, createSeoMetadataCommandRepository, normalizeCacheKey };
1425
+ // src/server/server-error.ts
1426
+ var ServerError = class extends Error {
1427
+ i18nKey;
1428
+ statusCode;
1429
+ constructor({
1430
+ message,
1431
+ i18nKey,
1432
+ statusCode
1433
+ }) {
1434
+ super(message);
1435
+ this.name = this.constructor.name;
1436
+ if (i18nKey) this.i18nKey = i18nKey;
1437
+ if (statusCode) this.statusCode = statusCode;
1438
+ }
1439
+ };
1440
+
1441
+ // src/server/interfaces/execute-action/normalize-error.ts
1442
+ var normalizeError = (error, translator) => {
1443
+ if (error instanceof ZodError) {
1444
+ const errors = error.issues.map((issue) => {
1445
+ let message = issue.message;
1446
+ if (issue.code === "custom" && issue.params?.["i18nKey"]) {
1447
+ message = translator.t(issue.params["i18nKey"]);
1448
+ }
1449
+ return {
1450
+ field: issue.path.join("."),
1451
+ // e.path: string[] e.g. ["name", "email"]
1452
+ message,
1453
+ code: issue.code
1454
+ };
1455
+ });
1456
+ return { message: "Validation faild", errors, statusCode: 422 };
1457
+ }
1458
+ if (error instanceof ServerError) {
1459
+ const message = translator.t(
1460
+ error.i18nKey ?? "error.internal-server-error"
1461
+ );
1462
+ return { message, statusCode: error.statusCode ?? 500 };
1463
+ }
1464
+ return {
1465
+ message: error instanceof Error ? error.message : JSON.stringify(error),
1466
+ statusCode: 500,
1467
+ isInternal: true
1468
+ };
1469
+ };
1470
+
1471
+ // src/server/interfaces/execute-action/create-execute-action.ts
1472
+ async function createExecuteAction({
1473
+ initI18n,
1474
+ cacheResult,
1475
+ cache,
1476
+ logger
1477
+ }) {
1478
+ return async function executeAction(fn, options = {}) {
1479
+ const translator = await initI18n();
1480
+ const withCache = options.key && options.type === "query";
1481
+ try {
1482
+ const { data, i18nKey, message, meta } = withCache ? await cacheResult({
1483
+ key: options.key,
1484
+ ...options.ttl ? { ttl: options.ttl } : {},
1485
+ load: async () => fn(translator)
1486
+ }) : await fn(translator);
1487
+ if (options.type === "command") cache.clear();
1488
+ const finalMessage = i18nKey ? translator.t(i18nKey) : message;
1489
+ return result.success({
1490
+ ...finalMessage ? { message: finalMessage } : {},
1491
+ ...data ? { data: data ?? {} } : {},
1492
+ ...meta ? { meta } : {}
1493
+ });
1494
+ } catch (error) {
1495
+ const { message, errors, isInternal } = normalizeError(error, translator);
1496
+ logger.error({ message, errors });
1497
+ return result.error({
1498
+ message: isInternal ? "Internal server error" : message,
1499
+ ...errors !== void 0 ? { errors } : {}
1500
+ });
1501
+ }
1502
+ };
1503
+ }
1504
+
1505
+ export { ADMIN_ORDER_BY, ORDER_BY, POST_ORDER_BY, ServerError, createAdminCommandRepository, createAdminQueryRepository, createAdminRefreshTokenCommandRepository, createAdminRefreshTokenQueryRepository, createArgon2Service, createCache, createCacheResult, createCookieService, createCryptoService, createExecuteAction, createFileCommandRepository, createFileQueryRepository, createFolderCommandRepository, createFolderQueryRepository, createIpRateLimiter, createJwtService, createPostCommandRepository, createPostQueryRepository, createSeoMetadataCommandRepository, normalizeCacheKey, normalizeError };
@@ -0,0 +1,20 @@
1
+ interface SuccessResult<D = unknown> {
2
+ success: true;
3
+ message?: string;
4
+ data?: D;
5
+ meta?: Record<string, unknown>;
6
+ }
7
+ interface ErrorResult {
8
+ success: false;
9
+ message?: string;
10
+ errors?: ErrorDetail[];
11
+ code?: string;
12
+ }
13
+ interface ErrorDetail {
14
+ field?: string;
15
+ message?: string;
16
+ code?: string;
17
+ }
18
+ type Result<D = unknown> = SuccessResult<D> | ErrorResult;
19
+
20
+ export type { ErrorDetail as E, Result as R, SuccessResult as S, ErrorResult as a };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yimingliao/cms",
3
- "version": "0.0.20",
3
+ "version": "0.0.22",
4
4
  "author": "Yiming Liao",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,6 +10,11 @@
10
10
  "import": "./dist/index.js",
11
11
  "require": "./dist/index.js"
12
12
  },
13
+ "./client": {
14
+ "types": "./dist/client/index.d.ts",
15
+ "import": "./dist/client/index.js",
16
+ "require": "./dist/client/index.js"
17
+ },
13
18
  "./server": {
14
19
  "types": "./dist/server/index.d.ts",
15
20
  "import": "./dist/server/index.js",
@@ -51,10 +56,12 @@
51
56
  "@types/mime-types": "^3.0.1",
52
57
  "@types/ssh2-sftp-client": "^9.0.6",
53
58
  "generic-pool": "^3.9.0",
59
+ "intor": "^2.5.0",
54
60
  "next": "^16.1.6",
55
61
  "prisma": "6.5.0",
56
62
  "tsup": "^8.5.1",
57
- "typescript": "^5.9.3"
63
+ "typescript": "^5.9.3",
64
+ "zod": "^4.3.6"
58
65
  },
59
66
  "peerDependencies": {
60
67
  "@aws-sdk/client-s3": "^3.0.0",