mock-fried 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/README.md +229 -0
- package/dist/module.d.mts +125 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +160 -0
- package/dist/runtime/components/ApiExplorer.d.vue.ts +7 -0
- package/dist/runtime/components/ApiExplorer.vue +168 -0
- package/dist/runtime/components/ApiExplorer.vue.d.ts +7 -0
- package/dist/runtime/components/EndpointCard.d.vue.ts +24 -0
- package/dist/runtime/components/EndpointCard.vue +173 -0
- package/dist/runtime/components/EndpointCard.vue.d.ts +24 -0
- package/dist/runtime/components/ResponseViewer.d.vue.ts +16 -0
- package/dist/runtime/components/ResponseViewer.vue +78 -0
- package/dist/runtime/components/ResponseViewer.vue.d.ts +16 -0
- package/dist/runtime/components/RpcMethodCard.d.vue.ts +20 -0
- package/dist/runtime/components/RpcMethodCard.vue +129 -0
- package/dist/runtime/components/RpcMethodCard.vue.d.ts +20 -0
- package/dist/runtime/composables/index.d.ts +1 -0
- package/dist/runtime/composables/index.js +1 -0
- package/dist/runtime/composables/useApi.d.ts +19 -0
- package/dist/runtime/composables/useApi.js +5 -0
- package/dist/runtime/plugin.d.ts +7 -0
- package/dist/runtime/plugin.js +75 -0
- package/dist/runtime/server/handlers/openapi.d.ts +2 -0
- package/dist/runtime/server/handlers/openapi.js +346 -0
- package/dist/runtime/server/handlers/rpc.d.ts +7 -0
- package/dist/runtime/server/handlers/rpc.js +140 -0
- package/dist/runtime/server/handlers/schema.d.ts +7 -0
- package/dist/runtime/server/handlers/schema.js +190 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/server/utils/client-parser.d.ts +13 -0
- package/dist/runtime/server/utils/client-parser.js +272 -0
- package/dist/runtime/server/utils/mock/client-generator.d.ts +108 -0
- package/dist/runtime/server/utils/mock/client-generator.js +346 -0
- package/dist/runtime/server/utils/mock/index.d.ts +9 -0
- package/dist/runtime/server/utils/mock/index.js +38 -0
- package/dist/runtime/server/utils/mock/openapi-generator.d.ts +4 -0
- package/dist/runtime/server/utils/mock/openapi-generator.js +118 -0
- package/dist/runtime/server/utils/mock/pagination/cursor-manager.d.ts +38 -0
- package/dist/runtime/server/utils/mock/pagination/cursor-manager.js +129 -0
- package/dist/runtime/server/utils/mock/pagination/index.d.ts +8 -0
- package/dist/runtime/server/utils/mock/pagination/index.js +18 -0
- package/dist/runtime/server/utils/mock/pagination/page-manager.d.ts +41 -0
- package/dist/runtime/server/utils/mock/pagination/page-manager.js +96 -0
- package/dist/runtime/server/utils/mock/pagination/snapshot-store.d.ts +64 -0
- package/dist/runtime/server/utils/mock/pagination/snapshot-store.js +125 -0
- package/dist/runtime/server/utils/mock/pagination/types.d.ts +141 -0
- package/dist/runtime/server/utils/mock/pagination/types.js +14 -0
- package/dist/runtime/server/utils/mock/proto-generator.d.ts +12 -0
- package/dist/runtime/server/utils/mock/proto-generator.js +67 -0
- package/dist/runtime/server/utils/mock/shared.d.ts +69 -0
- package/dist/runtime/server/utils/mock/shared.js +150 -0
- package/dist/runtime/server/utils/mock-generator.d.ts +9 -0
- package/dist/runtime/server/utils/mock-generator.js +30 -0
- package/dist/types.d.mts +9 -0
- package/package.json +73 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pagination 모듈 진입점
|
|
3
|
+
*/
|
|
4
|
+
export type { CursorPayload, PaginationSnapshot, PagePaginationOptions, PagePaginationResult, CursorPaginationOptions, CursorPaginationResult, PaginationConfig, CursorConfig, } from './types.js';
|
|
5
|
+
export { DEFAULT_PAGINATION_CONFIG, DEFAULT_CURSOR_CONFIG, } from './types.js';
|
|
6
|
+
export { SnapshotStore, getSnapshotStore, resetSnapshotStore, } from './snapshot-store.js';
|
|
7
|
+
export { CursorPaginationManager, encodeCursor, decodeCursor, isCursorExpired, } from './cursor-manager.js';
|
|
8
|
+
export { PagePaginationManager, } from './page-manager.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export {
|
|
2
|
+
DEFAULT_PAGINATION_CONFIG,
|
|
3
|
+
DEFAULT_CURSOR_CONFIG
|
|
4
|
+
} from "./types.js";
|
|
5
|
+
export {
|
|
6
|
+
SnapshotStore,
|
|
7
|
+
getSnapshotStore,
|
|
8
|
+
resetSnapshotStore
|
|
9
|
+
} from "./snapshot-store.js";
|
|
10
|
+
export {
|
|
11
|
+
CursorPaginationManager,
|
|
12
|
+
encodeCursor,
|
|
13
|
+
decodeCursor,
|
|
14
|
+
isCursorExpired
|
|
15
|
+
} from "./cursor-manager.js";
|
|
16
|
+
export {
|
|
17
|
+
PagePaginationManager
|
|
18
|
+
} from "./page-manager.js";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page/Limit 기반 Pagination 관리자
|
|
3
|
+
* 스냅샷 기반의 일관된 페이지네이션을 제공
|
|
4
|
+
*/
|
|
5
|
+
import type { PagePaginationOptions, PagePaginationResult, PaginationConfig } from './types.js';
|
|
6
|
+
import type { SnapshotStore } from './snapshot-store.js';
|
|
7
|
+
import type { SchemaMockGenerator } from '../client-generator.js';
|
|
8
|
+
/**
|
|
9
|
+
* Page 기반 Pagination 관리자 클래스
|
|
10
|
+
*/
|
|
11
|
+
export declare class PagePaginationManager {
|
|
12
|
+
private generator;
|
|
13
|
+
private snapshotStore;
|
|
14
|
+
private config;
|
|
15
|
+
constructor(generator: SchemaMockGenerator, options?: {
|
|
16
|
+
snapshotStore?: SnapshotStore;
|
|
17
|
+
config?: PaginationConfig;
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Page 기반 페이지 조회
|
|
21
|
+
*/
|
|
22
|
+
getPagedResponse(modelName: string, options?: PagePaginationOptions): PagePaginationResult<Record<string, unknown>>;
|
|
23
|
+
/**
|
|
24
|
+
* Offset 기반 페이지 조회
|
|
25
|
+
*/
|
|
26
|
+
getOffsetResponse(modelName: string, options?: {
|
|
27
|
+
offset?: number;
|
|
28
|
+
limit?: number;
|
|
29
|
+
total?: number;
|
|
30
|
+
seed?: string;
|
|
31
|
+
snapshotId?: string;
|
|
32
|
+
cache?: boolean;
|
|
33
|
+
ttl?: number;
|
|
34
|
+
}): {
|
|
35
|
+
items: Record<string, unknown>[];
|
|
36
|
+
offset: number;
|
|
37
|
+
limit: number;
|
|
38
|
+
total: number;
|
|
39
|
+
_snapshotId?: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { DEFAULT_PAGINATION_CONFIG } from "./types.js";
|
|
2
|
+
import { getSnapshotStore } from "./snapshot-store.js";
|
|
3
|
+
export class PagePaginationManager {
|
|
4
|
+
generator;
|
|
5
|
+
snapshotStore;
|
|
6
|
+
config;
|
|
7
|
+
constructor(generator, options) {
|
|
8
|
+
this.generator = generator;
|
|
9
|
+
this.snapshotStore = options?.snapshotStore ?? getSnapshotStore();
|
|
10
|
+
this.config = { ...DEFAULT_PAGINATION_CONFIG, ...options?.config };
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Page 기반 페이지 조회
|
|
14
|
+
*/
|
|
15
|
+
getPagedResponse(modelName, options = {}) {
|
|
16
|
+
const {
|
|
17
|
+
page = 1,
|
|
18
|
+
limit = this.config.defaultLimit,
|
|
19
|
+
total = this.config.defaultTotal,
|
|
20
|
+
seed = modelName,
|
|
21
|
+
snapshotId,
|
|
22
|
+
cache = this.config.cache,
|
|
23
|
+
ttl = this.config.cacheTTL
|
|
24
|
+
} = options;
|
|
25
|
+
let snapshot;
|
|
26
|
+
if (snapshotId) {
|
|
27
|
+
const existing = this.snapshotStore.getById(snapshotId);
|
|
28
|
+
if (existing) {
|
|
29
|
+
snapshot = existing;
|
|
30
|
+
} else {
|
|
31
|
+
snapshot = this.snapshotStore.getOrCreate(modelName, seed, total, { cache, ttl });
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
snapshot = this.snapshotStore.getOrCreate(modelName, seed, total, { cache, ttl });
|
|
35
|
+
}
|
|
36
|
+
const startIndex = (page - 1) * limit;
|
|
37
|
+
const endIndex = Math.min(startIndex + limit, snapshot.total);
|
|
38
|
+
const pageItemIds = snapshot.itemIds.slice(startIndex, endIndex);
|
|
39
|
+
const items = pageItemIds.map(
|
|
40
|
+
(itemId, i) => this.generator.generateOneWithId(modelName, itemId, `${seed}-${itemId}`, startIndex + i)
|
|
41
|
+
);
|
|
42
|
+
const result = {
|
|
43
|
+
items,
|
|
44
|
+
pagination: {
|
|
45
|
+
page,
|
|
46
|
+
limit,
|
|
47
|
+
total: snapshot.total,
|
|
48
|
+
totalPages: Math.ceil(snapshot.total / limit)
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
if (this.config.includeSnapshotId) {
|
|
52
|
+
result._snapshotId = snapshot.id;
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Offset 기반 페이지 조회
|
|
58
|
+
*/
|
|
59
|
+
getOffsetResponse(modelName, options = {}) {
|
|
60
|
+
const {
|
|
61
|
+
offset = 0,
|
|
62
|
+
limit = this.config.defaultLimit,
|
|
63
|
+
total = this.config.defaultTotal,
|
|
64
|
+
seed = modelName,
|
|
65
|
+
snapshotId,
|
|
66
|
+
cache = this.config.cache,
|
|
67
|
+
ttl = this.config.cacheTTL
|
|
68
|
+
} = options;
|
|
69
|
+
let snapshot;
|
|
70
|
+
if (snapshotId) {
|
|
71
|
+
const existing = this.snapshotStore.getById(snapshotId);
|
|
72
|
+
if (existing) {
|
|
73
|
+
snapshot = existing;
|
|
74
|
+
} else {
|
|
75
|
+
snapshot = this.snapshotStore.getOrCreate(modelName, seed, total, { cache, ttl });
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
snapshot = this.snapshotStore.getOrCreate(modelName, seed, total, { cache, ttl });
|
|
79
|
+
}
|
|
80
|
+
const endIndex = Math.min(offset + limit, snapshot.total);
|
|
81
|
+
const pageItemIds = snapshot.itemIds.slice(offset, endIndex);
|
|
82
|
+
const items = pageItemIds.map(
|
|
83
|
+
(itemId, i) => this.generator.generateOneWithId(modelName, itemId, `${seed}-${itemId}`, offset + i)
|
|
84
|
+
);
|
|
85
|
+
const result = {
|
|
86
|
+
items,
|
|
87
|
+
offset,
|
|
88
|
+
limit,
|
|
89
|
+
total: snapshot.total
|
|
90
|
+
};
|
|
91
|
+
if (this.config.includeSnapshotId) {
|
|
92
|
+
result._snapshotId = snapshot.id;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pagination 스냅샷 저장소
|
|
3
|
+
* 일관된 페이지네이션을 위한 데이터 스냅샷 관리
|
|
4
|
+
*/
|
|
5
|
+
import type { MockIdConfig } from '../../../../../types.js';
|
|
6
|
+
import type { PaginationSnapshot, PaginationConfig } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* 스냅샷 저장소 클래스
|
|
9
|
+
*/
|
|
10
|
+
export declare class SnapshotStore {
|
|
11
|
+
private snapshots;
|
|
12
|
+
private config;
|
|
13
|
+
private idConfig;
|
|
14
|
+
private cleanupInterval;
|
|
15
|
+
constructor(config?: PaginationConfig, idConfig?: MockIdConfig);
|
|
16
|
+
/**
|
|
17
|
+
* 스냅샷 키 생성
|
|
18
|
+
*/
|
|
19
|
+
private getKey;
|
|
20
|
+
/**
|
|
21
|
+
* 아이템 ID 목록 생성 (MockIdConfig 기반)
|
|
22
|
+
* 실제 응답에서 사용될 ID와 동일한 값을 생성
|
|
23
|
+
*/
|
|
24
|
+
private generateItemIds;
|
|
25
|
+
/**
|
|
26
|
+
* 스냅샷 가져오기 또는 생성
|
|
27
|
+
*/
|
|
28
|
+
getOrCreate(modelName: string, seed: string, total: number, options?: {
|
|
29
|
+
ttl?: number;
|
|
30
|
+
cache?: boolean;
|
|
31
|
+
}): PaginationSnapshot;
|
|
32
|
+
/**
|
|
33
|
+
* 스냅샷 ID로 조회
|
|
34
|
+
*/
|
|
35
|
+
getById(snapshotId: string): PaginationSnapshot | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* 스냅샷 만료 여부 확인
|
|
38
|
+
*/
|
|
39
|
+
private isExpired;
|
|
40
|
+
/**
|
|
41
|
+
* 만료된 스냅샷 정리
|
|
42
|
+
*/
|
|
43
|
+
cleanup(): number;
|
|
44
|
+
/**
|
|
45
|
+
* 모든 스냅샷 삭제
|
|
46
|
+
*/
|
|
47
|
+
clear(): void;
|
|
48
|
+
/**
|
|
49
|
+
* 스냅샷 수 조회
|
|
50
|
+
*/
|
|
51
|
+
size(): number;
|
|
52
|
+
/**
|
|
53
|
+
* 정리 인터벌 중지
|
|
54
|
+
*/
|
|
55
|
+
destroy(): void;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 기본 스냅샷 저장소 가져오기
|
|
59
|
+
*/
|
|
60
|
+
export declare function getSnapshotStore(config?: PaginationConfig, idConfig?: MockIdConfig): SnapshotStore;
|
|
61
|
+
/**
|
|
62
|
+
* 기본 스냅샷 저장소 초기화
|
|
63
|
+
*/
|
|
64
|
+
export declare function resetSnapshotStore(): void;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { DEFAULT_PAGINATION_CONFIG } from "./types.js";
|
|
2
|
+
import { generateSnapshotId, generateIdValue, DEFAULT_ID_CONFIG } from "../shared.js";
|
|
3
|
+
export class SnapshotStore {
|
|
4
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
5
|
+
config;
|
|
6
|
+
idConfig;
|
|
7
|
+
cleanupInterval = null;
|
|
8
|
+
constructor(config, idConfig) {
|
|
9
|
+
this.config = { ...DEFAULT_PAGINATION_CONFIG, ...config };
|
|
10
|
+
this.idConfig = idConfig ?? DEFAULT_ID_CONFIG;
|
|
11
|
+
if (typeof setInterval !== "undefined") {
|
|
12
|
+
this.cleanupInterval = setInterval(() => this.cleanup(), 5 * 60 * 1e3);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 스냅샷 키 생성
|
|
17
|
+
*/
|
|
18
|
+
getKey(modelName, seed) {
|
|
19
|
+
return `${modelName}:${seed}`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 아이템 ID 목록 생성 (MockIdConfig 기반)
|
|
23
|
+
* 실제 응답에서 사용될 ID와 동일한 값을 생성
|
|
24
|
+
*/
|
|
25
|
+
generateItemIds(total, seed, modelName) {
|
|
26
|
+
return Array.from({ length: total }, (_, i) => {
|
|
27
|
+
const id = generateIdValue("id", i, `${seed}-${modelName}-${i}`, this.idConfig);
|
|
28
|
+
return String(id);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 스냅샷 가져오기 또는 생성
|
|
33
|
+
*/
|
|
34
|
+
getOrCreate(modelName, seed, total, options) {
|
|
35
|
+
const key = this.getKey(modelName, seed);
|
|
36
|
+
const shouldCache = options?.cache ?? this.config.cache;
|
|
37
|
+
const existing = this.snapshots.get(key);
|
|
38
|
+
if (existing && !this.isExpired(existing)) {
|
|
39
|
+
existing.accessedAt = Date.now();
|
|
40
|
+
return existing;
|
|
41
|
+
}
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
const ttl = options?.ttl ?? this.config.cacheTTL;
|
|
44
|
+
const snapshot = {
|
|
45
|
+
id: generateSnapshotId(),
|
|
46
|
+
modelName,
|
|
47
|
+
seed,
|
|
48
|
+
total,
|
|
49
|
+
itemIds: this.generateItemIds(total, seed, modelName),
|
|
50
|
+
createdAt: now,
|
|
51
|
+
expiresAt: shouldCache ? now + ttl : void 0,
|
|
52
|
+
accessedAt: now
|
|
53
|
+
};
|
|
54
|
+
if (shouldCache) {
|
|
55
|
+
this.snapshots.set(key, snapshot);
|
|
56
|
+
}
|
|
57
|
+
return snapshot;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 스냅샷 ID로 조회
|
|
61
|
+
*/
|
|
62
|
+
getById(snapshotId) {
|
|
63
|
+
for (const snapshot of this.snapshots.values()) {
|
|
64
|
+
if (snapshot.id === snapshotId && !this.isExpired(snapshot)) {
|
|
65
|
+
snapshot.accessedAt = Date.now();
|
|
66
|
+
return snapshot;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 스냅샷 만료 여부 확인
|
|
73
|
+
*/
|
|
74
|
+
isExpired(snapshot) {
|
|
75
|
+
return snapshot.expiresAt !== void 0 && Date.now() > snapshot.expiresAt;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 만료된 스냅샷 정리
|
|
79
|
+
*/
|
|
80
|
+
cleanup() {
|
|
81
|
+
let cleaned = 0;
|
|
82
|
+
for (const [key, snapshot] of this.snapshots) {
|
|
83
|
+
if (this.isExpired(snapshot)) {
|
|
84
|
+
this.snapshots.delete(key);
|
|
85
|
+
cleaned++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return cleaned;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 모든 스냅샷 삭제
|
|
92
|
+
*/
|
|
93
|
+
clear() {
|
|
94
|
+
this.snapshots.clear();
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 스냅샷 수 조회
|
|
98
|
+
*/
|
|
99
|
+
size() {
|
|
100
|
+
return this.snapshots.size;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 정리 인터벌 중지
|
|
104
|
+
*/
|
|
105
|
+
destroy() {
|
|
106
|
+
if (this.cleanupInterval) {
|
|
107
|
+
clearInterval(this.cleanupInterval);
|
|
108
|
+
this.cleanupInterval = null;
|
|
109
|
+
}
|
|
110
|
+
this.clear();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
let defaultStore = null;
|
|
114
|
+
export function getSnapshotStore(config, idConfig) {
|
|
115
|
+
if (!defaultStore) {
|
|
116
|
+
defaultStore = new SnapshotStore(config, idConfig);
|
|
117
|
+
}
|
|
118
|
+
return defaultStore;
|
|
119
|
+
}
|
|
120
|
+
export function resetSnapshotStore() {
|
|
121
|
+
if (defaultStore) {
|
|
122
|
+
defaultStore.destroy();
|
|
123
|
+
defaultStore = null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pagination 관련 타입 정의
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Cursor 페이로드 - 연결성 있는 cursor 데이터
|
|
6
|
+
*/
|
|
7
|
+
export interface CursorPayload {
|
|
8
|
+
/** 마지막 아이템 ID (anchor) */
|
|
9
|
+
lastId: string;
|
|
10
|
+
/** 방향: forward (다음) | backward (이전) */
|
|
11
|
+
direction: 'forward' | 'backward';
|
|
12
|
+
/** 스냅샷 ID (옵션) */
|
|
13
|
+
snapshotId?: string;
|
|
14
|
+
/** 생성 시간 (만료 체크용) */
|
|
15
|
+
timestamp: number;
|
|
16
|
+
/** 정렬 필드 (옵션) */
|
|
17
|
+
sortField?: string;
|
|
18
|
+
/** 정렬 순서 (옵션) */
|
|
19
|
+
sortOrder?: 'asc' | 'desc';
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Pagination 스냅샷 - 일관된 페이지네이션을 위한 데이터 스냅샷
|
|
23
|
+
*/
|
|
24
|
+
export interface PaginationSnapshot {
|
|
25
|
+
/** 스냅샷 고유 ID */
|
|
26
|
+
id: string;
|
|
27
|
+
/** 모델명 */
|
|
28
|
+
modelName: string;
|
|
29
|
+
/** 생성 seed */
|
|
30
|
+
seed: string;
|
|
31
|
+
/** 총 아이템 수 */
|
|
32
|
+
total: number;
|
|
33
|
+
/** 아이템 ID 목록 (순서 보장) */
|
|
34
|
+
itemIds: string[];
|
|
35
|
+
/** 생성 시간 */
|
|
36
|
+
createdAt: number;
|
|
37
|
+
/** 만료 시간 (옵션) */
|
|
38
|
+
expiresAt?: number;
|
|
39
|
+
/** 마지막 접근 시간 */
|
|
40
|
+
accessedAt: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Page 기반 Pagination 옵션
|
|
44
|
+
*/
|
|
45
|
+
export interface PagePaginationOptions {
|
|
46
|
+
/** 페이지 번호 (1-based) */
|
|
47
|
+
page?: number;
|
|
48
|
+
/** 페이지 크기 */
|
|
49
|
+
limit?: number;
|
|
50
|
+
/** 총 아이템 수 */
|
|
51
|
+
total?: number;
|
|
52
|
+
/** 생성 seed */
|
|
53
|
+
seed?: string;
|
|
54
|
+
/** 스냅샷 ID (일관성 유지용) */
|
|
55
|
+
snapshotId?: string;
|
|
56
|
+
/** 캐싱 활성화 */
|
|
57
|
+
cache?: boolean;
|
|
58
|
+
/** 캐시 TTL (ms) */
|
|
59
|
+
ttl?: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Page 기반 Pagination 결과
|
|
63
|
+
*/
|
|
64
|
+
export interface PagePaginationResult<T> {
|
|
65
|
+
/** 아이템 목록 */
|
|
66
|
+
items: T[];
|
|
67
|
+
/** Pagination 메타데이터 */
|
|
68
|
+
pagination: {
|
|
69
|
+
page: number;
|
|
70
|
+
limit: number;
|
|
71
|
+
total: number;
|
|
72
|
+
totalPages: number;
|
|
73
|
+
};
|
|
74
|
+
/** 스냅샷 ID (클라이언트 전달용) */
|
|
75
|
+
_snapshotId?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Cursor 기반 Pagination 옵션
|
|
79
|
+
*/
|
|
80
|
+
export interface CursorPaginationOptions {
|
|
81
|
+
/** Cursor 문자열 */
|
|
82
|
+
cursor?: string;
|
|
83
|
+
/** 페이지 크기 */
|
|
84
|
+
limit?: number;
|
|
85
|
+
/** 총 아이템 수 */
|
|
86
|
+
total?: number;
|
|
87
|
+
/** 생성 seed */
|
|
88
|
+
seed?: string;
|
|
89
|
+
/** 스냅샷 ID (일관성 유지용) */
|
|
90
|
+
snapshotId?: string;
|
|
91
|
+
/** 캐싱 활성화 */
|
|
92
|
+
cache?: boolean;
|
|
93
|
+
/** 캐시 TTL (ms) */
|
|
94
|
+
ttl?: number;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Cursor 기반 Pagination 결과
|
|
98
|
+
*/
|
|
99
|
+
export interface CursorPaginationResult<T> {
|
|
100
|
+
/** 아이템 목록 */
|
|
101
|
+
items: T[];
|
|
102
|
+
/** 다음 페이지 cursor */
|
|
103
|
+
nextCursor?: string;
|
|
104
|
+
/** 이전 페이지 cursor */
|
|
105
|
+
prevCursor?: string;
|
|
106
|
+
/** 더 많은 데이터 존재 여부 */
|
|
107
|
+
hasMore: boolean;
|
|
108
|
+
/** 스냅샷 ID (클라이언트 전달용) */
|
|
109
|
+
_snapshotId?: string;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Pagination 설정
|
|
113
|
+
*/
|
|
114
|
+
export interface PaginationConfig {
|
|
115
|
+
/** 캐싱 활성화 (기본: true) */
|
|
116
|
+
cache?: boolean;
|
|
117
|
+
/** 캐시 TTL ms (기본: 1800000 = 30분) */
|
|
118
|
+
cacheTTL?: number;
|
|
119
|
+
/** 기본 총 아이템 수 (기본: 100) */
|
|
120
|
+
defaultTotal?: number;
|
|
121
|
+
/** 기본 페이지 크기 (기본: 20) */
|
|
122
|
+
defaultLimit?: number;
|
|
123
|
+
/** 응답에 snapshotId 포함 (기본: false) */
|
|
124
|
+
includeSnapshotId?: boolean;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Cursor 설정
|
|
128
|
+
*/
|
|
129
|
+
export interface CursorConfig {
|
|
130
|
+
/** 만료 활성화 (기본: true) */
|
|
131
|
+
enableExpiry?: boolean;
|
|
132
|
+
/** Cursor TTL ms (기본: 3600000 = 1시간) */
|
|
133
|
+
cursorTTL?: number;
|
|
134
|
+
/** 정렬 정보 포함 (기본: false) */
|
|
135
|
+
includeSortInfo?: boolean;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 기본 설정값
|
|
139
|
+
*/
|
|
140
|
+
export declare const DEFAULT_PAGINATION_CONFIG: Required<PaginationConfig>;
|
|
141
|
+
export declare const DEFAULT_CURSOR_CONFIG: Required<CursorConfig>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const DEFAULT_PAGINATION_CONFIG = {
|
|
2
|
+
cache: true,
|
|
3
|
+
cacheTTL: 30 * 60 * 1e3,
|
|
4
|
+
// 30분
|
|
5
|
+
defaultTotal: 100,
|
|
6
|
+
defaultLimit: 20,
|
|
7
|
+
includeSnapshotId: false
|
|
8
|
+
};
|
|
9
|
+
export const DEFAULT_CURSOR_CONFIG = {
|
|
10
|
+
enableExpiry: true,
|
|
11
|
+
cursorTTL: 60 * 60 * 1e3,
|
|
12
|
+
// 1시간
|
|
13
|
+
includeSortInfo: false
|
|
14
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proto 필드 타입에 따른 mock 값 생성
|
|
3
|
+
*/
|
|
4
|
+
export declare function generateMockValueForProtoField(fieldName: string, fieldType: string, seed?: number): unknown;
|
|
5
|
+
/**
|
|
6
|
+
* Proto 메시지 타입에서 mock 객체 생성
|
|
7
|
+
*/
|
|
8
|
+
export declare function generateMockMessage(messageType: Record<string, unknown>, seed?: number): Record<string, unknown>;
|
|
9
|
+
/**
|
|
10
|
+
* 요청 객체에서 seed 추출
|
|
11
|
+
*/
|
|
12
|
+
export declare function deriveSeedFromRequest(request: unknown): number;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { hashString, seededRandom } from "./shared.js";
|
|
2
|
+
export function generateMockValueForProtoField(fieldName, fieldType, seed = 1) {
|
|
3
|
+
const fieldSeed = hashString(fieldName) + seed;
|
|
4
|
+
const random = seededRandom(fieldSeed);
|
|
5
|
+
switch (fieldType.toLowerCase()) {
|
|
6
|
+
case "string":
|
|
7
|
+
return `${fieldName}_${Math.floor(random() * 1e3)}`;
|
|
8
|
+
case "int32":
|
|
9
|
+
case "sint32":
|
|
10
|
+
case "sfixed32":
|
|
11
|
+
return Math.floor(random() * 1e4);
|
|
12
|
+
case "int64":
|
|
13
|
+
case "sint64":
|
|
14
|
+
case "sfixed64":
|
|
15
|
+
return String(Math.floor(random() * 1e7));
|
|
16
|
+
case "uint32":
|
|
17
|
+
case "fixed32":
|
|
18
|
+
return Math.floor(random() * 1e4);
|
|
19
|
+
case "uint64":
|
|
20
|
+
case "fixed64":
|
|
21
|
+
return String(Math.floor(random() * 1e7));
|
|
22
|
+
case "float":
|
|
23
|
+
case "double":
|
|
24
|
+
return Math.round(random() * 1e4) / 100;
|
|
25
|
+
case "bool":
|
|
26
|
+
return random() > 0.5;
|
|
27
|
+
case "bytes":
|
|
28
|
+
return Buffer.from(`mock_bytes_${fieldSeed}`).toString("base64");
|
|
29
|
+
default:
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function generateMockMessage(messageType, seed = 1) {
|
|
34
|
+
const result = {};
|
|
35
|
+
const fields = messageType.fields || {};
|
|
36
|
+
let currentSeed = seed;
|
|
37
|
+
for (const [fieldName, fieldDef] of Object.entries(fields)) {
|
|
38
|
+
const field = fieldDef;
|
|
39
|
+
let value;
|
|
40
|
+
if (field.resolvedType && typeof field.resolvedType === "object") {
|
|
41
|
+
const resolvedType = field.resolvedType;
|
|
42
|
+
if (resolvedType.values) {
|
|
43
|
+
const enumValues = Object.keys(resolvedType.values);
|
|
44
|
+
value = enumValues[0] || "UNKNOWN";
|
|
45
|
+
} else if (resolvedType.fields) {
|
|
46
|
+
value = generateMockMessage(resolvedType, currentSeed);
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
value = generateMockValueForProtoField(fieldName, field.type || "string", currentSeed);
|
|
50
|
+
}
|
|
51
|
+
if (field.rule === "repeated") {
|
|
52
|
+
value = [value];
|
|
53
|
+
}
|
|
54
|
+
if (field.keyType) {
|
|
55
|
+
const keyValue = field.keyType === "string" ? `key_${currentSeed}` : currentSeed;
|
|
56
|
+
value = { [keyValue]: value };
|
|
57
|
+
}
|
|
58
|
+
result[fieldName] = value;
|
|
59
|
+
currentSeed++;
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
export function deriveSeedFromRequest(request) {
|
|
64
|
+
if (!request) return 42;
|
|
65
|
+
const str = JSON.stringify(request);
|
|
66
|
+
return hashString(str) || 42;
|
|
67
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 공통 Mock 생성 유틸리티
|
|
3
|
+
* Proto와 OpenAPI 모두에서 사용하는 기본 함수들
|
|
4
|
+
*/
|
|
5
|
+
import type { MockIdConfig, MockIdFormat } from '../../../../types.js';
|
|
6
|
+
/**
|
|
7
|
+
* 문자열에서 해시값 생성
|
|
8
|
+
*/
|
|
9
|
+
export declare function hashString(str: string): number;
|
|
10
|
+
/**
|
|
11
|
+
* seed 기반으로 결정론적 난수 생성 (간단한 LCG)
|
|
12
|
+
*/
|
|
13
|
+
export declare function seededRandom(seed: number): () => number;
|
|
14
|
+
/**
|
|
15
|
+
* 시드 기반 난수 생성기 클래스
|
|
16
|
+
* OOP 스타일로 사용할 수 있는 래퍼
|
|
17
|
+
*/
|
|
18
|
+
export declare class SeededRandom {
|
|
19
|
+
private seed;
|
|
20
|
+
constructor(seed: number | string);
|
|
21
|
+
next(): number;
|
|
22
|
+
nextInt(min: number, max: number): number;
|
|
23
|
+
pick<T>(array: T[]): T;
|
|
24
|
+
/**
|
|
25
|
+
* 주어진 확률로 true 반환
|
|
26
|
+
*/
|
|
27
|
+
chance(probability: number): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* UUID v4 형식 문자열 생성 (결정론적)
|
|
30
|
+
*/
|
|
31
|
+
uuid(): string;
|
|
32
|
+
/**
|
|
33
|
+
* ULID 형식 문자열 생성 (결정론적)
|
|
34
|
+
* 26자리 Base32 인코딩 (Crockford's Base32)
|
|
35
|
+
*/
|
|
36
|
+
ulid(): string;
|
|
37
|
+
/**
|
|
38
|
+
* NanoID 형식 문자열 생성 (결정론적, 21자리)
|
|
39
|
+
*/
|
|
40
|
+
nanoid(size?: number): string;
|
|
41
|
+
/**
|
|
42
|
+
* 짧은 해시 ID 생성 (결정론적, 8자리)
|
|
43
|
+
*/
|
|
44
|
+
hashId(size?: number): string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 고유 ID 생성 (결정론적)
|
|
48
|
+
*/
|
|
49
|
+
export declare function generateId(prefix: string, seed: number | string): string;
|
|
50
|
+
/**
|
|
51
|
+
* 스냅샷 ID 생성
|
|
52
|
+
*/
|
|
53
|
+
export declare function generateSnapshotId(): string;
|
|
54
|
+
/**
|
|
55
|
+
* 기본 ID 설정
|
|
56
|
+
*/
|
|
57
|
+
export declare const DEFAULT_ID_CONFIG: MockIdConfig;
|
|
58
|
+
/**
|
|
59
|
+
* ID 필드 여부 확인
|
|
60
|
+
*/
|
|
61
|
+
export declare function isIdField(fieldName: string, config?: MockIdConfig): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* ID 값 생성
|
|
64
|
+
*/
|
|
65
|
+
export declare function generateIdValue(fieldName: string, index: number, seed: string, config?: MockIdConfig): string | number;
|
|
66
|
+
/**
|
|
67
|
+
* 포맷별 ID 생성
|
|
68
|
+
*/
|
|
69
|
+
export declare function generateByFormat(format: MockIdFormat, index: number, rng: SeededRandom, prefix?: string): string | number;
|