gencow 0.1.120 → 0.1.121

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
@@ -1928,7 +1928,7 @@ function ownerRls(userIdColumn, options) {
1928
1928
  "[ownerRls] userIdColumn must have a .name property. Ensure you pass a valid Drizzle column reference (e.g. t.userId)."
1929
1929
  );
1930
1930
  }
1931
- const isOwner = sql`${userIdColumn} = current_setting('app.current_user_id')`;
1931
+ const isOwner = sql`${userIdColumn} = current_setting('app.current_user_id', true)`;
1932
1932
  const meta = {
1933
1933
  columnName: colName,
1934
1934
  readPublic: options?.read === "public"
@@ -1966,6 +1966,10 @@ function createRlsDb(db, userId) {
1966
1966
  // ../core/src/crud.ts
1967
1967
  import { eq, ne, gt, gte, lt, lte, desc, asc, like, ilike, inArray, notInArray, or, and, count as drizzleCount, getTableName, getTableColumns } from "drizzle-orm";
1968
1968
  import { getTableConfig } from "drizzle-orm/pg-core";
1969
+ var _ownerRlsTables = [];
1970
+ function getOwnerRlsTables() {
1971
+ return _ownerRlsTables;
1972
+ }
1969
1973
  function detectIdType(column) {
1970
1974
  const colType = column.dataType;
1971
1975
  if (colType === "string") return v.string();
@@ -2001,6 +2005,10 @@ function detectOwnerMeta(table) {
2001
2005
  registerOwnerRls(table, { columnName: colName, readPublic: false });
2002
2006
  return { column: userIdCol, columnName: colName, propertyName: propName, readPublic: false };
2003
2007
  }
2008
+ const tblName = getTableName(table);
2009
+ console.warn(
2010
+ `[crud] \u26A0\uFE0F Table "${tblName}" has ${config.policies.length} pgPolicy but no userId/user_id column found. ownerRls auto-isolation will NOT be applied. If you used ownerRls(), ensure the column is named 'userId' (JS) / 'user_id' (DB).`
2011
+ );
2004
2012
  }
2005
2013
  } catch {
2006
2014
  }
@@ -2088,6 +2096,13 @@ function crud(table, options) {
2088
2096
  const defaultOrderCol = createdAtCol || pk;
2089
2097
  const userIdCol = anyTable["userId"];
2090
2098
  const ownerMeta = detectOwnerMeta(table);
2099
+ if (ownerMeta && !_ownerRlsTables.some((t) => t.tableName === tableName)) {
2100
+ _ownerRlsTables.push({
2101
+ tableName,
2102
+ columnName: ownerMeta.columnName,
2103
+ readPublic: ownerMeta.readPublic
2104
+ });
2105
+ }
2091
2106
  if (ownerMeta && isPublic && !ownerMeta.readPublic) {
2092
2107
  console.warn(
2093
2108
  `[crud] \u26A0\uFE0F Table "${tableName}": ownerRls detected but public=true. CUD operations will still enforce ownerRls (auth required). Consider removing { public: true } or using ownerRls(col, { read: "public" }).`
@@ -2116,15 +2131,23 @@ function crud(table, options) {
2116
2131
  }
2117
2132
  return conditions.length > 0 ? and(...conditions) : void 0;
2118
2133
  }
2134
+ async function inRlsOrPlainTx(db, fn) {
2135
+ if (typeof db?.transaction === "function") {
2136
+ return await db.transaction(fn);
2137
+ }
2138
+ return await fn(db);
2139
+ }
2119
2140
  async function fetchListWithTotal(db, whereClause, userId) {
2120
2141
  let effectiveWhere = whereClause;
2121
2142
  if (ownerMeta && userId && !ownerMeta.readPublic) {
2122
2143
  const ownerFilter = eq(ownerMeta.column, userId);
2123
2144
  effectiveWhere = effectiveWhere ? and(effectiveWhere, ownerFilter) : ownerFilter;
2124
2145
  }
2125
- const data = await db.select().from(anyTable).where(effectiveWhere).orderBy(desc(defaultOrderCol));
2126
- const countResult = await db.select({ count: drizzleCount() }).from(anyTable).where(effectiveWhere);
2127
- return { data, total: Number(countResult[0]?.count ?? 0) };
2146
+ return await inRlsOrPlainTx(db, async (tx) => {
2147
+ const data = await tx.select().from(anyTable).where(effectiveWhere).orderBy(desc(defaultOrderCol));
2148
+ const countResult = await tx.select({ count: drizzleCount() }).from(anyTable).where(effectiveWhere);
2149
+ return { data, total: Number(countResult[0]?.count ?? 0) };
2150
+ });
2128
2151
  }
2129
2152
  const enabledMethods = new Set(options?.methods ?? ["list", "get", "create", "update", "remove"]);
2130
2153
  const listDef = !enabledMethods.has("list") ? void 0 : query(`${prefix}.list`, {
@@ -2155,12 +2178,14 @@ function crud(table, options) {
2155
2178
  } else {
2156
2179
  orderByClause = desc(defaultOrderCol);
2157
2180
  }
2158
- const results = await ctx.db.select().from(anyTable).where(whereClause).orderBy(orderByClause).limit(limit).offset(offset);
2159
- const countResult = await ctx.db.select({ count: drizzleCount() }).from(anyTable).where(whereClause);
2160
- return {
2161
- data: results,
2162
- total: Number(countResult[0]?.count ?? 0)
2163
- };
2181
+ return await inRlsOrPlainTx(ctx.db, async (tx) => {
2182
+ const results = await tx.select().from(anyTable).where(whereClause).orderBy(orderByClause).limit(limit).offset(offset);
2183
+ const countResult = await tx.select({ count: drizzleCount() }).from(anyTable).where(whereClause);
2184
+ return {
2185
+ data: results,
2186
+ total: Number(countResult[0]?.count ?? 0)
2187
+ };
2188
+ });
2164
2189
  }
2165
2190
  });
2166
2191
  const getDef = !enabledMethods.has("get") ? void 0 : query(`${prefix}.get`, {
@@ -2177,8 +2202,10 @@ function crud(table, options) {
2177
2202
  const sdField = anyTable[options.softDelete.field];
2178
2203
  whereCond = and(whereCond, eq(sdField, null));
2179
2204
  }
2180
- const [result] = await ctx.db.select().from(anyTable).where(whereCond).limit(1);
2181
- return result ?? null;
2205
+ return await inRlsOrPlainTx(ctx.db, async (tx) => {
2206
+ const [result] = await tx.select().from(anyTable).where(whereCond).limit(1);
2207
+ return result ?? null;
2208
+ });
2182
2209
  }
2183
2210
  });
2184
2211
  const createDef = !enabledMethods.has("create") ? void 0 : mutation(`${prefix}.create`, {
@@ -2186,6 +2213,12 @@ function crud(table, options) {
2186
2213
  handler: async (ctx, args) => {
2187
2214
  const user = ownerMeta || !isPublic ? ctx.auth.requireAuth() : null;
2188
2215
  let insertData = { ...args };
2216
+ if (ownerMeta && user) {
2217
+ const requestedOwner = insertData[ownerMeta.propertyName] ?? insertData[ownerMeta.columnName] ?? insertData.userId;
2218
+ if (requestedOwner != null && requestedOwner !== user.id) {
2219
+ throw new Error("Forbidden: cannot create resource for another user");
2220
+ }
2221
+ }
2189
2222
  if (ownerMeta && user) {
2190
2223
  insertData[ownerMeta.propertyName] = user.id;
2191
2224
  } else if (userIdCol && user && !insertData.userId) {
@@ -2194,7 +2227,10 @@ function crud(table, options) {
2194
2227
  if (options?.hooks?.beforeCreate) {
2195
2228
  insertData = await options.hooks.beforeCreate(insertData);
2196
2229
  }
2197
- const [result] = await ctx.db.insert(anyTable).values(insertData).returning();
2230
+ const [result] = await inRlsOrPlainTx(
2231
+ ctx.db,
2232
+ async (tx) => tx.insert(anyTable).values(insertData).returning()
2233
+ );
2198
2234
  if (useRealtime && enabledMethods.has("list")) {
2199
2235
  const currentUserId = user?.id;
2200
2236
  const listResult = await fetchListWithTotal(ctx.db, void 0, currentUserId);
@@ -2223,7 +2259,10 @@ function crud(table, options) {
2223
2259
  if (options?.hooks?.beforeUpdate) {
2224
2260
  updateData = await options.hooks.beforeUpdate(updateData);
2225
2261
  }
2226
- const [result] = await ctx.db.update(anyTable).set(updateData).where(updateWhere).returning();
2262
+ const [result] = await inRlsOrPlainTx(
2263
+ ctx.db,
2264
+ async (tx) => tx.update(anyTable).set(updateData).where(updateWhere).returning()
2265
+ );
2227
2266
  if (useRealtime) {
2228
2267
  const currentUserId = ownerMeta ? user?.id : void 0;
2229
2268
  if (enabledMethods.has("list")) {
@@ -2245,12 +2284,14 @@ function crud(table, options) {
2245
2284
  if (ownerMeta && user) {
2246
2285
  deleteWhere = and(eq(pk, args.id), eq(ownerMeta.column, user.id));
2247
2286
  }
2248
- if (options?.softDelete) {
2249
- const sdField = options.softDelete.field;
2250
- await ctx.db.update(anyTable).set({ [sdField]: /* @__PURE__ */ new Date() }).where(deleteWhere);
2251
- } else {
2252
- await ctx.db.delete(anyTable).where(deleteWhere);
2253
- }
2287
+ await inRlsOrPlainTx(ctx.db, async (tx) => {
2288
+ if (options?.softDelete) {
2289
+ const sdField = options.softDelete.field;
2290
+ await tx.update(anyTable).set({ [sdField]: /* @__PURE__ */ new Date() }).where(deleteWhere);
2291
+ } else {
2292
+ await tx.delete(anyTable).where(deleteWhere);
2293
+ }
2294
+ });
2254
2295
  if (useRealtime && enabledMethods.has("list")) {
2255
2296
  const currentUserId = ownerMeta ? user?.id : void 0;
2256
2297
  const listResult = await fetchListWithTotal(ctx.db, void 0, currentUserId);
@@ -2279,6 +2320,7 @@ export {
2279
2320
  deregisterClient,
2280
2321
  crud as gencowCrud,
2281
2322
  getOwnerRlsMeta,
2323
+ getOwnerRlsTables,
2282
2324
  getQueryDef,
2283
2325
  getQueryHandler,
2284
2326
  getRegisteredHttpActions,
package/package.json CHANGED
@@ -1,33 +1,32 @@
1
1
  {
2
- "name": "gencow",
3
- "version": "0.1.120",
4
- "description": "Gencow — AI Backend Engine",
5
- "type": "module",
6
- "bin": {
7
- "gencow": "./bin/gencow.mjs"
8
- },
9
- "files": [
10
- "bin/",
11
- "lib/",
12
- "templates/",
13
- "server/",
14
- "core/",
15
- "scripts/",
16
- "dashboard/"
17
- ],
18
- "scripts": {
19
- "build": "node scripts/bundle-server.mjs",
20
- "prepublishOnly": "npm run build && node scripts/pre-publish-check.mjs"
21
- },
22
- "dependencies": {
23
- "@modelcontextprotocol/sdk": "^1.27.1",
24
- "open": "^10.1.0",
25
- "tar": "^7",
26
- "ws": "^8.19.0",
27
- "zod": "^4.3.6"
28
- },
29
- "devDependencies": {
30
- "@types/node": "^25",
31
- "esbuild": "^0.27.3"
32
- }
33
- }
2
+ "name": "gencow",
3
+ "version": "0.1.121",
4
+ "description": "Gencow — AI Backend Engine",
5
+ "type": "module",
6
+ "bin": {
7
+ "gencow": "./bin/gencow.mjs"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "lib/",
12
+ "templates/",
13
+ "server/",
14
+ "core/",
15
+ "scripts/",
16
+ "dashboard/"
17
+ ],
18
+ "dependencies": {
19
+ "@modelcontextprotocol/sdk": "^1.27.1",
20
+ "open": "^10.1.0",
21
+ "tar": "^7",
22
+ "ws": "^8.19.0",
23
+ "zod": "^4.3.6"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^25",
27
+ "esbuild": "^0.27.3"
28
+ },
29
+ "scripts": {
30
+ "build": "node scripts/bundle-server.mjs"
31
+ }
32
+ }
package/server/index.js CHANGED
@@ -1968,7 +1968,7 @@ function ownerRls(userIdColumn, options) {
1968
1968
  "[ownerRls] userIdColumn must have a .name property. Ensure you pass a valid Drizzle column reference (e.g. t.userId)."
1969
1969
  );
1970
1970
  }
1971
- const isOwner = sql`${userIdColumn} = current_setting('app.current_user_id')`;
1971
+ const isOwner = sql`${userIdColumn} = current_setting('app.current_user_id', true)`;
1972
1972
  const meta3 = {
1973
1973
  columnName: colName,
1974
1974
  readPublic: options?.read === "public"
@@ -2018,6 +2018,9 @@ var init_rls_db = __esm({
2018
2018
  // ../core/src/crud.ts
2019
2019
  import { eq, ne, gt, gte, lt, lte, desc, asc, like, ilike, inArray, notInArray, or, and, count as drizzleCount, getTableName, getTableColumns } from "drizzle-orm";
2020
2020
  import { getTableConfig } from "drizzle-orm/pg-core";
2021
+ function getOwnerRlsTables() {
2022
+ return _ownerRlsTables;
2023
+ }
2021
2024
  function detectIdType(column) {
2022
2025
  const colType = column.dataType;
2023
2026
  if (colType === "string") return v.string();
@@ -2053,6 +2056,10 @@ function detectOwnerMeta(table) {
2053
2056
  registerOwnerRls(table, { columnName: colName, readPublic: false });
2054
2057
  return { column: userIdCol, columnName: colName, propertyName: propName, readPublic: false };
2055
2058
  }
2059
+ const tblName = getTableName(table);
2060
+ console.warn(
2061
+ `[crud] \u26A0\uFE0F Table "${tblName}" has ${config2.policies.length} pgPolicy but no userId/user_id column found. ownerRls auto-isolation will NOT be applied. If you used ownerRls(), ensure the column is named 'userId' (JS) / 'user_id' (DB).`
2062
+ );
2056
2063
  }
2057
2064
  } catch {
2058
2065
  }
@@ -2138,6 +2145,13 @@ function crud(table, options) {
2138
2145
  const defaultOrderCol = createdAtCol || pk;
2139
2146
  const userIdCol = anyTable["userId"];
2140
2147
  const ownerMeta = detectOwnerMeta(table);
2148
+ if (ownerMeta && !_ownerRlsTables.some((t) => t.tableName === tableName)) {
2149
+ _ownerRlsTables.push({
2150
+ tableName,
2151
+ columnName: ownerMeta.columnName,
2152
+ readPublic: ownerMeta.readPublic
2153
+ });
2154
+ }
2141
2155
  if (ownerMeta && isPublic && !ownerMeta.readPublic) {
2142
2156
  console.warn(
2143
2157
  `[crud] \u26A0\uFE0F Table "${tableName}": ownerRls detected but public=true. CUD operations will still enforce ownerRls (auth required). Consider removing { public: true } or using ownerRls(col, { read: "public" }).`
@@ -2166,15 +2180,23 @@ function crud(table, options) {
2166
2180
  }
2167
2181
  return conditions.length > 0 ? and(...conditions) : void 0;
2168
2182
  }
2183
+ async function inRlsOrPlainTx(db, fn) {
2184
+ if (typeof db?.transaction === "function") {
2185
+ return await db.transaction(fn);
2186
+ }
2187
+ return await fn(db);
2188
+ }
2169
2189
  async function fetchListWithTotal(db, whereClause, userId) {
2170
2190
  let effectiveWhere = whereClause;
2171
2191
  if (ownerMeta && userId && !ownerMeta.readPublic) {
2172
2192
  const ownerFilter = eq(ownerMeta.column, userId);
2173
2193
  effectiveWhere = effectiveWhere ? and(effectiveWhere, ownerFilter) : ownerFilter;
2174
2194
  }
2175
- const data = await db.select().from(anyTable).where(effectiveWhere).orderBy(desc(defaultOrderCol));
2176
- const countResult = await db.select({ count: drizzleCount() }).from(anyTable).where(effectiveWhere);
2177
- return { data, total: Number(countResult[0]?.count ?? 0) };
2195
+ return await inRlsOrPlainTx(db, async (tx) => {
2196
+ const data = await tx.select().from(anyTable).where(effectiveWhere).orderBy(desc(defaultOrderCol));
2197
+ const countResult = await tx.select({ count: drizzleCount() }).from(anyTable).where(effectiveWhere);
2198
+ return { data, total: Number(countResult[0]?.count ?? 0) };
2199
+ });
2178
2200
  }
2179
2201
  const enabledMethods = new Set(options?.methods ?? ["list", "get", "create", "update", "remove"]);
2180
2202
  const listDef = !enabledMethods.has("list") ? void 0 : query(`${prefix}.list`, {
@@ -2205,12 +2227,14 @@ function crud(table, options) {
2205
2227
  } else {
2206
2228
  orderByClause = desc(defaultOrderCol);
2207
2229
  }
2208
- const results = await ctx.db.select().from(anyTable).where(whereClause).orderBy(orderByClause).limit(limit).offset(offset);
2209
- const countResult = await ctx.db.select({ count: drizzleCount() }).from(anyTable).where(whereClause);
2210
- return {
2211
- data: results,
2212
- total: Number(countResult[0]?.count ?? 0)
2213
- };
2230
+ return await inRlsOrPlainTx(ctx.db, async (tx) => {
2231
+ const results = await tx.select().from(anyTable).where(whereClause).orderBy(orderByClause).limit(limit).offset(offset);
2232
+ const countResult = await tx.select({ count: drizzleCount() }).from(anyTable).where(whereClause);
2233
+ return {
2234
+ data: results,
2235
+ total: Number(countResult[0]?.count ?? 0)
2236
+ };
2237
+ });
2214
2238
  }
2215
2239
  });
2216
2240
  const getDef = !enabledMethods.has("get") ? void 0 : query(`${prefix}.get`, {
@@ -2227,8 +2251,10 @@ function crud(table, options) {
2227
2251
  const sdField = anyTable[options.softDelete.field];
2228
2252
  whereCond = and(whereCond, eq(sdField, null));
2229
2253
  }
2230
- const [result] = await ctx.db.select().from(anyTable).where(whereCond).limit(1);
2231
- return result ?? null;
2254
+ return await inRlsOrPlainTx(ctx.db, async (tx) => {
2255
+ const [result] = await tx.select().from(anyTable).where(whereCond).limit(1);
2256
+ return result ?? null;
2257
+ });
2232
2258
  }
2233
2259
  });
2234
2260
  const createDef = !enabledMethods.has("create") ? void 0 : mutation(`${prefix}.create`, {
@@ -2236,6 +2262,12 @@ function crud(table, options) {
2236
2262
  handler: async (ctx, args) => {
2237
2263
  const user2 = ownerMeta || !isPublic ? ctx.auth.requireAuth() : null;
2238
2264
  let insertData = { ...args };
2265
+ if (ownerMeta && user2) {
2266
+ const requestedOwner = insertData[ownerMeta.propertyName] ?? insertData[ownerMeta.columnName] ?? insertData.userId;
2267
+ if (requestedOwner != null && requestedOwner !== user2.id) {
2268
+ throw new Error("Forbidden: cannot create resource for another user");
2269
+ }
2270
+ }
2239
2271
  if (ownerMeta && user2) {
2240
2272
  insertData[ownerMeta.propertyName] = user2.id;
2241
2273
  } else if (userIdCol && user2 && !insertData.userId) {
@@ -2244,7 +2276,10 @@ function crud(table, options) {
2244
2276
  if (options?.hooks?.beforeCreate) {
2245
2277
  insertData = await options.hooks.beforeCreate(insertData);
2246
2278
  }
2247
- const [result] = await ctx.db.insert(anyTable).values(insertData).returning();
2279
+ const [result] = await inRlsOrPlainTx(
2280
+ ctx.db,
2281
+ async (tx) => tx.insert(anyTable).values(insertData).returning()
2282
+ );
2248
2283
  if (useRealtime && enabledMethods.has("list")) {
2249
2284
  const currentUserId = user2?.id;
2250
2285
  const listResult = await fetchListWithTotal(ctx.db, void 0, currentUserId);
@@ -2273,7 +2308,10 @@ function crud(table, options) {
2273
2308
  if (options?.hooks?.beforeUpdate) {
2274
2309
  updateData = await options.hooks.beforeUpdate(updateData);
2275
2310
  }
2276
- const [result] = await ctx.db.update(anyTable).set(updateData).where(updateWhere).returning();
2311
+ const [result] = await inRlsOrPlainTx(
2312
+ ctx.db,
2313
+ async (tx) => tx.update(anyTable).set(updateData).where(updateWhere).returning()
2314
+ );
2277
2315
  if (useRealtime) {
2278
2316
  const currentUserId = ownerMeta ? user2?.id : void 0;
2279
2317
  if (enabledMethods.has("list")) {
@@ -2295,12 +2333,14 @@ function crud(table, options) {
2295
2333
  if (ownerMeta && user2) {
2296
2334
  deleteWhere = and(eq(pk, args.id), eq(ownerMeta.column, user2.id));
2297
2335
  }
2298
- if (options?.softDelete) {
2299
- const sdField = options.softDelete.field;
2300
- await ctx.db.update(anyTable).set({ [sdField]: /* @__PURE__ */ new Date() }).where(deleteWhere);
2301
- } else {
2302
- await ctx.db.delete(anyTable).where(deleteWhere);
2303
- }
2336
+ await inRlsOrPlainTx(ctx.db, async (tx) => {
2337
+ if (options?.softDelete) {
2338
+ const sdField = options.softDelete.field;
2339
+ await tx.update(anyTable).set({ [sdField]: /* @__PURE__ */ new Date() }).where(deleteWhere);
2340
+ } else {
2341
+ await tx.delete(anyTable).where(deleteWhere);
2342
+ }
2343
+ });
2304
2344
  if (useRealtime && enabledMethods.has("list")) {
2305
2345
  const currentUserId = ownerMeta ? user2?.id : void 0;
2306
2346
  const listResult = await fetchListWithTotal(ctx.db, void 0, currentUserId);
@@ -2317,13 +2357,14 @@ function crud(table, options) {
2317
2357
  remove: removeDef
2318
2358
  };
2319
2359
  }
2320
- var MAX_FILTER_DEPTH, FILTER_OPS;
2360
+ var _ownerRlsTables, MAX_FILTER_DEPTH, FILTER_OPS;
2321
2361
  var init_crud = __esm({
2322
2362
  "../core/src/crud.ts"() {
2323
2363
  "use strict";
2324
2364
  init_reactive();
2325
2365
  init_v();
2326
2366
  init_rls();
2367
+ _ownerRlsTables = [];
2327
2368
  MAX_FILTER_DEPTH = 5;
2328
2369
  FILTER_OPS = ["eq", "ne", "gt", "gte", "lt", "lte", "in", "nin", "like", "ilike"];
2329
2370
  }
@@ -2343,6 +2384,7 @@ __export(src_exports, {
2343
2384
  deregisterClient: () => deregisterClient,
2344
2385
  gencowCrud: () => crud,
2345
2386
  getOwnerRlsMeta: () => getOwnerRlsMeta,
2387
+ getOwnerRlsTables: () => getOwnerRlsTables,
2346
2388
  getQueryDef: () => getQueryDef,
2347
2389
  getQueryHandler: () => getQueryHandler,
2348
2390
  getRegisteredHttpActions: () => getRegisteredHttpActions,
@@ -61278,9 +61320,7 @@ var ALLOWED_NODE_MODULES = /* @__PURE__ */ new Set([
61278
61320
  "node:timers",
61279
61321
  "node:timers/promises",
61280
61322
  "node:zlib",
61281
- // 서드파티 패키지 호환을 위해 허용 (nsjail이 보안 담당)
61282
- "node:fs",
61283
- "node:fs/promises",
61323
+ // 서드파티 패키지 호환을 위해 허용 (cowbox Landlock TCP가 보안 담당)
61284
61324
  "node:http",
61285
61325
  "node:https",
61286
61326
  "node:http2",
@@ -61300,8 +61340,6 @@ var ALLOWED_NODE_MODULES = /* @__PURE__ */ new Set([
61300
61340
  "string_decoder",
61301
61341
  "timers",
61302
61342
  "zlib",
61303
- "fs",
61304
- "fs/promises",
61305
61343
  "http",
61306
61344
  "https",
61307
61345
  "http2",
@@ -62531,7 +62569,13 @@ async function main() {
62531
62569
  printFrontendAntiPatternReport(frontendResult);
62532
62570
  }
62533
62571
  _t("pre-import");
62534
- appModule = await import(functionsPath);
62572
+ let importPath = functionsPath;
62573
+ if (existsSync4(resolve6(functionsPath, "index.js"))) {
62574
+ importPath = resolve6(functionsPath, "index.js");
62575
+ } else if (existsSync4(resolve6(functionsPath, "index.ts"))) {
62576
+ importPath = resolve6(functionsPath, "index.ts");
62577
+ }
62578
+ appModule = await import(importPath);
62535
62579
  _t("post-import");
62536
62580
  const regQ = getRegisteredQueries().length;
62537
62581
  const regM = getRegisteredMutations().length;
@@ -63959,6 +64003,17 @@ async function main() {
63959
64003
  console.log(`[gencow] \u2192 \u{1F4C4} gencow/SECURITY.md \uCC38\uACE0`);
63960
64004
  }
63961
64005
  console.log(`[gencow] \u{1F512} Secure by Default: ${protectedQueryCount} queries + ${protectedMutationCount} mutations require auth`);
64006
+ const rlsTables = getOwnerRlsTables();
64007
+ if (rlsTables.length > 0) {
64008
+ console.log(``);
64009
+ console.log(`[gencow] \u{1F6E1}\uFE0F ownerRls \u2014 2-Layer Data Isolation:`);
64010
+ for (const t of rlsTables) {
64011
+ const mode = t.readPublic ? "read: public, CUD: owner" : "full owner isolation";
64012
+ console.log(`[gencow] \u2713 ${t.tableName} (${t.columnName}) \u2014 ${mode}`);
64013
+ }
64014
+ console.log(`[gencow] Layer 1: crud() auto-filters all queries by userId`);
64015
+ console.log(`[gencow] Layer 2: PostgreSQL RLS policies (set_config in transactions)`);
64016
+ }
63962
64017
  const _aiKey = process.env.OPENAI_API_KEY;
63963
64018
  if (_aiKey && (_aiKey.length < 20 || /^sk-\.{2,}$/.test(_aiKey) || _aiKey.includes("your-key") || _aiKey.includes("YOUR_KEY"))) {
63964
64019
  console.warn(`[gencow] \u26A0\uFE0F OPENAI_API_KEY\uAC00 \uD50C\uB808\uC774\uC2A4\uD640\uB354\uC785\uB2C8\uB2E4.`);