@gencow/core 0.1.15 → 0.1.17
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/crud.d.ts +12 -5
- package/dist/crud.js +20 -12
- package/dist/reactive.d.ts +17 -6
- package/dist/reactive.js +19 -3
- package/package.json +1 -1
- package/src/crud.ts +28 -12
- package/src/reactive.ts +29 -7
package/dist/crud.d.ts
CHANGED
|
@@ -48,6 +48,13 @@ type CrudOptions<T extends PgTable> = {
|
|
|
48
48
|
realtime?: boolean;
|
|
49
49
|
/** 키 접두사 오버라이드 (기본: 테이블명) */
|
|
50
50
|
prefix?: string;
|
|
51
|
+
/**
|
|
52
|
+
* 생성할 메서드 목록 (기본: 전체 5개)
|
|
53
|
+
* 지정하면 해당 메서드만 레지스트리에 등록되고 반환됨.
|
|
54
|
+
* api.ts codegen에도 지정된 메서드만 포함됨.
|
|
55
|
+
* @example crud(table, { methods: ['list', 'get', 'create'] })
|
|
56
|
+
*/
|
|
57
|
+
methods?: ('list' | 'get' | 'create' | 'update' | 'remove')[];
|
|
51
58
|
};
|
|
52
59
|
/** 지원 연산자 목록 */
|
|
53
60
|
declare const FILTER_OPS: readonly ["eq", "ne", "gt", "gte", "lt", "lte", "in", "nin", "like", "ilike"];
|
|
@@ -103,14 +110,14 @@ export declare function crud<T extends PgTable>(table: T, options?: CrudOptions<
|
|
|
103
110
|
}, {
|
|
104
111
|
data: any;
|
|
105
112
|
total: number;
|
|
106
|
-
}
|
|
113
|
+
}> | undefined;
|
|
107
114
|
get: import("./reactive").QueryDef<{
|
|
108
115
|
id: import("./v").Validator<string> | import("./v").Validator<number>;
|
|
109
|
-
}, any
|
|
110
|
-
create: import("./reactive").MutationDef<any, any
|
|
111
|
-
update: import("./reactive").MutationDef<any, any
|
|
116
|
+
}, any> | undefined;
|
|
117
|
+
create: import("./reactive").MutationDef<any, any> | undefined;
|
|
118
|
+
update: import("./reactive").MutationDef<any, any> | undefined;
|
|
112
119
|
remove: import("./reactive").MutationDef<any, {
|
|
113
120
|
success: boolean;
|
|
114
|
-
}
|
|
121
|
+
}> | undefined;
|
|
115
122
|
};
|
|
116
123
|
export {};
|
package/dist/crud.js
CHANGED
|
@@ -215,8 +215,11 @@ export function crud(table, options) {
|
|
|
215
215
|
const countResult = await db.select({ count: drizzleCount() }).from(anyTable).where(whereClause);
|
|
216
216
|
return { data, total: Number(countResult[0]?.count ?? 0) };
|
|
217
217
|
}
|
|
218
|
+
// ── methods 필터링: 지정된 메서드만 레지스트리 등록 ──
|
|
219
|
+
// methods 옵션 미지정 시 전체 5개 등록 (하위호환)
|
|
220
|
+
const enabledMethods = new Set(options?.methods ?? ['list', 'get', 'create', 'update', 'remove']);
|
|
218
221
|
// ── list ──────────────────────────────────────
|
|
219
|
-
const listDef = query(`${prefix}.list`, {
|
|
222
|
+
const listDef = !enabledMethods.has('list') ? undefined : query(`${prefix}.list`, {
|
|
220
223
|
public: isPublic,
|
|
221
224
|
args: {
|
|
222
225
|
page: v.optional(v.number()),
|
|
@@ -243,8 +246,6 @@ export function crud(table, options) {
|
|
|
243
246
|
orderByClause = desc(defaultOrderCol);
|
|
244
247
|
}
|
|
245
248
|
// SELECT + COUNT 순차 실행 — 소규모 커넥션 풀 환경에서 동시 점유 방지
|
|
246
|
-
// Note: data←→count 사이 INSERT/DELETE 시 total 불일치 가능 (BaaS 단일사용자/저부하에서 무시 가능)
|
|
247
|
-
// TODO(P2): 대규모 시 db.transaction() 래핑 검토
|
|
248
249
|
const results = await ctx.db.select()
|
|
249
250
|
.from(anyTable)
|
|
250
251
|
.where(whereClause)
|
|
@@ -261,7 +262,7 @@ export function crud(table, options) {
|
|
|
261
262
|
}
|
|
262
263
|
});
|
|
263
264
|
// ── get ───────────────────────────────────────
|
|
264
|
-
const getDef = query(`${prefix}.get`, {
|
|
265
|
+
const getDef = !enabledMethods.has('get') ? undefined : query(`${prefix}.get`, {
|
|
265
266
|
public: isPublic,
|
|
266
267
|
args: { id: idValidator },
|
|
267
268
|
handler: async (ctx, args) => {
|
|
@@ -277,7 +278,7 @@ export function crud(table, options) {
|
|
|
277
278
|
}
|
|
278
279
|
});
|
|
279
280
|
// ── create ────────────────────────────────────
|
|
280
|
-
const createDef = mutation(`${prefix}.create`, {
|
|
281
|
+
const createDef = !enabledMethods.has('create') ? undefined : mutation(`${prefix}.create`, {
|
|
281
282
|
public: isPublic,
|
|
282
283
|
invalidates: [],
|
|
283
284
|
handler: async (ctx, args) => {
|
|
@@ -293,7 +294,7 @@ export function crud(table, options) {
|
|
|
293
294
|
}
|
|
294
295
|
const [result] = await ctx.db.insert(anyTable).values(insertData).returning();
|
|
295
296
|
// Realtime push — { data, total } 형태로 emit
|
|
296
|
-
if (useRealtime) {
|
|
297
|
+
if (useRealtime && enabledMethods.has('list')) {
|
|
297
298
|
const listResult = await fetchListWithTotal(ctx.db);
|
|
298
299
|
ctx.realtime.emit(`${prefix}.list`, listResult);
|
|
299
300
|
}
|
|
@@ -301,7 +302,7 @@ export function crud(table, options) {
|
|
|
301
302
|
}
|
|
302
303
|
});
|
|
303
304
|
// ── update ────────────────────────────────────
|
|
304
|
-
const updateDef = mutation(`${prefix}.update`, {
|
|
305
|
+
const updateDef = !enabledMethods.has('update') ? undefined : mutation(`${prefix}.update`, {
|
|
305
306
|
public: isPublic,
|
|
306
307
|
invalidates: [],
|
|
307
308
|
handler: async (ctx, args) => {
|
|
@@ -323,15 +324,19 @@ export function crud(table, options) {
|
|
|
323
324
|
.returning();
|
|
324
325
|
// Realtime push (list + get 양쪽) — { data, total } 형태
|
|
325
326
|
if (useRealtime) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
327
|
+
if (enabledMethods.has('list')) {
|
|
328
|
+
const listResult = await fetchListWithTotal(ctx.db);
|
|
329
|
+
ctx.realtime.emit(`${prefix}.list`, listResult);
|
|
330
|
+
}
|
|
331
|
+
if (enabledMethods.has('get')) {
|
|
332
|
+
ctx.realtime.emit(`${prefix}.get`, result);
|
|
333
|
+
}
|
|
329
334
|
}
|
|
330
335
|
return result;
|
|
331
336
|
}
|
|
332
337
|
});
|
|
333
338
|
// ── remove ────────────────────────────────────
|
|
334
|
-
const removeDef = mutation(`${prefix}.remove`, {
|
|
339
|
+
const removeDef = !enabledMethods.has('remove') ? undefined : mutation(`${prefix}.remove`, {
|
|
335
340
|
public: isPublic,
|
|
336
341
|
invalidates: [],
|
|
337
342
|
handler: async (ctx, args) => {
|
|
@@ -347,13 +352,16 @@ export function crud(table, options) {
|
|
|
347
352
|
await ctx.db.delete(anyTable).where(eq(pk, args.id));
|
|
348
353
|
}
|
|
349
354
|
// Realtime push — { data, total } 형태
|
|
350
|
-
if (useRealtime) {
|
|
355
|
+
if (useRealtime && enabledMethods.has('list')) {
|
|
351
356
|
const listResult = await fetchListWithTotal(ctx.db);
|
|
352
357
|
ctx.realtime.emit(`${prefix}.list`, listResult);
|
|
353
358
|
}
|
|
354
359
|
return { success: true };
|
|
355
360
|
}
|
|
356
361
|
});
|
|
362
|
+
// 반환 객체는 항상 5개 키를 가지지만, 비활성 메서드는 undefined.
|
|
363
|
+
// 사용자가 destructure 시 undefined를 받으면 export하지 않으므로
|
|
364
|
+
// codegen의 레지스트리에 등록되지 않아 api.ts 불일치가 해소됨.
|
|
357
365
|
return {
|
|
358
366
|
list: listDef,
|
|
359
367
|
get: getDef,
|
package/dist/reactive.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export interface AIResult {
|
|
|
48
48
|
model: string;
|
|
49
49
|
}
|
|
50
50
|
export interface AIContext {
|
|
51
|
-
/** AI 텍스트 생성
|
|
51
|
+
/** AI 텍스트 생성 */
|
|
52
52
|
chat: (opts: {
|
|
53
53
|
model?: string;
|
|
54
54
|
messages: AIMessage[];
|
|
@@ -61,9 +61,9 @@ export interface AIContext {
|
|
|
61
61
|
type: string;
|
|
62
62
|
};
|
|
63
63
|
}) => Promise<AIResult>;
|
|
64
|
-
/** 텍스트 임베딩 (단일)
|
|
64
|
+
/** 텍스트 임베딩 (단일) */
|
|
65
65
|
embed: (text: string) => Promise<number[]>;
|
|
66
|
-
/** 배치 임베딩
|
|
66
|
+
/** 배치 임베딩 */
|
|
67
67
|
embedMany: (texts: string[]) => Promise<number[][]>;
|
|
68
68
|
}
|
|
69
69
|
export interface GencowCtx {
|
|
@@ -81,7 +81,7 @@ export interface GencowCtx {
|
|
|
81
81
|
realtime: RealtimeCtx;
|
|
82
82
|
/** 재시도 — ctx.retry(fn, opts) — exponential backoff + jitter */
|
|
83
83
|
retry: <T>(fn: () => Promise<T>, options?: import("./retry").RetryOptions) => Promise<T>;
|
|
84
|
-
/** AI 헬퍼
|
|
84
|
+
/** AI 헬퍼 */
|
|
85
85
|
ai?: AIContext;
|
|
86
86
|
}
|
|
87
87
|
type QueryHandler<TArgs = any, TReturn = any> = (ctx: GencowCtx, args: TArgs) => Promise<TReturn>;
|
|
@@ -248,8 +248,18 @@ export declare function deregisterClient(ws: WSContext): void;
|
|
|
248
248
|
* 마지막 데이터만 push하여 불필요한 전송을 방지합니다.
|
|
249
249
|
*
|
|
250
250
|
* ⚠️ 매 mutation 호출마다 새로 생성해야 합니다 (debounce timer가 mutation scope에 격리).
|
|
251
|
+
*
|
|
252
|
+
* @param options.httpCallback BaaS 모드: Platform WS Gateway에 HTTP로 emit 전달.
|
|
253
|
+
* 설정되면 WS 직접 push 대신 이 콜백을 호출.
|
|
254
|
+
* 로컬 dev에서는 미설정 → 기존 WS 직접 push 유지.
|
|
251
255
|
*/
|
|
252
|
-
export declare function buildRealtimeCtx(
|
|
256
|
+
export declare function buildRealtimeCtx(options?: {
|
|
257
|
+
httpCallback?: (event: {
|
|
258
|
+
type: "emit";
|
|
259
|
+
queryKey: string;
|
|
260
|
+
data: unknown;
|
|
261
|
+
}) => void;
|
|
262
|
+
}): RealtimeCtx;
|
|
253
263
|
/**
|
|
254
264
|
* mutation이 끝난 후 호출되는 legacy fallback.
|
|
255
265
|
* `ctx.realtime.emit()`을 사용하는 새 mutation에서는 빈 배열([])을 전달하면 됩니다.
|
|
@@ -258,8 +268,9 @@ export declare function buildRealtimeCtx(): RealtimeCtx;
|
|
|
258
268
|
* (서버에서 쿼리를 재실행하지 않으므로 DB 부하 없음)
|
|
259
269
|
*
|
|
260
270
|
* @deprecated ctx.realtime.emit() 사용 권장
|
|
271
|
+
* @param httpInvalidateCallback BaaS 모드: Platform WS Gateway에 HTTP로 invalidation 전달.
|
|
261
272
|
*/
|
|
262
|
-
export declare function invalidateQueries(queryKeys: string[], ctx: GencowCtx): Promise<void>;
|
|
273
|
+
export declare function invalidateQueries(queryKeys: string[], ctx: GencowCtx, httpInvalidateCallback?: (queryKeys: string[]) => void): Promise<void>;
|
|
263
274
|
export declare function handleWsMessage(ws: WSContext, raw: string | ArrayBuffer): void;
|
|
264
275
|
export declare function getQueryHandler(key: string): QueryHandler | undefined;
|
|
265
276
|
export declare function getQueryDef(key: string): QueryDef | undefined;
|
package/dist/reactive.js
CHANGED
|
@@ -181,8 +181,12 @@ export function deregisterClient(ws) {
|
|
|
181
181
|
* 마지막 데이터만 push하여 불필요한 전송을 방지합니다.
|
|
182
182
|
*
|
|
183
183
|
* ⚠️ 매 mutation 호출마다 새로 생성해야 합니다 (debounce timer가 mutation scope에 격리).
|
|
184
|
+
*
|
|
185
|
+
* @param options.httpCallback BaaS 모드: Platform WS Gateway에 HTTP로 emit 전달.
|
|
186
|
+
* 설정되면 WS 직접 push 대신 이 콜백을 호출.
|
|
187
|
+
* 로컬 dev에서는 미설정 → 기존 WS 직접 push 유지.
|
|
184
188
|
*/
|
|
185
|
-
export function buildRealtimeCtx() {
|
|
189
|
+
export function buildRealtimeCtx(options) {
|
|
186
190
|
const pendingEmits = new Map();
|
|
187
191
|
return {
|
|
188
192
|
emit(queryKey, data) {
|
|
@@ -192,6 +196,12 @@ export function buildRealtimeCtx() {
|
|
|
192
196
|
clearTimeout(existing.timer);
|
|
193
197
|
const timer = setTimeout(() => {
|
|
194
198
|
pendingEmits.delete(queryKey);
|
|
199
|
+
// BaaS 모드: Platform WS Gateway에 HTTP callback
|
|
200
|
+
if (options?.httpCallback) {
|
|
201
|
+
options.httpCallback({ type: "emit", queryKey, data });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
// 로컬 dev: WS 직접 push (기존 동작)
|
|
195
205
|
const clients = subscribers.get(queryKey);
|
|
196
206
|
if (!clients || clients.size === 0)
|
|
197
207
|
return;
|
|
@@ -221,11 +231,17 @@ export function buildRealtimeCtx() {
|
|
|
221
231
|
* (서버에서 쿼리를 재실행하지 않으므로 DB 부하 없음)
|
|
222
232
|
*
|
|
223
233
|
* @deprecated ctx.realtime.emit() 사용 권장
|
|
234
|
+
* @param httpInvalidateCallback BaaS 모드: Platform WS Gateway에 HTTP로 invalidation 전달.
|
|
224
235
|
*/
|
|
225
|
-
export async function invalidateQueries(queryKeys, ctx) {
|
|
236
|
+
export async function invalidateQueries(queryKeys, ctx, httpInvalidateCallback) {
|
|
226
237
|
if (queryKeys.length === 0)
|
|
227
238
|
return; // emit() 방식에서는 no-op
|
|
228
|
-
//
|
|
239
|
+
// BaaS 모드: Platform WS Gateway에 HTTP callback
|
|
240
|
+
if (httpInvalidateCallback) {
|
|
241
|
+
httpInvalidateCallback(queryKeys);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// 로컬 dev: WS 직접 broadcast (기존 동작)
|
|
229
245
|
const invalidateMsg = JSON.stringify({ type: "invalidate", queries: queryKeys });
|
|
230
246
|
for (const ws of connectedClients) {
|
|
231
247
|
try {
|
package/package.json
CHANGED
package/src/crud.ts
CHANGED
|
@@ -52,6 +52,13 @@ type CrudOptions<T extends PgTable> = {
|
|
|
52
52
|
realtime?: boolean;
|
|
53
53
|
/** 키 접두사 오버라이드 (기본: 테이블명) */
|
|
54
54
|
prefix?: string;
|
|
55
|
+
/**
|
|
56
|
+
* 생성할 메서드 목록 (기본: 전체 5개)
|
|
57
|
+
* 지정하면 해당 메서드만 레지스트리에 등록되고 반환됨.
|
|
58
|
+
* api.ts codegen에도 지정된 메서드만 포함됨.
|
|
59
|
+
* @example crud(table, { methods: ['list', 'get', 'create'] })
|
|
60
|
+
*/
|
|
61
|
+
methods?: ('list' | 'get' | 'create' | 'update' | 'remove')[];
|
|
55
62
|
};
|
|
56
63
|
|
|
57
64
|
// ─── Helpers ────────────────────────────────────────────
|
|
@@ -271,9 +278,13 @@ export function crud<T extends PgTable>(table: T, options?: CrudOptions<T>) {
|
|
|
271
278
|
return { data, total: Number(countResult[0]?.count ?? 0) };
|
|
272
279
|
}
|
|
273
280
|
|
|
281
|
+
// ── methods 필터링: 지정된 메서드만 레지스트리 등록 ──
|
|
282
|
+
// methods 옵션 미지정 시 전체 5개 등록 (하위호환)
|
|
283
|
+
const enabledMethods = new Set(options?.methods ?? ['list', 'get', 'create', 'update', 'remove']);
|
|
284
|
+
|
|
274
285
|
// ── list ──────────────────────────────────────
|
|
275
286
|
|
|
276
|
-
const listDef = query(`${prefix}.list`, {
|
|
287
|
+
const listDef = !enabledMethods.has('list') ? undefined : query(`${prefix}.list`, {
|
|
277
288
|
public: isPublic,
|
|
278
289
|
args: {
|
|
279
290
|
page: v.optional(v.number()),
|
|
@@ -302,8 +313,6 @@ export function crud<T extends PgTable>(table: T, options?: CrudOptions<T>) {
|
|
|
302
313
|
}
|
|
303
314
|
|
|
304
315
|
// SELECT + COUNT 순차 실행 — 소규모 커넥션 풀 환경에서 동시 점유 방지
|
|
305
|
-
// Note: data←→count 사이 INSERT/DELETE 시 total 불일치 가능 (BaaS 단일사용자/저부하에서 무시 가능)
|
|
306
|
-
// TODO(P2): 대규모 시 db.transaction() 래핑 검토
|
|
307
316
|
const results = await ctx.db.select()
|
|
308
317
|
.from(anyTable)
|
|
309
318
|
.where(whereClause)
|
|
@@ -323,7 +332,7 @@ export function crud<T extends PgTable>(table: T, options?: CrudOptions<T>) {
|
|
|
323
332
|
|
|
324
333
|
// ── get ───────────────────────────────────────
|
|
325
334
|
|
|
326
|
-
const getDef = query(`${prefix}.get`, {
|
|
335
|
+
const getDef = !enabledMethods.has('get') ? undefined : query(`${prefix}.get`, {
|
|
327
336
|
public: isPublic,
|
|
328
337
|
args: { id: idValidator },
|
|
329
338
|
handler: async (ctx: any, args: any) => {
|
|
@@ -343,7 +352,7 @@ export function crud<T extends PgTable>(table: T, options?: CrudOptions<T>) {
|
|
|
343
352
|
|
|
344
353
|
// ── create ────────────────────────────────────
|
|
345
354
|
|
|
346
|
-
const createDef = mutation(`${prefix}.create`, {
|
|
355
|
+
const createDef = !enabledMethods.has('create') ? undefined : mutation(`${prefix}.create`, {
|
|
347
356
|
public: isPublic,
|
|
348
357
|
invalidates: [],
|
|
349
358
|
handler: async (ctx: any, args: any) => {
|
|
@@ -364,7 +373,7 @@ export function crud<T extends PgTable>(table: T, options?: CrudOptions<T>) {
|
|
|
364
373
|
const [result] = await ctx.db.insert(anyTable).values(insertData).returning();
|
|
365
374
|
|
|
366
375
|
// Realtime push — { data, total } 형태로 emit
|
|
367
|
-
if (useRealtime) {
|
|
376
|
+
if (useRealtime && enabledMethods.has('list')) {
|
|
368
377
|
const listResult = await fetchListWithTotal(ctx.db);
|
|
369
378
|
ctx.realtime.emit(`${prefix}.list`, listResult);
|
|
370
379
|
}
|
|
@@ -375,7 +384,7 @@ export function crud<T extends PgTable>(table: T, options?: CrudOptions<T>) {
|
|
|
375
384
|
|
|
376
385
|
// ── update ────────────────────────────────────
|
|
377
386
|
|
|
378
|
-
const updateDef = mutation(`${prefix}.update`, {
|
|
387
|
+
const updateDef = !enabledMethods.has('update') ? undefined : mutation(`${prefix}.update`, {
|
|
379
388
|
public: isPublic,
|
|
380
389
|
invalidates: [],
|
|
381
390
|
handler: async (ctx: any, args: any) => {
|
|
@@ -402,9 +411,13 @@ export function crud<T extends PgTable>(table: T, options?: CrudOptions<T>) {
|
|
|
402
411
|
|
|
403
412
|
// Realtime push (list + get 양쪽) — { data, total } 형태
|
|
404
413
|
if (useRealtime) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
414
|
+
if (enabledMethods.has('list')) {
|
|
415
|
+
const listResult = await fetchListWithTotal(ctx.db);
|
|
416
|
+
ctx.realtime.emit(`${prefix}.list`, listResult);
|
|
417
|
+
}
|
|
418
|
+
if (enabledMethods.has('get')) {
|
|
419
|
+
ctx.realtime.emit(`${prefix}.get`, result);
|
|
420
|
+
}
|
|
408
421
|
}
|
|
409
422
|
|
|
410
423
|
return result;
|
|
@@ -413,7 +426,7 @@ export function crud<T extends PgTable>(table: T, options?: CrudOptions<T>) {
|
|
|
413
426
|
|
|
414
427
|
// ── remove ────────────────────────────────────
|
|
415
428
|
|
|
416
|
-
const removeDef = mutation(`${prefix}.remove`, {
|
|
429
|
+
const removeDef = !enabledMethods.has('remove') ? undefined : mutation(`${prefix}.remove`, {
|
|
417
430
|
public: isPublic,
|
|
418
431
|
invalidates: [],
|
|
419
432
|
handler: async (ctx: any, args: any) => {
|
|
@@ -429,7 +442,7 @@ export function crud<T extends PgTable>(table: T, options?: CrudOptions<T>) {
|
|
|
429
442
|
}
|
|
430
443
|
|
|
431
444
|
// Realtime push — { data, total } 형태
|
|
432
|
-
if (useRealtime) {
|
|
445
|
+
if (useRealtime && enabledMethods.has('list')) {
|
|
433
446
|
const listResult = await fetchListWithTotal(ctx.db);
|
|
434
447
|
ctx.realtime.emit(`${prefix}.list`, listResult);
|
|
435
448
|
}
|
|
@@ -438,6 +451,9 @@ export function crud<T extends PgTable>(table: T, options?: CrudOptions<T>) {
|
|
|
438
451
|
}
|
|
439
452
|
});
|
|
440
453
|
|
|
454
|
+
// 반환 객체는 항상 5개 키를 가지지만, 비활성 메서드는 undefined.
|
|
455
|
+
// 사용자가 destructure 시 undefined를 받으면 export하지 않으므로
|
|
456
|
+
// codegen의 레지스트리에 등록되지 않아 api.ts 불일치가 해소됨.
|
|
441
457
|
return {
|
|
442
458
|
list: listDef,
|
|
443
459
|
get: getDef,
|
package/src/reactive.ts
CHANGED
|
@@ -54,7 +54,7 @@ export interface AIResult {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
export interface AIContext {
|
|
57
|
-
/** AI 텍스트 생성
|
|
57
|
+
/** AI 텍스트 생성 */
|
|
58
58
|
chat: (opts: {
|
|
59
59
|
model?: string;
|
|
60
60
|
messages: AIMessage[];
|
|
@@ -65,9 +65,9 @@ export interface AIContext {
|
|
|
65
65
|
/** Response format — e.g. { type: "json_object" } for JSON mode */
|
|
66
66
|
responseFormat?: { type: string };
|
|
67
67
|
}) => Promise<AIResult>;
|
|
68
|
-
/** 텍스트 임베딩 (단일)
|
|
68
|
+
/** 텍스트 임베딩 (단일) */
|
|
69
69
|
embed: (text: string) => Promise<number[]>;
|
|
70
|
-
/** 배치 임베딩
|
|
70
|
+
/** 배치 임베딩 */
|
|
71
71
|
embedMany: (texts: string[]) => Promise<number[][]>;
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -86,7 +86,7 @@ export interface GencowCtx {
|
|
|
86
86
|
realtime: RealtimeCtx;
|
|
87
87
|
/** 재시도 — ctx.retry(fn, opts) — exponential backoff + jitter */
|
|
88
88
|
retry: <T>(fn: () => Promise<T>, options?: import("./retry").RetryOptions) => Promise<T>;
|
|
89
|
-
/** AI 헬퍼
|
|
89
|
+
/** AI 헬퍼 */
|
|
90
90
|
ai?: AIContext;
|
|
91
91
|
}
|
|
92
92
|
|
|
@@ -392,8 +392,14 @@ export function deregisterClient(ws: WSContext) {
|
|
|
392
392
|
* 마지막 데이터만 push하여 불필요한 전송을 방지합니다.
|
|
393
393
|
*
|
|
394
394
|
* ⚠️ 매 mutation 호출마다 새로 생성해야 합니다 (debounce timer가 mutation scope에 격리).
|
|
395
|
+
*
|
|
396
|
+
* @param options.httpCallback BaaS 모드: Platform WS Gateway에 HTTP로 emit 전달.
|
|
397
|
+
* 설정되면 WS 직접 push 대신 이 콜백을 호출.
|
|
398
|
+
* 로컬 dev에서는 미설정 → 기존 WS 직접 push 유지.
|
|
395
399
|
*/
|
|
396
|
-
export function buildRealtimeCtx(
|
|
400
|
+
export function buildRealtimeCtx(options?: {
|
|
401
|
+
httpCallback?: (event: { type: "emit"; queryKey: string; data: unknown }) => void;
|
|
402
|
+
}): RealtimeCtx {
|
|
397
403
|
const pendingEmits = new Map<string, { data: unknown; timer: ReturnType<typeof setTimeout> }>();
|
|
398
404
|
|
|
399
405
|
return {
|
|
@@ -404,6 +410,14 @@ export function buildRealtimeCtx(): RealtimeCtx {
|
|
|
404
410
|
|
|
405
411
|
const timer = setTimeout(() => {
|
|
406
412
|
pendingEmits.delete(queryKey);
|
|
413
|
+
|
|
414
|
+
// BaaS 모드: Platform WS Gateway에 HTTP callback
|
|
415
|
+
if (options?.httpCallback) {
|
|
416
|
+
options.httpCallback({ type: "emit", queryKey, data });
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// 로컬 dev: WS 직접 push (기존 동작)
|
|
407
421
|
const clients = subscribers.get(queryKey);
|
|
408
422
|
if (!clients || clients.size === 0) return;
|
|
409
423
|
|
|
@@ -430,14 +444,22 @@ export function buildRealtimeCtx(): RealtimeCtx {
|
|
|
430
444
|
* (서버에서 쿼리를 재실행하지 않으므로 DB 부하 없음)
|
|
431
445
|
*
|
|
432
446
|
* @deprecated ctx.realtime.emit() 사용 권장
|
|
447
|
+
* @param httpInvalidateCallback BaaS 모드: Platform WS Gateway에 HTTP로 invalidation 전달.
|
|
433
448
|
*/
|
|
434
449
|
export async function invalidateQueries(
|
|
435
450
|
queryKeys: string[],
|
|
436
|
-
ctx: GencowCtx
|
|
451
|
+
ctx: GencowCtx,
|
|
452
|
+
httpInvalidateCallback?: (queryKeys: string[]) => void,
|
|
437
453
|
): Promise<void> {
|
|
438
454
|
if (queryKeys.length === 0) return; // emit() 방식에서는 no-op
|
|
439
455
|
|
|
440
|
-
//
|
|
456
|
+
// BaaS 모드: Platform WS Gateway에 HTTP callback
|
|
457
|
+
if (httpInvalidateCallback) {
|
|
458
|
+
httpInvalidateCallback(queryKeys);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// 로컬 dev: WS 직접 broadcast (기존 동작)
|
|
441
463
|
const invalidateMsg = JSON.stringify({ type: "invalidate", queries: queryKeys });
|
|
442
464
|
for (const ws of connectedClients) {
|
|
443
465
|
try { ws.send(invalidateMsg); } catch { connectedClients.delete(ws); }
|