gencow 0.1.113 → 0.1.115

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/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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gencow",
3
- "version": "0.1.113",
3
+ "version": "0.1.115",
4
4
  "description": "Gencow — AI Backend Engine",
5
5
  "type": "module",
6
6
  "bin": {
package/server/index.js CHANGED
@@ -54,7 +54,6 @@ function query(key, handlerOrDef) {
54
54
  return def;
55
55
  }
56
56
  function mutation(nameOrInvalidatesOrDef, handlerOrDef, name21) {
57
- let invalidates;
58
57
  let argsSchema;
59
58
  let actualHandler;
60
59
  let mutName;
@@ -62,16 +61,13 @@ function mutation(nameOrInvalidatesOrDef, handlerOrDef, name21) {
62
61
  if (typeof nameOrInvalidatesOrDef === "string") {
63
62
  mutName = nameOrInvalidatesOrDef;
64
63
  const def2 = handlerOrDef;
65
- invalidates = def2.invalidates || [];
66
64
  actualHandler = def2.handler;
67
65
  argsSchema = def2.args;
68
66
  isPublic = def2.public === true;
69
67
  } else if (Array.isArray(nameOrInvalidatesOrDef)) {
70
- invalidates = nameOrInvalidatesOrDef;
71
68
  actualHandler = handlerOrDef;
72
69
  mutName = name21 || `mutation_${++mutationCounter}`;
73
70
  } else {
74
- invalidates = nameOrInvalidatesOrDef.invalidates;
75
71
  actualHandler = nameOrInvalidatesOrDef.handler;
76
72
  argsSchema = nameOrInvalidatesOrDef.args;
77
73
  isPublic = nameOrInvalidatesOrDef.public === true;
@@ -84,7 +80,6 @@ function mutation(nameOrInvalidatesOrDef, handlerOrDef, name21) {
84
80
  }
85
81
  const def = {
86
82
  name: mutName,
87
- invalidates,
88
83
  handler: actualHandler,
89
84
  argsSchema,
90
85
  isPublic
@@ -129,8 +124,11 @@ function deregisterClient(ws) {
129
124
  }
130
125
  function buildRealtimeCtx(options) {
131
126
  const pendingEmits = /* @__PURE__ */ new Map();
127
+ const _pendingRefresh = [];
128
+ let _hasEmitted = false;
132
129
  return {
133
130
  emit(queryKey, data) {
131
+ _hasEmitted = true;
134
132
  const existing = pendingEmits.get(queryKey);
135
133
  if (existing) clearTimeout(existing.timer);
136
134
  const timer = setTimeout(() => {
@@ -155,24 +153,58 @@ function buildRealtimeCtx(options) {
155
153
  }
156
154
  }, 50);
157
155
  pendingEmits.set(queryKey, { data, timer });
156
+ },
157
+ refresh(queryKey) {
158
+ _hasEmitted = true;
159
+ if (!_pendingRefresh.includes(queryKey)) {
160
+ _pendingRefresh.push(queryKey);
161
+ }
162
+ },
163
+ get _hasEmitted() {
164
+ return _hasEmitted;
165
+ },
166
+ get _pendingRefresh() {
167
+ return [..._pendingRefresh];
168
+ },
169
+ async _flushRefresh() {
170
+ if (_pendingRefresh.length === 0) return;
171
+ const qMap = options?.queryMap ?? queryRegistry;
172
+ for (const key of _pendingRefresh) {
173
+ const queryDef = qMap.get(key);
174
+ if (!queryDef) {
175
+ console.warn(`[gencow] refresh("${key}"): query not found in registry. Skipping.`);
176
+ continue;
177
+ }
178
+ try {
179
+ const refreshCtx = options?.buildCtxForRefresh?.() ?? {};
180
+ const result = await queryDef.handler(refreshCtx, {});
181
+ if (options?.httpCallback) {
182
+ options.httpCallback({ type: "emit", queryKey: key, data: result });
183
+ } else {
184
+ const clients = subscribers.get(key);
185
+ if (clients && clients.size > 0) {
186
+ const message = JSON.stringify({
187
+ type: "query:updated",
188
+ query: key,
189
+ data: result
190
+ });
191
+ for (const ws of clients) {
192
+ try {
193
+ ws.send(message);
194
+ } catch {
195
+ clients.delete(ws);
196
+ }
197
+ }
198
+ }
199
+ }
200
+ } catch (e) {
201
+ console.warn(`[gencow] refresh("${key}") failed:`, e instanceof Error ? e.message : e);
202
+ }
203
+ }
204
+ _pendingRefresh.length = 0;
158
205
  }
159
206
  };
160
207
  }
161
- async function invalidateQueries(queryKeys, ctx, httpInvalidateCallback) {
162
- if (queryKeys.length === 0) return;
163
- if (httpInvalidateCallback) {
164
- httpInvalidateCallback(queryKeys);
165
- return;
166
- }
167
- const invalidateMsg = JSON.stringify({ type: "invalidate", queries: queryKeys });
168
- for (const ws of connectedClients) {
169
- try {
170
- ws.send(invalidateMsg);
171
- } catch {
172
- connectedClients.delete(ws);
173
- }
174
- }
175
- }
176
208
  function handleWsMessage(ws, raw2) {
177
209
  try {
178
210
  const msg = typeof raw2 === "string" ? JSON.parse(raw2) : JSON.parse(raw2.toString());
@@ -2124,7 +2156,6 @@ function crud(table, options) {
2124
2156
  });
2125
2157
  const createDef = !enabledMethods.has("create") ? void 0 : mutation(`${prefix}.create`, {
2126
2158
  public: isPublic,
2127
- invalidates: [],
2128
2159
  handler: async (ctx, args) => {
2129
2160
  const user2 = isPublic ? null : ctx.auth.requireAuth();
2130
2161
  let insertData = { ...args };
@@ -2144,7 +2175,6 @@ function crud(table, options) {
2144
2175
  });
2145
2176
  const updateDef = !enabledMethods.has("update") ? void 0 : mutation(`${prefix}.update`, {
2146
2177
  public: isPublic,
2147
- invalidates: [],
2148
2178
  handler: async (ctx, args) => {
2149
2179
  if (!isPublic) ctx.auth.requireAuth();
2150
2180
  const { id, ...updates } = args;
@@ -2170,7 +2200,6 @@ function crud(table, options) {
2170
2200
  });
2171
2201
  const removeDef = !enabledMethods.has("remove") ? void 0 : mutation(`${prefix}.remove`, {
2172
2202
  public: isPublic,
2173
- invalidates: [],
2174
2203
  handler: async (ctx, args) => {
2175
2204
  if (!isPublic) ctx.auth.requireAuth();
2176
2205
  if (options?.softDelete) {
@@ -2226,7 +2255,6 @@ __export(src_exports, {
2226
2255
  getSchedulerInfo: () => getSchedulerInfo,
2227
2256
  handleWsMessage: () => handleWsMessage,
2228
2257
  httpAction: () => httpAction,
2229
- invalidateQueries: () => invalidateQueries,
2230
2258
  mutation: () => mutation,
2231
2259
  ownerRls: () => ownerRls,
2232
2260
  parseArgs: () => parseArgs,
@@ -52048,9 +52076,13 @@ function createAdminRoutes(db, rawSql, driver, getCtx, storage, functionsPath) {
52048
52076
  const related = allQueries.filter((q) => q.includes(table));
52049
52077
  if (related.length > 0) {
52050
52078
  try {
52051
- await invalidateQueries(related, getCtx());
52079
+ const ctx = getCtx();
52080
+ const rtx = buildRealtimeCtx();
52081
+ for (const queryKey of related) {
52082
+ rtx.emit(queryKey, null);
52083
+ }
52052
52084
  } catch (e) {
52053
- console.warn("[admin] invalidation failed:", e);
52085
+ console.warn("[admin] realtime push failed:", e);
52054
52086
  }
52055
52087
  }
52056
52088
  }
@@ -52722,7 +52754,6 @@ var handleOpen = mod.handleOpen;
52722
52754
  var handleMessage = mod.handleMessage;
52723
52755
  var handleClose = mod.handleClose;
52724
52756
  var notifyEmit = mod.notifyEmit;
52725
- var notifyInvalidation = mod.notifyInvalidation;
52726
52757
  var getConnectionCount = mod.getConnectionCount;
52727
52758
  var getStats = mod.getStats;
52728
52759
  var startHeartbeat = mod.startHeartbeat;
@@ -53239,7 +53270,7 @@ async function main() {
53239
53270
  var invalidateDomainCache = invalidateDomainCache2, cronCalculateNextRun = cronCalculateNextRun2;
53240
53271
  const { addProxyMetric } = await import(resolve6(functionsPath, "../src/proxy-metrics.ts"));
53241
53272
  const { apps: appsTable } = await import(resolve6(functionsPath, "schema.ts"));
53242
- const { eq: eqOp, inArray: inArrayOp } = await import("drizzle-orm");
53273
+ const { eq: eqOp, and: andOp, inArray: inArrayOp } = await import("drizzle-orm");
53243
53274
  const { getAppPort: getLivePort } = await import(resolve6(functionsPath, "../src/provisioner.ts"));
53244
53275
  const domainCache = /* @__PURE__ */ new Map();
53245
53276
  const CACHE_TTL = 5 * 60 * 1e3;
@@ -53249,11 +53280,17 @@ async function main() {
53249
53280
  if (cached2 && cached2.expiresAt > Date.now()) {
53250
53281
  return cached2.appName || null;
53251
53282
  }
53252
- const rows = await db.select({ name: appsTable.name }).from(appsTable).where(eqOp(appsTable.customDomain, host));
53283
+ const rows = await db.select({ name: appsTable.name }).from(appsTable).where(andOp(
53284
+ eqOp(appsTable.customDomain, host),
53285
+ eqOp(appsTable.customDomainStatus, "active")
53286
+ ));
53253
53287
  if (rows.length === 0) {
53254
53288
  if (host.startsWith("www.")) {
53255
53289
  const bare = host.slice(4);
53256
- const bareRows = await db.select({ name: appsTable.name }).from(appsTable).where(eqOp(appsTable.customDomain, bare));
53290
+ const bareRows = await db.select({ name: appsTable.name }).from(appsTable).where(andOp(
53291
+ eqOp(appsTable.customDomain, bare),
53292
+ eqOp(appsTable.customDomainStatus, "active")
53293
+ ));
53257
53294
  if (bareRows.length > 0) {
53258
53295
  domainCache.set(host, { appName: bareRows[0].name, expiresAt: Date.now() + CACHE_TTL });
53259
53296
  return bareRows[0].name;
@@ -53275,6 +53312,11 @@ async function main() {
53275
53312
  let appName = match2?.[1] || null;
53276
53313
  if (!appName) {
53277
53314
  appName = await resolveCustomDomain(host);
53315
+ if (appName && host.startsWith("www.")) {
53316
+ const bare = host.slice(4);
53317
+ const url2 = new URL(c.req.url);
53318
+ return c.redirect(`https://${bare}${url2.pathname}${url2.search}`, 301);
53319
+ }
53278
53320
  }
53279
53321
  if (!appName) return next();
53280
53322
  try {
@@ -53829,10 +53871,6 @@ async function main() {
53829
53871
  const sent = notifyEmit(notifyAppName, body.queryKey, body.data);
53830
53872
  return c.json({ ok: true, sent });
53831
53873
  }
53832
- if (type === "invalidate") {
53833
- const sent = notifyInvalidation(notifyAppName, body.queries || []);
53834
- return c.json({ ok: true, sent });
53835
- }
53836
53874
  return c.json({ error: `Unknown type: ${type}` }, 400);
53837
53875
  } catch (e) {
53838
53876
  console.error("[ws-gateway] notify error:", e.message);
@@ -54208,13 +54246,15 @@ async function main() {
54208
54246
  "mutation",
54209
54247
  name21
54210
54248
  );
54211
- await invalidateQueries(
54212
- mut.invalidates,
54213
- ctx,
54214
- IS_BAAS ? (queryKeys) => {
54215
- notifyGateway({ type: "invalidate", queries: queryKeys });
54216
- } : void 0
54217
- );
54249
+ const rtx = ctx.realtime;
54250
+ if (rtx._flushRefresh) {
54251
+ await rtx._flushRefresh();
54252
+ }
54253
+ if (rtx._hasEmitted !== void 0 && !rtx._hasEmitted) {
54254
+ console.warn(
54255
+ `[gencow] \u26A0\uFE0F mutation "${name21}" completed without ctx.realtime.emit() or ctx.realtime.refresh(). Subscribed clients won't see updates. \u{1F4A1} Add ctx.realtime.refresh("queryKey") to push changes.`
54256
+ );
54257
+ }
54218
54258
  return c.json(result ?? null, 201);
54219
54259
  } catch (err) {
54220
54260
  const status = err?.code === "FUNCTION_TIMEOUT" ? 408 : err instanceof GencowValidationError ? 400 : 500;