@gencow/core 0.1.3 → 0.1.5
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.d.ts +1 -1
- package/dist/reactive.d.ts +25 -0
- package/dist/storage.js +18 -0
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/reactive.ts +26 -0
- package/src/storage.ts +25 -0
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Provides: query, mutation, storage, scheduler, auth
|
|
5
5
|
* All with Convex-compatible DX patterns.
|
|
6
6
|
*/
|
|
7
|
-
export type { GencowCtx, AuthCtx, UserIdentity, QueryDef, MutationDef, RealtimeCtx, HttpActionDef, HttpActionRequest, HttpActionResponse, HttpActionHandler } from "./reactive";
|
|
7
|
+
export type { GencowCtx, AuthCtx, UserIdentity, QueryDef, MutationDef, RealtimeCtx, HttpActionDef, HttpActionRequest, HttpActionResponse, HttpActionHandler, AIContext, AIMessage, AIResult } from "./reactive";
|
|
8
8
|
export { query, mutation, httpAction, invalidateQueries, buildRealtimeCtx, subscribe, unsubscribe, registerClient, deregisterClient, handleWsMessage, getQueryHandler, getQueryDef, getRegisteredQueries, getRegisteredMutations, getRegisteredHttpActions } from "./reactive";
|
|
9
9
|
export type { Storage } from "./storage";
|
|
10
10
|
export { createScheduler, getSchedulerInfo } from "./scheduler";
|
package/dist/reactive.d.ts
CHANGED
|
@@ -33,6 +33,29 @@ export interface RealtimeCtx {
|
|
|
33
33
|
* Convex의 ctx 패턴과 동일하게, 이 객체를 통해서만 DB/Storage/Auth에 접근 가능.
|
|
34
34
|
* fs, child_process 등 원시 Node.js API는 노출되지 않음.
|
|
35
35
|
*/
|
|
36
|
+
export interface AIMessage {
|
|
37
|
+
role: "user" | "system" | "assistant";
|
|
38
|
+
content: string;
|
|
39
|
+
}
|
|
40
|
+
export interface AIResult {
|
|
41
|
+
text: string;
|
|
42
|
+
usage: {
|
|
43
|
+
promptTokens: number;
|
|
44
|
+
completionTokens: number;
|
|
45
|
+
totalTokens: number;
|
|
46
|
+
};
|
|
47
|
+
creditsCharged: number;
|
|
48
|
+
model: string;
|
|
49
|
+
}
|
|
50
|
+
export interface AIContext {
|
|
51
|
+
/** AI 텍스트 생성 — ctx.ai.chat({ model, messages }) */
|
|
52
|
+
chat: (opts: {
|
|
53
|
+
model?: string;
|
|
54
|
+
messages: AIMessage[];
|
|
55
|
+
temperature?: number;
|
|
56
|
+
maxTokens?: number;
|
|
57
|
+
}) => Promise<AIResult>;
|
|
58
|
+
}
|
|
36
59
|
export interface GencowCtx {
|
|
37
60
|
/** Drizzle DB 인스턴스 — ctx.db.select().from(table) */
|
|
38
61
|
db: any;
|
|
@@ -46,6 +69,8 @@ export interface GencowCtx {
|
|
|
46
69
|
realtime: RealtimeCtx;
|
|
47
70
|
/** 재시도 — ctx.retry(fn, opts) — exponential backoff + jitter */
|
|
48
71
|
retry: <T>(fn: () => Promise<T>, options?: import("./retry").RetryOptions) => Promise<T>;
|
|
72
|
+
/** AI 헬퍼 — ctx.ai.chat({ model, messages }) — 로컬: 직접 호출, BaaS: Control Plane 프록시 */
|
|
73
|
+
ai?: AIContext;
|
|
49
74
|
}
|
|
50
75
|
type QueryHandler<TArgs = any, TReturn = any> = (ctx: GencowCtx, args: TArgs) => Promise<TReturn>;
|
|
51
76
|
type MutationHandler<TArgs = any, TReturn = any> = (ctx: GencowCtx, args: TArgs) => Promise<TReturn>;
|
package/dist/storage.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import * as fs from "fs/promises";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import * as crypto from "crypto";
|
|
4
|
+
// ─── Constants ──────────────────────────────────────────
|
|
5
|
+
/** 파일 업로드 최대 크기: 50MB (하드코딩 — 사용자가 오버라이드 불가) */
|
|
6
|
+
const MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
7
|
+
function formatBytes(bytes) {
|
|
8
|
+
if (bytes < 1024)
|
|
9
|
+
return `${bytes}B`;
|
|
10
|
+
if (bytes < 1024 * 1024)
|
|
11
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
12
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
13
|
+
}
|
|
4
14
|
// ─── Implementation ─────────────────────────────────────
|
|
5
15
|
const metaStore = new Map();
|
|
6
16
|
/**
|
|
@@ -16,6 +26,10 @@ export function createStorage(dir = "./uploads") {
|
|
|
16
26
|
fs.mkdir(dir, { recursive: true }).catch(() => { });
|
|
17
27
|
return {
|
|
18
28
|
async store(file, filename) {
|
|
29
|
+
// 크기 제한 검증
|
|
30
|
+
if (file.size > MAX_FILE_SIZE) {
|
|
31
|
+
throw new Error(`File too large: ${formatBytes(file.size)} exceeds limit of ${formatBytes(MAX_FILE_SIZE)}`);
|
|
32
|
+
}
|
|
19
33
|
const id = crypto.randomUUID();
|
|
20
34
|
const name = filename || (file instanceof File ? file.name : `${id}.bin`);
|
|
21
35
|
const filePath = path.join(dir, id);
|
|
@@ -33,6 +47,10 @@ export function createStorage(dir = "./uploads") {
|
|
|
33
47
|
async storeBuffer(buffer, filename, type = "application/octet-stream") {
|
|
34
48
|
const id = crypto.randomUUID();
|
|
35
49
|
const filePath = path.join(dir, id);
|
|
50
|
+
// 크기 제한 검증
|
|
51
|
+
if (buffer.length > MAX_FILE_SIZE) {
|
|
52
|
+
throw new Error(`File too large: ${formatBytes(buffer.length)} exceeds limit of ${formatBytes(MAX_FILE_SIZE)}`);
|
|
53
|
+
}
|
|
36
54
|
await fs.writeFile(filePath, buffer);
|
|
37
55
|
metaStore.set(id, {
|
|
38
56
|
id,
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* All with Convex-compatible DX patterns.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export type { GencowCtx, AuthCtx, UserIdentity, QueryDef, MutationDef, RealtimeCtx, HttpActionDef, HttpActionRequest, HttpActionResponse, HttpActionHandler } from "./reactive";
|
|
8
|
+
export type { GencowCtx, AuthCtx, UserIdentity, QueryDef, MutationDef, RealtimeCtx, HttpActionDef, HttpActionRequest, HttpActionResponse, HttpActionHandler, AIContext, AIMessage, AIResult } from "./reactive";
|
|
9
9
|
export { query, mutation, httpAction, invalidateQueries, buildRealtimeCtx, subscribe, unsubscribe, registerClient, deregisterClient, handleWsMessage, getQueryHandler, getQueryDef, getRegisteredQueries, getRegisteredMutations, getRegisteredHttpActions } from "./reactive";
|
|
10
10
|
export type { Storage } from "./storage";
|
|
11
11
|
export { createScheduler, getSchedulerInfo } from "./scheduler";
|
package/src/reactive.ts
CHANGED
|
@@ -39,6 +39,30 @@ export interface RealtimeCtx {
|
|
|
39
39
|
* Convex의 ctx 패턴과 동일하게, 이 객체를 통해서만 DB/Storage/Auth에 접근 가능.
|
|
40
40
|
* fs, child_process 등 원시 Node.js API는 노출되지 않음.
|
|
41
41
|
*/
|
|
42
|
+
// ─── AI Context ─────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
export interface AIMessage {
|
|
45
|
+
role: "user" | "system" | "assistant";
|
|
46
|
+
content: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface AIResult {
|
|
50
|
+
text: string;
|
|
51
|
+
usage: { promptTokens: number; completionTokens: number; totalTokens: number };
|
|
52
|
+
creditsCharged: number;
|
|
53
|
+
model: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface AIContext {
|
|
57
|
+
/** AI 텍스트 생성 — ctx.ai.chat({ model, messages }) */
|
|
58
|
+
chat: (opts: {
|
|
59
|
+
model?: string;
|
|
60
|
+
messages: AIMessage[];
|
|
61
|
+
temperature?: number;
|
|
62
|
+
maxTokens?: number;
|
|
63
|
+
}) => Promise<AIResult>;
|
|
64
|
+
}
|
|
65
|
+
|
|
42
66
|
export interface GencowCtx {
|
|
43
67
|
/** Drizzle DB 인스턴스 — ctx.db.select().from(table) */
|
|
44
68
|
db: any; // typed per-app via generic
|
|
@@ -52,6 +76,8 @@ export interface GencowCtx {
|
|
|
52
76
|
realtime: RealtimeCtx;
|
|
53
77
|
/** 재시도 — ctx.retry(fn, opts) — exponential backoff + jitter */
|
|
54
78
|
retry: <T>(fn: () => Promise<T>, options?: import("./retry").RetryOptions) => Promise<T>;
|
|
79
|
+
/** AI 헬퍼 — ctx.ai.chat({ model, messages }) — 로컬: 직접 호출, BaaS: Control Plane 프록시 */
|
|
80
|
+
ai?: AIContext;
|
|
55
81
|
}
|
|
56
82
|
|
|
57
83
|
// ─── Types ──────────────────────────────────────────────
|
package/src/storage.ts
CHANGED
|
@@ -2,6 +2,17 @@ import * as fs from "fs/promises";
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import * as crypto from "crypto";
|
|
4
4
|
|
|
5
|
+
// ─── Constants ──────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
/** 파일 업로드 최대 크기: 50MB (하드코딩 — 사용자가 오버라이드 불가) */
|
|
8
|
+
const MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
9
|
+
|
|
10
|
+
function formatBytes(bytes: number): string {
|
|
11
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
12
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
13
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
14
|
+
}
|
|
15
|
+
|
|
5
16
|
// ─── Types ──────────────────────────────────────────────
|
|
6
17
|
|
|
7
18
|
interface StorageFile {
|
|
@@ -43,6 +54,13 @@ export function createStorage(dir: string = "./uploads"): Storage {
|
|
|
43
54
|
|
|
44
55
|
return {
|
|
45
56
|
async store(file: File | Blob, filename?: string): Promise<string> {
|
|
57
|
+
// 크기 제한 검증
|
|
58
|
+
if (file.size > MAX_FILE_SIZE) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`File too large: ${formatBytes(file.size)} exceeds limit of ${formatBytes(MAX_FILE_SIZE)}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
46
64
|
const id = crypto.randomUUID();
|
|
47
65
|
const name = filename || (file instanceof File ? file.name : `${id}.bin`);
|
|
48
66
|
const filePath = path.join(dir, id);
|
|
@@ -69,6 +87,13 @@ export function createStorage(dir: string = "./uploads"): Storage {
|
|
|
69
87
|
const id = crypto.randomUUID();
|
|
70
88
|
const filePath = path.join(dir, id);
|
|
71
89
|
|
|
90
|
+
// 크기 제한 검증
|
|
91
|
+
if (buffer.length > MAX_FILE_SIZE) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`File too large: ${formatBytes(buffer.length)} exceeds limit of ${formatBytes(MAX_FILE_SIZE)}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
72
97
|
await fs.writeFile(filePath, buffer);
|
|
73
98
|
|
|
74
99
|
metaStore.set(id, {
|