gencow 0.1.114 → 0.1.116
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/bin/gencow.mjs +295 -0
- package/core/index.js +52 -24
- package/dashboard/assets/index-Bur5ZNpv.js +372 -0
- package/dashboard/index.html +1 -1
- package/package.json +1 -1
- package/server/index.js +12995 -42532
- package/server/index.js.map +4 -4
- package/templates/ai-chat/chat.ts +0 -1
- package/templates/fullstack/files.ts +0 -1
- package/templates/fullstack/tasks.ts +0 -3
- package/templates/task-app/files.ts +0 -1
- package/templates/task-app/tasks.ts +0 -3
- package/dashboard/assets/index-C26b7MIs.js +0 -372
package/bin/gencow.mjs
CHANGED
|
@@ -1718,6 +1718,10 @@ ${BOLD}BaaS commands (login required):${RESET}
|
|
|
1718
1718
|
${GREEN}env set K=V${RESET} Set cloud env var ${DIM}(hot-reload, no restart)${RESET}
|
|
1719
1719
|
${GREEN}env unset KEY${RESET} Remove cloud env var
|
|
1720
1720
|
${GREEN}env push${RESET} Push .env to cloud
|
|
1721
|
+
${GREEN}files upload${RESET} Upload files to app storage ${DIM}(--recursive for dirs)${RESET}
|
|
1722
|
+
${GREEN}files list${RESET} List uploaded files
|
|
1723
|
+
${GREEN}files delete${RESET} Delete uploaded file ${DIM}(--yes to skip confirm)${RESET}
|
|
1724
|
+
${GREEN}files url${RESET} Get serving URL for a file
|
|
1721
1725
|
${GREEN}domain set${RESET} Connect custom domain ${DIM}(myapp.com)${RESET}
|
|
1722
1726
|
${GREEN}domain status${RESET} Check domain DNS/TLS status
|
|
1723
1727
|
${GREEN}domain remove${RESET} Disconnect custom domain
|
|
@@ -2981,6 +2985,297 @@ ${BOLD}Examples:${RESET}
|
|
|
2981
2985
|
},
|
|
2982
2986
|
|
|
2983
2987
|
|
|
2988
|
+
// ── files ─────────────────────────────────────────────
|
|
2989
|
+
async files(...filesArgs) {
|
|
2990
|
+
const subCmd = filesArgs[0] || "help";
|
|
2991
|
+
const restArgs = filesArgs.slice(1);
|
|
2992
|
+
|
|
2993
|
+
// help는 인증/앱 정보 없이도 표시
|
|
2994
|
+
if (subCmd === "help" || subCmd === "--help" || subCmd === "-h") {
|
|
2995
|
+
log(`\n${BOLD}${CYAN}gencow files${RESET} — 파일 관리\n`);
|
|
2996
|
+
log(` ${CYAN}upload${RESET} <경로...> [--recursive|-r] 파일 업로드`);
|
|
2997
|
+
log(` ${CYAN}list${RESET} 파일 목록`);
|
|
2998
|
+
log(` ${CYAN}delete${RESET} <storage_id> [--yes|-y] 파일 삭제`);
|
|
2999
|
+
log(` ${CYAN}url${RESET} <storage_id> 서빙 URL 출력`);
|
|
3000
|
+
log(`\n ${DIM}옵션:${RESET}`);
|
|
3001
|
+
log(` ${DIM}--app, -a <앱이름> 대상 앱 지정 (기본: gencow.json)${RESET}\n`);
|
|
3002
|
+
return;
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
const creds = requireCreds();
|
|
3006
|
+
|
|
3007
|
+
// 앱 ID 결정 + --app 플래그 위치 추적 (paths 필터링용)
|
|
3008
|
+
let appId = null;
|
|
3009
|
+
const flagIndices = new Set(); // --app와 그 값의 인덱스
|
|
3010
|
+
for (let i = 0; i < restArgs.length; i++) {
|
|
3011
|
+
if (restArgs[i] === "--app" || restArgs[i] === "-a") {
|
|
3012
|
+
flagIndices.add(i);
|
|
3013
|
+
flagIndices.add(i + 1);
|
|
3014
|
+
appId = restArgs[++i];
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
if (!appId) {
|
|
3019
|
+
const gencowJsonPath = resolve(process.cwd(), "gencow.json");
|
|
3020
|
+
if (existsSync(gencowJsonPath)) {
|
|
3021
|
+
const gencowJson = JSON.parse(readFileSync(gencowJsonPath, "utf8"));
|
|
3022
|
+
appId = gencowJson.appId || gencowJson.appName;
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
if (!appId) {
|
|
3027
|
+
error("앱 ID를 찾을 수 없습니다. gencow deploy를 먼저 실행하세요.");
|
|
3028
|
+
return;
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50 MB
|
|
3032
|
+
|
|
3033
|
+
function fmtFileSize(bytes) {
|
|
3034
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
3035
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
3036
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
function fmtDate(d) {
|
|
3040
|
+
return new Date(d).toLocaleString("ko-KR", {
|
|
3041
|
+
year: "numeric", month: "2-digit", day: "2-digit",
|
|
3042
|
+
hour: "2-digit", minute: "2-digit",
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
/** 파일 서빙 URL 생성 */
|
|
3047
|
+
function getFileUrl(storageId) {
|
|
3048
|
+
return `${getAppUrl(appId, creds.platformUrl)}/api/storage/${storageId}`;
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
/** 단일 파일 업로드 */
|
|
3052
|
+
async function uploadFile(filePath) {
|
|
3053
|
+
const fileName = basename(filePath);
|
|
3054
|
+
const stat = statSync(filePath);
|
|
3055
|
+
|
|
3056
|
+
// 로컬 크기 검증 (서버 왕복 방지)
|
|
3057
|
+
if (stat.size > MAX_FILE_SIZE) {
|
|
3058
|
+
error(`${fileName}: ${fmtFileSize(stat.size)} — 50MB 제한 초과`);
|
|
3059
|
+
return null;
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
info(`${fileName} (${fmtFileSize(stat.size)}) 업로드 중...`);
|
|
3063
|
+
|
|
3064
|
+
const fileBuffer = readFileSync(filePath);
|
|
3065
|
+
const blob = new Blob([fileBuffer]);
|
|
3066
|
+
|
|
3067
|
+
const formData = new FormData();
|
|
3068
|
+
formData.append("file", blob, fileName);
|
|
3069
|
+
|
|
3070
|
+
try {
|
|
3071
|
+
const res = await fetch(
|
|
3072
|
+
`${creds.platformUrl}/platform/files/upload?appName=${appId}`,
|
|
3073
|
+
{
|
|
3074
|
+
method: "POST",
|
|
3075
|
+
headers: {
|
|
3076
|
+
"Authorization": `Bearer ${creds.apiKey}`,
|
|
3077
|
+
},
|
|
3078
|
+
body: formData,
|
|
3079
|
+
}
|
|
3080
|
+
);
|
|
3081
|
+
|
|
3082
|
+
const data = await res.json().catch(() => ({}));
|
|
3083
|
+
if (!res.ok) {
|
|
3084
|
+
error(`${fileName}: ${data.error || res.statusText}`);
|
|
3085
|
+
return null;
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
success(`${fileName} → ${data.storageId}`);
|
|
3089
|
+
info(` URL: ${getFileUrl(data.storageId)}`);
|
|
3090
|
+
return data;
|
|
3091
|
+
} catch (err) {
|
|
3092
|
+
error(`${fileName}: ${err.message}`);
|
|
3093
|
+
return null;
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
/** 디렉토리 재귀 파일 수집 */
|
|
3098
|
+
function collectFiles(dirPath) {
|
|
3099
|
+
const results = [];
|
|
3100
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
3101
|
+
for (const entry of entries) {
|
|
3102
|
+
const full = resolve(dirPath, entry.name);
|
|
3103
|
+
if (entry.isDirectory()) {
|
|
3104
|
+
results.push(...collectFiles(full));
|
|
3105
|
+
} else if (entry.isFile()) {
|
|
3106
|
+
results.push(full);
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
return results;
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
switch (subCmd) {
|
|
3113
|
+
case "upload":
|
|
3114
|
+
case "up": {
|
|
3115
|
+
const recursive = restArgs.includes("--recursive") || restArgs.includes("-r");
|
|
3116
|
+
const paths = restArgs.filter((a, i) => !a.startsWith("-") && !flagIndices.has(i));
|
|
3117
|
+
|
|
3118
|
+
if (paths.length === 0) {
|
|
3119
|
+
error("사용법: gencow files upload <파일|폴더 경로...> [--recursive|-r]");
|
|
3120
|
+
return;
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
let filesToUpload = [];
|
|
3124
|
+
for (const p of paths) {
|
|
3125
|
+
const resolved = resolve(process.cwd(), p);
|
|
3126
|
+
if (!existsSync(resolved)) {
|
|
3127
|
+
error(`파일을 찾을 수 없습니다: ${p}`);
|
|
3128
|
+
continue;
|
|
3129
|
+
}
|
|
3130
|
+
const stat = statSync(resolved);
|
|
3131
|
+
if (stat.isDirectory()) {
|
|
3132
|
+
if (!recursive) {
|
|
3133
|
+
error(`${p}는 디렉토리입니다. --recursive (-r) 옵션을 사용하세요.`);
|
|
3134
|
+
continue;
|
|
3135
|
+
}
|
|
3136
|
+
filesToUpload.push(...collectFiles(resolved));
|
|
3137
|
+
} else {
|
|
3138
|
+
filesToUpload.push(resolved);
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
if (filesToUpload.length === 0) {
|
|
3143
|
+
error("업로드할 파일이 없습니다.");
|
|
3144
|
+
return;
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
log(`\n${BOLD}${CYAN}파일 업로드${RESET} — ${appId}\n`);
|
|
3148
|
+
info(`${filesToUpload.length}개 파일 업로드 시작...\n`);
|
|
3149
|
+
|
|
3150
|
+
let uploaded = 0;
|
|
3151
|
+
let failed = 0;
|
|
3152
|
+
for (const filePath of filesToUpload) {
|
|
3153
|
+
const result = await uploadFile(filePath);
|
|
3154
|
+
if (result) uploaded++;
|
|
3155
|
+
else failed++;
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
log("");
|
|
3159
|
+
if (uploaded > 0) success(`${uploaded}개 파일 업로드 완료`);
|
|
3160
|
+
if (failed > 0) warn(`${failed}개 파일 실패`);
|
|
3161
|
+
break;
|
|
3162
|
+
}
|
|
3163
|
+
|
|
3164
|
+
case "list":
|
|
3165
|
+
case "ls": {
|
|
3166
|
+
log(`\n${BOLD}${CYAN}파일 목록${RESET} — ${appId}\n`);
|
|
3167
|
+
|
|
3168
|
+
const res = await fetch(
|
|
3169
|
+
`${creds.platformUrl}/platform/files/list`,
|
|
3170
|
+
{
|
|
3171
|
+
method: "POST",
|
|
3172
|
+
headers: {
|
|
3173
|
+
"Authorization": `Bearer ${creds.apiKey}`,
|
|
3174
|
+
"Content-Type": "application/json",
|
|
3175
|
+
},
|
|
3176
|
+
body: JSON.stringify({ appName: appId }),
|
|
3177
|
+
}
|
|
3178
|
+
);
|
|
3179
|
+
|
|
3180
|
+
const data = await res.json().catch(() => []);
|
|
3181
|
+
if (!res.ok) {
|
|
3182
|
+
error(data.error || "파일 목록 조회 실패");
|
|
3183
|
+
return;
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
const files = Array.isArray(data) ? data : [];
|
|
3187
|
+
if (files.length === 0) {
|
|
3188
|
+
info("저장된 파일이 없습니다.");
|
|
3189
|
+
log("");
|
|
3190
|
+
return;
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
// 테이블 형식 출력
|
|
3194
|
+
const idWidth = Math.max(12, ...files.map(f => (f.storage_id || "").length));
|
|
3195
|
+
const nameWidth = Math.max(8, ...files.map(f => (f.name || "").length).map(l => Math.min(l, 40)));
|
|
3196
|
+
|
|
3197
|
+
log(` ${DIM}${"ID".padEnd(idWidth)} ${"이름".padEnd(nameWidth)} ${"크기".padStart(10)} ${"소스".padEnd(10)} 업로드 시각${RESET}`);
|
|
3198
|
+
log(` ${DIM}${"─".repeat(idWidth)} ${"─".repeat(nameWidth)} ${"─".repeat(10)} ${"─".repeat(10)} ${"─".repeat(16)}${RESET}`);
|
|
3199
|
+
|
|
3200
|
+
for (const f of files) {
|
|
3201
|
+
const name = (f.name || "").length > 40 ? f.name.slice(0, 37) + "..." : (f.name || "");
|
|
3202
|
+
const size = fmtFileSize(Number(f.size) || 0).padStart(10);
|
|
3203
|
+
const source = (f.uploaded_by || "api").padEnd(10);
|
|
3204
|
+
const date = f.created_at ? fmtDate(f.created_at) : "-";
|
|
3205
|
+
log(` ${(f.storage_id || "").padEnd(idWidth)} ${name.padEnd(nameWidth)} ${size} ${source} ${date}`);
|
|
3206
|
+
}
|
|
3207
|
+
log(`\n ${DIM}총 ${files.length}개 파일${RESET}\n`);
|
|
3208
|
+
break;
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
case "delete":
|
|
3212
|
+
case "rm": {
|
|
3213
|
+
const storageId = restArgs.find((a, i) => !a.startsWith("-") && !flagIndices.has(i));
|
|
3214
|
+
if (!storageId) {
|
|
3215
|
+
error("사용법: gencow files delete <storage_id>");
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
// 확인 프롬프트
|
|
3220
|
+
const skipConfirm = restArgs.includes("--yes") || restArgs.includes("-y");
|
|
3221
|
+
if (!skipConfirm) {
|
|
3222
|
+
const { createInterface } = await import("readline");
|
|
3223
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3224
|
+
const answer = await new Promise(resolve => {
|
|
3225
|
+
rl.question(`\n ${YELLOW}정말 삭제하시겠습니까?${RESET} ${DIM}${storageId}${RESET} (y/N): `, a => {
|
|
3226
|
+
rl.close();
|
|
3227
|
+
resolve(a.trim().toLowerCase());
|
|
3228
|
+
});
|
|
3229
|
+
});
|
|
3230
|
+
if (answer !== "y" && answer !== "yes") {
|
|
3231
|
+
info("삭제를 취소했습니다.");
|
|
3232
|
+
return;
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
const res = await fetch(
|
|
3237
|
+
`${creds.platformUrl}/platform/files/delete`,
|
|
3238
|
+
{
|
|
3239
|
+
method: "POST",
|
|
3240
|
+
headers: {
|
|
3241
|
+
"Authorization": `Bearer ${creds.apiKey}`,
|
|
3242
|
+
"Content-Type": "application/json",
|
|
3243
|
+
},
|
|
3244
|
+
body: JSON.stringify({ appName: appId, storageId }),
|
|
3245
|
+
}
|
|
3246
|
+
);
|
|
3247
|
+
|
|
3248
|
+
const data = await res.json().catch(() => ({}));
|
|
3249
|
+
if (res.ok && data.success) {
|
|
3250
|
+
success(`파일 삭제 완료: ${storageId}`);
|
|
3251
|
+
} else {
|
|
3252
|
+
error(`삭제 실패: ${data.error || res.statusText}`);
|
|
3253
|
+
}
|
|
3254
|
+
break;
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
case "url": {
|
|
3258
|
+
const storageId = restArgs.find((a, i) => !a.startsWith("-") && !flagIndices.has(i));
|
|
3259
|
+
if (!storageId) {
|
|
3260
|
+
error("사용법: gencow files url <storage_id>");
|
|
3261
|
+
return;
|
|
3262
|
+
}
|
|
3263
|
+
log(getFileUrl(storageId));
|
|
3264
|
+
break;
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3267
|
+
default:
|
|
3268
|
+
if (subCmd !== "help") error(`알 수 없는 하위 명령: ${subCmd}`);
|
|
3269
|
+
log(`\n${BOLD}${CYAN}gencow files${RESET} — 파일 관리\n`);
|
|
3270
|
+
log(` ${CYAN}upload${RESET} <경로...> [--recursive|-r] 파일 업로드`);
|
|
3271
|
+
log(` ${CYAN}list${RESET} 파일 목록`);
|
|
3272
|
+
log(` ${CYAN}delete${RESET} <storage_id> [--yes|-y] 파일 삭제`);
|
|
3273
|
+
log(` ${CYAN}url${RESET} <storage_id> 서빙 URL 출력`);
|
|
3274
|
+
log(`\n ${DIM}옵션:${RESET}`);
|
|
3275
|
+
log(` ${DIM}--app, -a <앱이름> 대상 앱 지정 (기본: gencow.json)${RESET}\n`);
|
|
3276
|
+
}
|
|
3277
|
+
},
|
|
3278
|
+
|
|
2984
3279
|
// ── backup ─────────────────────────────────────────────
|
|
2985
3280
|
async backup(...backupArgs) {
|
|
2986
3281
|
const creds = requireCreds();
|
package/core/index.js
CHANGED
|
@@ -1402,7 +1402,6 @@ function query(key, handlerOrDef) {
|
|
|
1402
1402
|
}
|
|
1403
1403
|
var mutationCounter = 0;
|
|
1404
1404
|
function mutation(nameOrInvalidatesOrDef, handlerOrDef, name) {
|
|
1405
|
-
let invalidates;
|
|
1406
1405
|
let argsSchema;
|
|
1407
1406
|
let actualHandler;
|
|
1408
1407
|
let mutName;
|
|
@@ -1410,16 +1409,13 @@ function mutation(nameOrInvalidatesOrDef, handlerOrDef, name) {
|
|
|
1410
1409
|
if (typeof nameOrInvalidatesOrDef === "string") {
|
|
1411
1410
|
mutName = nameOrInvalidatesOrDef;
|
|
1412
1411
|
const def2 = handlerOrDef;
|
|
1413
|
-
invalidates = def2.invalidates || [];
|
|
1414
1412
|
actualHandler = def2.handler;
|
|
1415
1413
|
argsSchema = def2.args;
|
|
1416
1414
|
isPublic = def2.public === true;
|
|
1417
1415
|
} else if (Array.isArray(nameOrInvalidatesOrDef)) {
|
|
1418
|
-
invalidates = nameOrInvalidatesOrDef;
|
|
1419
1416
|
actualHandler = handlerOrDef;
|
|
1420
1417
|
mutName = name || `mutation_${++mutationCounter}`;
|
|
1421
1418
|
} else {
|
|
1422
|
-
invalidates = nameOrInvalidatesOrDef.invalidates;
|
|
1423
1419
|
actualHandler = nameOrInvalidatesOrDef.handler;
|
|
1424
1420
|
argsSchema = nameOrInvalidatesOrDef.args;
|
|
1425
1421
|
isPublic = nameOrInvalidatesOrDef.public === true;
|
|
@@ -1432,7 +1428,6 @@ function mutation(nameOrInvalidatesOrDef, handlerOrDef, name) {
|
|
|
1432
1428
|
}
|
|
1433
1429
|
const def = {
|
|
1434
1430
|
name: mutName,
|
|
1435
|
-
invalidates,
|
|
1436
1431
|
handler: actualHandler,
|
|
1437
1432
|
argsSchema,
|
|
1438
1433
|
isPublic
|
|
@@ -1477,8 +1472,11 @@ function deregisterClient(ws) {
|
|
|
1477
1472
|
}
|
|
1478
1473
|
function buildRealtimeCtx(options) {
|
|
1479
1474
|
const pendingEmits = /* @__PURE__ */ new Map();
|
|
1475
|
+
const _pendingRefresh = [];
|
|
1476
|
+
let _hasEmitted = false;
|
|
1480
1477
|
return {
|
|
1481
1478
|
emit(queryKey, data) {
|
|
1479
|
+
_hasEmitted = true;
|
|
1482
1480
|
const existing = pendingEmits.get(queryKey);
|
|
1483
1481
|
if (existing) clearTimeout(existing.timer);
|
|
1484
1482
|
const timer = setTimeout(() => {
|
|
@@ -1503,24 +1501,58 @@ function buildRealtimeCtx(options) {
|
|
|
1503
1501
|
}
|
|
1504
1502
|
}, 50);
|
|
1505
1503
|
pendingEmits.set(queryKey, { data, timer });
|
|
1504
|
+
},
|
|
1505
|
+
refresh(queryKey) {
|
|
1506
|
+
_hasEmitted = true;
|
|
1507
|
+
if (!_pendingRefresh.includes(queryKey)) {
|
|
1508
|
+
_pendingRefresh.push(queryKey);
|
|
1509
|
+
}
|
|
1510
|
+
},
|
|
1511
|
+
get _hasEmitted() {
|
|
1512
|
+
return _hasEmitted;
|
|
1513
|
+
},
|
|
1514
|
+
get _pendingRefresh() {
|
|
1515
|
+
return [..._pendingRefresh];
|
|
1516
|
+
},
|
|
1517
|
+
async _flushRefresh() {
|
|
1518
|
+
if (_pendingRefresh.length === 0) return;
|
|
1519
|
+
const qMap = options?.queryMap ?? queryRegistry;
|
|
1520
|
+
for (const key of _pendingRefresh) {
|
|
1521
|
+
const queryDef = qMap.get(key);
|
|
1522
|
+
if (!queryDef) {
|
|
1523
|
+
console.warn(`[gencow] refresh("${key}"): query not found in registry. Skipping.`);
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
try {
|
|
1527
|
+
const refreshCtx = options?.buildCtxForRefresh?.() ?? {};
|
|
1528
|
+
const result = await queryDef.handler(refreshCtx, {});
|
|
1529
|
+
if (options?.httpCallback) {
|
|
1530
|
+
options.httpCallback({ type: "emit", queryKey: key, data: result });
|
|
1531
|
+
} else {
|
|
1532
|
+
const clients = subscribers.get(key);
|
|
1533
|
+
if (clients && clients.size > 0) {
|
|
1534
|
+
const message = JSON.stringify({
|
|
1535
|
+
type: "query:updated",
|
|
1536
|
+
query: key,
|
|
1537
|
+
data: result
|
|
1538
|
+
});
|
|
1539
|
+
for (const ws of clients) {
|
|
1540
|
+
try {
|
|
1541
|
+
ws.send(message);
|
|
1542
|
+
} catch {
|
|
1543
|
+
clients.delete(ws);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
} catch (e) {
|
|
1549
|
+
console.warn(`[gencow] refresh("${key}") failed:`, e instanceof Error ? e.message : e);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
_pendingRefresh.length = 0;
|
|
1506
1553
|
}
|
|
1507
1554
|
};
|
|
1508
1555
|
}
|
|
1509
|
-
async function invalidateQueries(queryKeys, ctx, httpInvalidateCallback) {
|
|
1510
|
-
if (queryKeys.length === 0) return;
|
|
1511
|
-
if (httpInvalidateCallback) {
|
|
1512
|
-
httpInvalidateCallback(queryKeys);
|
|
1513
|
-
return;
|
|
1514
|
-
}
|
|
1515
|
-
const invalidateMsg = JSON.stringify({ type: "invalidate", queries: queryKeys });
|
|
1516
|
-
for (const ws of connectedClients) {
|
|
1517
|
-
try {
|
|
1518
|
-
ws.send(invalidateMsg);
|
|
1519
|
-
} catch {
|
|
1520
|
-
connectedClients.delete(ws);
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
1556
|
function handleWsMessage(ws, raw) {
|
|
1525
1557
|
try {
|
|
1526
1558
|
const msg = typeof raw === "string" ? JSON.parse(raw) : JSON.parse(raw.toString());
|
|
@@ -2075,7 +2107,6 @@ function crud(table, options) {
|
|
|
2075
2107
|
});
|
|
2076
2108
|
const createDef = !enabledMethods.has("create") ? void 0 : mutation(`${prefix}.create`, {
|
|
2077
2109
|
public: isPublic,
|
|
2078
|
-
invalidates: [],
|
|
2079
2110
|
handler: async (ctx, args) => {
|
|
2080
2111
|
const user = isPublic ? null : ctx.auth.requireAuth();
|
|
2081
2112
|
let insertData = { ...args };
|
|
@@ -2095,7 +2126,6 @@ function crud(table, options) {
|
|
|
2095
2126
|
});
|
|
2096
2127
|
const updateDef = !enabledMethods.has("update") ? void 0 : mutation(`${prefix}.update`, {
|
|
2097
2128
|
public: isPublic,
|
|
2098
|
-
invalidates: [],
|
|
2099
2129
|
handler: async (ctx, args) => {
|
|
2100
2130
|
if (!isPublic) ctx.auth.requireAuth();
|
|
2101
2131
|
const { id, ...updates } = args;
|
|
@@ -2121,7 +2151,6 @@ function crud(table, options) {
|
|
|
2121
2151
|
});
|
|
2122
2152
|
const removeDef = !enabledMethods.has("remove") ? void 0 : mutation(`${prefix}.remove`, {
|
|
2123
2153
|
public: isPublic,
|
|
2124
|
-
invalidates: [],
|
|
2125
2154
|
handler: async (ctx, args) => {
|
|
2126
2155
|
if (!isPublic) ctx.auth.requireAuth();
|
|
2127
2156
|
if (options?.softDelete) {
|
|
@@ -2164,7 +2193,6 @@ export {
|
|
|
2164
2193
|
getSchedulerInfo,
|
|
2165
2194
|
handleWsMessage,
|
|
2166
2195
|
httpAction,
|
|
2167
|
-
invalidateQueries,
|
|
2168
2196
|
mutation,
|
|
2169
2197
|
ownerRls,
|
|
2170
2198
|
parseArgs,
|