@neat.is/core 0.10.1-dev.20260927 → 0.10.1-dev.20260928

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.
@@ -11115,13 +11115,16 @@ function objectKeys(obj) {
11115
11115
  var MODULAR_WRITES = /* @__PURE__ */ new Set(["addDoc", "setDoc", "updateDoc"]);
11116
11116
  var NAMESPACED_WRITES = /* @__PURE__ */ new Set(["add", "set", "update"]);
11117
11117
  var READ_CLAUSES = /* @__PURE__ */ new Set(["where", "orderBy"]);
11118
- function firestoreEndpointsFromFile(file, serviceDir) {
11118
+ function firestoreEndpointsFromFile(file, serviceDir, imported) {
11119
11119
  const hasClient = FIRESTORE_CLIENT_IMPORT_RE.test(file.content);
11120
11120
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
11121
- if (!hasClient && !hasAdmin) return [];
11122
- const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
11121
+ const importedNames = imported?.names ?? /* @__PURE__ */ new Set();
11122
+ if (!hasClient && !hasAdmin && importedNames.size === 0) return [];
11123
+ const ownSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
11124
+ const fileSdk = !hasClient && !hasAdmin ? imported?.sdk ?? null : ownSdk;
11123
11125
  const tree = parseSource3(parserForExt2(path44.extname(file.path)), file.content);
11124
11126
  const clientVars = firestoreClientVars(tree.rootNode);
11127
+ for (const n of importedNames) clientVars.add(n);
11125
11128
  const collLine = /* @__PURE__ */ new Map();
11126
11129
  const writes = /* @__PURE__ */ new Map();
11127
11130
  const reads = /* @__PURE__ */ new Map();
@@ -11232,6 +11235,99 @@ function firestoreEndpointsFromFile(file, serviceDir) {
11232
11235
  }
11233
11236
  return out;
11234
11237
  }
11238
+ function exportedFirestoreClients(root) {
11239
+ const out = /* @__PURE__ */ new Set();
11240
+ const collect = (node) => {
11241
+ if (node.type === "variable_declarator") {
11242
+ const name = node.childForFieldName("name");
11243
+ let value = node.childForFieldName("value");
11244
+ if (value?.type === "await_expression") value = namedChildren(value)[0] ?? null;
11245
+ if (name?.type === "identifier" && isFirestoreClientFactory(value)) out.add(name.text);
11246
+ }
11247
+ for (const c of namedChildren(node)) collect(c);
11248
+ };
11249
+ const walk11 = (node) => {
11250
+ if (node.type === "export_statement") collect(node);
11251
+ else for (const c of namedChildren(node)) walk11(c);
11252
+ };
11253
+ walk11(root);
11254
+ return out;
11255
+ }
11256
+ function namedImportBindings(root) {
11257
+ const out = [];
11258
+ const walk11 = (node) => {
11259
+ if (node.type === "import_statement") {
11260
+ const src = node.childForFieldName("source");
11261
+ const specifier = src ? src.text.slice(1, -1) : "";
11262
+ if (specifier.length > 0) {
11263
+ const stack = [node];
11264
+ while (stack.length > 0) {
11265
+ const n = stack.pop();
11266
+ if (n.type === "import_specifier") {
11267
+ const nameNode = n.childForFieldName("name");
11268
+ const aliasNode = n.childForFieldName("alias");
11269
+ const importedName = nameNode?.text;
11270
+ if (importedName) {
11271
+ out.push({ local: aliasNode?.text ?? importedName, imported: importedName, specifier });
11272
+ }
11273
+ continue;
11274
+ }
11275
+ for (const c of namedChildren(n)) stack.push(c);
11276
+ }
11277
+ }
11278
+ }
11279
+ for (const c of namedChildren(node)) walk11(c);
11280
+ };
11281
+ walk11(root);
11282
+ return out;
11283
+ }
11284
+ function sdkOf(content) {
11285
+ const hasClient = FIRESTORE_CLIENT_IMPORT_RE.test(content);
11286
+ const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(content);
11287
+ return hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
11288
+ }
11289
+ function parseOrNull(file) {
11290
+ const ext = path44.extname(file.path);
11291
+ if (!(ext in GRAMMAR_BY_EXT)) return null;
11292
+ try {
11293
+ return parseSource3(parserForExt2(ext), file.content).rootNode;
11294
+ } catch {
11295
+ return null;
11296
+ }
11297
+ }
11298
+ async function firestoreCrossFileEndpoints(files, serviceDir) {
11299
+ const registry = /* @__PURE__ */ new Map();
11300
+ for (const f of files) {
11301
+ if (sdkOf(f.content) === null && !FIRESTORE_CLIENT_IMPORT_RE.test(f.content)) continue;
11302
+ const root = parseOrNull(f);
11303
+ if (!root) continue;
11304
+ const names = exportedFirestoreClients(root);
11305
+ if (names.size === 0) continue;
11306
+ registry.set(toPosix(path44.relative(serviceDir, f.path)), { names, sdk: sdkOf(f.content) });
11307
+ }
11308
+ if (registry.size === 0) return [];
11309
+ const out = [];
11310
+ for (const f of files) {
11311
+ const root = parseOrNull(f);
11312
+ if (!root) continue;
11313
+ if (firestoreClientVars(root).size > 0) continue;
11314
+ const bindings = namedImportBindings(root);
11315
+ if (bindings.length === 0) continue;
11316
+ const names = /* @__PURE__ */ new Set();
11317
+ let sdk = null;
11318
+ for (const b of bindings) {
11319
+ const rel = await resolveJsImport(b.specifier, path44.dirname(f.path), serviceDir, null);
11320
+ if (!rel) continue;
11321
+ const entry = registry.get(rel);
11322
+ if (!entry || !entry.names.has(b.imported)) continue;
11323
+ names.add(b.local);
11324
+ sdk ??= entry.sdk;
11325
+ }
11326
+ if (names.size === 0) continue;
11327
+ out.push(...firestoreEndpointsFromFile(f, serviceDir, { names, sdk }));
11328
+ }
11329
+ return out;
11330
+ }
11235
11331
 
11236
11332
  // src/extract/calls/mongoose.ts
11237
11333
  import path45 from "path";
@@ -14029,6 +14125,7 @@ async function addExternalEndpointEdges(graph, services) {
14029
14125
  recordExtractionError("python socket call extraction", file.path, err);
14030
14126
  }
14031
14127
  }
14128
+ endpoints.push(...await firestoreCrossFileEndpoints(maskedFiles, service.dir));
14032
14129
  endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
14033
14130
  endpoints.push(...pythonOrmCrossFileEndpoints(maskedFiles, service.dir));
14034
14131
  endpoints.push(...await prismaColumnEndpoints(service.dir));
@@ -15323,6 +15420,9 @@ function stripComments(src) {
15323
15420
  function segmentsFromMatchPath(pathToken) {
15324
15421
  return pathToken.split("/").filter((s) => s.length > 0);
15325
15422
  }
15423
+ function isRecursiveWildcard(seg) {
15424
+ return /^\{[^}]*=\s*\*\*\}$/.test(seg);
15425
+ }
15326
15426
  function collectionKeyFromSegments(segments) {
15327
15427
  let segs = segments;
15328
15428
  if (segs.length >= 3 && segs[0] === "databases" && /^\{.*\}$/.test(segs[1]) && segs[2] === "documents") {
@@ -15335,10 +15435,11 @@ function collectionKeyFromName(name) {
15335
15435
  }
15336
15436
  function fieldsFromCondition(cond) {
15337
15437
  const out = [];
15338
- const callRe = /\b(?:hasAll|hasOnly|hasAny)\s*\(\s*\[([^\]]*)\]\s*\)/g;
15438
+ const callRe = /(!\s*)?\b(?:hasAll|hasOnly|hasAny)\s*\(\s*\[([^\]]*)\]\s*\)/g;
15339
15439
  let m;
15340
15440
  while ((m = callRe.exec(cond)) !== null) {
15341
- const inner = m[1];
15441
+ if (m[1] !== void 0 || isNegatedAt(cond, m.index)) continue;
15442
+ const inner = m[2];
15342
15443
  const litRe = /'([^']*)'|"([^"]*)"/g;
15343
15444
  let lm;
15344
15445
  while ((lm = litRe.exec(inner)) !== null) {
@@ -15348,6 +15449,28 @@ function fieldsFromCondition(cond) {
15348
15449
  }
15349
15450
  return out;
15350
15451
  }
15452
+ function isNegatedAt(cond, idx) {
15453
+ let i = idx - 1;
15454
+ while (i >= 0) {
15455
+ const ch = cond[i];
15456
+ if (ch === ")") {
15457
+ let depth = 1;
15458
+ i--;
15459
+ while (i >= 0 && depth > 0) {
15460
+ if (cond[i] === ")") depth++;
15461
+ else if (cond[i] === "(") depth--;
15462
+ i--;
15463
+ }
15464
+ continue;
15465
+ }
15466
+ if (/[A-Za-z0-9_$.\]['"]/.test(ch) || ch === " ") {
15467
+ i--;
15468
+ continue;
15469
+ }
15470
+ return ch === "!";
15471
+ }
15472
+ return false;
15473
+ }
15351
15474
  function parseFirestoreRules(content) {
15352
15475
  const src = stripComments(content);
15353
15476
  const tokenRe = /match\s+((?:\/(?:\{[^}]*\}|[^\s/{}]+))+)\s*\{|allow\b([^;{}]*);|(\{)|(\})/g;
@@ -15360,7 +15483,8 @@ function parseFirestoreRules(content) {
15360
15483
  while ((m = tokenRe.exec(src)) !== null) {
15361
15484
  if (m[1] !== void 0) {
15362
15485
  depth++;
15363
- matchStack.push({ depth, segs: segmentsFromMatchPath(m[1]) });
15486
+ const segs = segmentsFromMatchPath(m[1]);
15487
+ matchStack.push({ depth, segs, recursive: segs.some(isRecursiveWildcard) });
15364
15488
  continue;
15365
15489
  }
15366
15490
  if (m[2] !== void 0) {
@@ -15371,6 +15495,10 @@ function parseFirestoreRules(content) {
15371
15495
  if (!methods.some((mm) => WRITE_METHODS.has(mm))) continue;
15372
15496
  const key = currentKey();
15373
15497
  if (key.length === 0) continue;
15498
+ if (matchStack.some((f) => f.recursive)) {
15499
+ indeterminate.add(key);
15500
+ continue;
15501
+ }
15374
15502
  const cond = colon === -1 ? "" : body.slice(colon + 1);
15375
15503
  const fields = fieldsFromCondition(cond);
15376
15504
  if (fields.length > 0) {
@@ -23170,4 +23298,4 @@ export {
23170
23298
  deprovisionConnector,
23171
23299
  buildApi
23172
23300
  };
23173
- //# sourceMappingURL=chunk-J3VVEB45.js.map
23301
+ //# sourceMappingURL=chunk-CNOJ4T6I.js.map