@neat.is/core 0.10.1-dev.20260926 → 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.
package/dist/index.cjs CHANGED
@@ -11874,13 +11874,16 @@ function objectKeys(obj) {
11874
11874
  var MODULAR_WRITES = /* @__PURE__ */ new Set(["addDoc", "setDoc", "updateDoc"]);
11875
11875
  var NAMESPACED_WRITES = /* @__PURE__ */ new Set(["add", "set", "update"]);
11876
11876
  var READ_CLAUSES = /* @__PURE__ */ new Set(["where", "orderBy"]);
11877
- function firestoreEndpointsFromFile(file, serviceDir) {
11877
+ function firestoreEndpointsFromFile(file, serviceDir, imported) {
11878
11878
  const hasClient = FIRESTORE_CLIENT_IMPORT_RE.test(file.content);
11879
11879
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
11880
- if (!hasClient && !hasAdmin) return [];
11881
- const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
11880
+ const importedNames = imported?.names ?? /* @__PURE__ */ new Set();
11881
+ if (!hasClient && !hasAdmin && importedNames.size === 0) return [];
11882
+ const ownSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
11883
+ const fileSdk = !hasClient && !hasAdmin ? imported?.sdk ?? null : ownSdk;
11882
11884
  const tree = parseSource3(parserForExt2(import_node_path44.default.extname(file.path)), file.content);
11883
11885
  const clientVars = firestoreClientVars(tree.rootNode);
11886
+ for (const n of importedNames) clientVars.add(n);
11884
11887
  const collLine = /* @__PURE__ */ new Map();
11885
11888
  const writes = /* @__PURE__ */ new Map();
11886
11889
  const reads = /* @__PURE__ */ new Map();
@@ -11991,6 +11994,99 @@ function firestoreEndpointsFromFile(file, serviceDir) {
11991
11994
  }
11992
11995
  return out;
11993
11996
  }
11997
+ function exportedFirestoreClients(root) {
11998
+ const out = /* @__PURE__ */ new Set();
11999
+ const collect = (node) => {
12000
+ if (node.type === "variable_declarator") {
12001
+ const name = node.childForFieldName("name");
12002
+ let value = node.childForFieldName("value");
12003
+ if (value?.type === "await_expression") value = namedChildren(value)[0] ?? null;
12004
+ if (name?.type === "identifier" && isFirestoreClientFactory(value)) out.add(name.text);
12005
+ }
12006
+ for (const c of namedChildren(node)) collect(c);
12007
+ };
12008
+ const walk11 = (node) => {
12009
+ if (node.type === "export_statement") collect(node);
12010
+ else for (const c of namedChildren(node)) walk11(c);
12011
+ };
12012
+ walk11(root);
12013
+ return out;
12014
+ }
12015
+ function namedImportBindings(root) {
12016
+ const out = [];
12017
+ const walk11 = (node) => {
12018
+ if (node.type === "import_statement") {
12019
+ const src = node.childForFieldName("source");
12020
+ const specifier = src ? src.text.slice(1, -1) : "";
12021
+ if (specifier.length > 0) {
12022
+ const stack = [node];
12023
+ while (stack.length > 0) {
12024
+ const n = stack.pop();
12025
+ if (n.type === "import_specifier") {
12026
+ const nameNode = n.childForFieldName("name");
12027
+ const aliasNode = n.childForFieldName("alias");
12028
+ const importedName = nameNode?.text;
12029
+ if (importedName) {
12030
+ out.push({ local: aliasNode?.text ?? importedName, imported: importedName, specifier });
12031
+ }
12032
+ continue;
12033
+ }
12034
+ for (const c of namedChildren(n)) stack.push(c);
12035
+ }
12036
+ }
12037
+ }
12038
+ for (const c of namedChildren(node)) walk11(c);
12039
+ };
12040
+ walk11(root);
12041
+ return out;
12042
+ }
12043
+ function sdkOf(content) {
12044
+ const hasClient = FIRESTORE_CLIENT_IMPORT_RE.test(content);
12045
+ const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(content);
12046
+ return hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
12047
+ }
12048
+ function parseOrNull(file) {
12049
+ const ext = import_node_path44.default.extname(file.path);
12050
+ if (!(ext in GRAMMAR_BY_EXT)) return null;
12051
+ try {
12052
+ return parseSource3(parserForExt2(ext), file.content).rootNode;
12053
+ } catch {
12054
+ return null;
12055
+ }
12056
+ }
12057
+ async function firestoreCrossFileEndpoints(files, serviceDir) {
12058
+ const registry = /* @__PURE__ */ new Map();
12059
+ for (const f of files) {
12060
+ if (sdkOf(f.content) === null && !FIRESTORE_CLIENT_IMPORT_RE.test(f.content)) continue;
12061
+ const root = parseOrNull(f);
12062
+ if (!root) continue;
12063
+ const names = exportedFirestoreClients(root);
12064
+ if (names.size === 0) continue;
12065
+ registry.set(toPosix(import_node_path44.default.relative(serviceDir, f.path)), { names, sdk: sdkOf(f.content) });
12066
+ }
12067
+ if (registry.size === 0) return [];
12068
+ const out = [];
12069
+ for (const f of files) {
12070
+ const root = parseOrNull(f);
12071
+ if (!root) continue;
12072
+ if (firestoreClientVars(root).size > 0) continue;
12073
+ const bindings = namedImportBindings(root);
12074
+ if (bindings.length === 0) continue;
12075
+ const names = /* @__PURE__ */ new Set();
12076
+ let sdk = null;
12077
+ for (const b of bindings) {
12078
+ const rel = await resolveJsImport(b.specifier, import_node_path44.default.dirname(f.path), serviceDir, null);
12079
+ if (!rel) continue;
12080
+ const entry = registry.get(rel);
12081
+ if (!entry || !entry.names.has(b.imported)) continue;
12082
+ names.add(b.local);
12083
+ sdk ??= entry.sdk;
12084
+ }
12085
+ if (names.size === 0) continue;
12086
+ out.push(...firestoreEndpointsFromFile(f, serviceDir, { names, sdk }));
12087
+ }
12088
+ return out;
12089
+ }
11994
12090
 
11995
12091
  // src/extract/calls/mongoose.ts
11996
12092
  init_cjs_shims();
@@ -14800,6 +14896,7 @@ async function addExternalEndpointEdges(graph, services) {
14800
14896
  recordExtractionError("python socket call extraction", file.path, err);
14801
14897
  }
14802
14898
  }
14899
+ endpoints.push(...await firestoreCrossFileEndpoints(maskedFiles, service.dir));
14803
14900
  endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
14804
14901
  endpoints.push(...pythonOrmCrossFileEndpoints(maskedFiles, service.dir));
14805
14902
  endpoints.push(...await prismaColumnEndpoints(service.dir));
@@ -16095,6 +16192,9 @@ function stripComments(src) {
16095
16192
  function segmentsFromMatchPath(pathToken) {
16096
16193
  return pathToken.split("/").filter((s) => s.length > 0);
16097
16194
  }
16195
+ function isRecursiveWildcard(seg) {
16196
+ return /^\{[^}]*=\s*\*\*\}$/.test(seg);
16197
+ }
16098
16198
  function collectionKeyFromSegments(segments) {
16099
16199
  let segs = segments;
16100
16200
  if (segs.length >= 3 && segs[0] === "databases" && /^\{.*\}$/.test(segs[1]) && segs[2] === "documents") {
@@ -16107,10 +16207,11 @@ function collectionKeyFromName(name) {
16107
16207
  }
16108
16208
  function fieldsFromCondition(cond) {
16109
16209
  const out = [];
16110
- const callRe = /\b(?:hasAll|hasOnly|hasAny)\s*\(\s*\[([^\]]*)\]\s*\)/g;
16210
+ const callRe = /(!\s*)?\b(?:hasAll|hasOnly|hasAny)\s*\(\s*\[([^\]]*)\]\s*\)/g;
16111
16211
  let m;
16112
16212
  while ((m = callRe.exec(cond)) !== null) {
16113
- const inner = m[1];
16213
+ if (m[1] !== void 0 || isNegatedAt(cond, m.index)) continue;
16214
+ const inner = m[2];
16114
16215
  const litRe = /'([^']*)'|"([^"]*)"/g;
16115
16216
  let lm;
16116
16217
  while ((lm = litRe.exec(inner)) !== null) {
@@ -16120,6 +16221,28 @@ function fieldsFromCondition(cond) {
16120
16221
  }
16121
16222
  return out;
16122
16223
  }
16224
+ function isNegatedAt(cond, idx) {
16225
+ let i = idx - 1;
16226
+ while (i >= 0) {
16227
+ const ch = cond[i];
16228
+ if (ch === ")") {
16229
+ let depth = 1;
16230
+ i--;
16231
+ while (i >= 0 && depth > 0) {
16232
+ if (cond[i] === ")") depth++;
16233
+ else if (cond[i] === "(") depth--;
16234
+ i--;
16235
+ }
16236
+ continue;
16237
+ }
16238
+ if (/[A-Za-z0-9_$.\]['"]/.test(ch) || ch === " ") {
16239
+ i--;
16240
+ continue;
16241
+ }
16242
+ return ch === "!";
16243
+ }
16244
+ return false;
16245
+ }
16123
16246
  function parseFirestoreRules(content) {
16124
16247
  const src = stripComments(content);
16125
16248
  const tokenRe = /match\s+((?:\/(?:\{[^}]*\}|[^\s/{}]+))+)\s*\{|allow\b([^;{}]*);|(\{)|(\})/g;
@@ -16132,7 +16255,8 @@ function parseFirestoreRules(content) {
16132
16255
  while ((m = tokenRe.exec(src)) !== null) {
16133
16256
  if (m[1] !== void 0) {
16134
16257
  depth++;
16135
- matchStack.push({ depth, segs: segmentsFromMatchPath(m[1]) });
16258
+ const segs = segmentsFromMatchPath(m[1]);
16259
+ matchStack.push({ depth, segs, recursive: segs.some(isRecursiveWildcard) });
16136
16260
  continue;
16137
16261
  }
16138
16262
  if (m[2] !== void 0) {
@@ -16143,6 +16267,10 @@ function parseFirestoreRules(content) {
16143
16267
  if (!methods.some((mm) => WRITE_METHODS.has(mm))) continue;
16144
16268
  const key = currentKey();
16145
16269
  if (key.length === 0) continue;
16270
+ if (matchStack.some((f) => f.recursive)) {
16271
+ indeterminate.add(key);
16272
+ continue;
16273
+ }
16146
16274
  const cond = colon === -1 ? "" : body.slice(colon + 1);
16147
16275
  const fields = fieldsFromCondition(cond);
16148
16276
  if (fields.length > 0) {
@@ -23744,6 +23872,7 @@ function decodeRailwayTargetRef(ref) {
23744
23872
  // src/connectors/hosted.ts
23745
23873
  var CREDENTIAL_REFRESH_SKEW_MS = 6e4;
23746
23874
  var CP_REQUEST_TIMEOUT_MS = 1e4;
23875
+ var DEFAULT_RELIST_INTERVAL_MS = 6e4;
23747
23876
  async function cpGet(path79, deps) {
23748
23877
  const f = deps.fetchImpl ?? fetch;
23749
23878
  const res = await f(`${deps.cpUrl.replace(/\/+$/, "")}${path79}`, {
@@ -23914,67 +24043,111 @@ function createHostedCredentialSource(provider, deps) {
23914
24043
  return record;
23915
24044
  };
23916
24045
  }
23917
- async function startHostedConnectors(input) {
24046
+ function connectionSignature(c) {
24047
+ return `${c.projectRef ?? ""}|${c.needsProjectSelection ? 1 : 0}`;
24048
+ }
24049
+ function startConnectionLoops(c, input, startLoop) {
23918
24050
  const { deps, graph, projectDir, project, errorsPath, onSkip } = input;
23919
- const startLoop = input.startLoop ?? startConnectorPollLoop;
24051
+ const stops = [];
24052
+ const credentialSource = createHostedCredentialSource(c.provider, deps);
24053
+ for (const name of HOSTED_CONNECTORS_FOR[c.provider] ?? [c.provider]) {
24054
+ const dispatch = PROVIDER_DISPATCH[name];
24055
+ if (!dispatch) {
24056
+ onSkip?.(name, "no pull connector for this provider");
24057
+ continue;
24058
+ }
24059
+ if (c.needsProjectSelection || !c.projectRef) {
24060
+ onSkip?.(name, "no project selected yet \u2014 not pullable");
24061
+ continue;
24062
+ }
24063
+ const options = hostedOptions(name, c, project, input.firebaseServiceMap);
24064
+ if (!options) {
24065
+ onSkip?.(name, "no hosted option mapping for this provider");
24066
+ continue;
24067
+ }
24068
+ let built;
24069
+ try {
24070
+ built = dispatch.build(graph, options);
24071
+ } catch (err) {
24072
+ onSkip?.(name, err.message);
24073
+ continue;
24074
+ }
24075
+ stops.push(
24076
+ startLoop(
24077
+ built.connector,
24078
+ { projectDir, project, credentials: {}, ...errorsPath ? { errorsPath } : {} },
24079
+ graph,
24080
+ built.resolveTarget,
24081
+ {
24082
+ connectorId: `hosted:${name}`,
24083
+ // The credential is fetched by the CONNECTION's provider (`gcp`), not the connector's name.
24084
+ refreshCredentials: credentialSource
24085
+ }
24086
+ )
24087
+ );
24088
+ }
24089
+ return () => {
24090
+ for (const stop of stops) stop();
24091
+ };
24092
+ }
24093
+ async function runHostedConnectorsPass(input, startLoop, running) {
24094
+ const { deps, onSkip } = input;
23920
24095
  let connections;
23921
24096
  try {
23922
24097
  connections = await cpGet(`/internal/projects/${deps.projectId}/connections`, deps);
23923
24098
  } catch (err) {
23924
24099
  onSkip?.("(all)", `control plane connection list unreadable \u2014 ${err.message}`);
23925
- return () => {
23926
- };
24100
+ return;
23927
24101
  }
23928
- if (!Array.isArray(connections)) return () => {
23929
- };
23930
- const stops = [];
24102
+ if (!Array.isArray(connections)) return;
23931
24103
  const lookupPush = input.pushDispatch ?? getPushProviderDispatch;
24104
+ const seen = /* @__PURE__ */ new Set();
23932
24105
  for (const c of connections) {
24106
+ seen.add(c.provider);
24107
+ const signature = connectionSignature(c);
24108
+ const existing = running.get(c.provider);
24109
+ if (existing && existing.signature === signature) continue;
24110
+ if (existing) existing.stop();
23933
24111
  const push = lookupPush(c.provider);
23934
24112
  if (push) {
23935
24113
  await provisionHostedPushProvider(c, push, input);
24114
+ running.set(c.provider, { signature, stop: () => {
24115
+ } });
23936
24116
  continue;
23937
24117
  }
23938
- const credentialSource = createHostedCredentialSource(c.provider, deps);
23939
- for (const name of HOSTED_CONNECTORS_FOR[c.provider] ?? [c.provider]) {
23940
- const dispatch = PROVIDER_DISPATCH[name];
23941
- if (!dispatch) {
23942
- onSkip?.(name, "no pull connector for this provider");
23943
- continue;
23944
- }
23945
- if (c.needsProjectSelection || !c.projectRef) {
23946
- onSkip?.(name, "no project selected yet \u2014 not pullable");
23947
- continue;
23948
- }
23949
- const options = hostedOptions(name, c, project, input.firebaseServiceMap);
23950
- if (!options) {
23951
- onSkip?.(name, "no hosted option mapping for this provider");
23952
- continue;
23953
- }
23954
- let built;
23955
- try {
23956
- built = dispatch.build(graph, options);
23957
- } catch (err) {
23958
- onSkip?.(name, err.message);
23959
- continue;
23960
- }
23961
- stops.push(
23962
- startLoop(
23963
- built.connector,
23964
- { projectDir, project, credentials: {}, ...errorsPath ? { errorsPath } : {} },
23965
- graph,
23966
- built.resolveTarget,
23967
- {
23968
- connectorId: `hosted:${name}`,
23969
- // The credential is fetched by the CONNECTION's provider (`gcp`), not the connector's name.
23970
- refreshCredentials: credentialSource
23971
- }
23972
- )
23973
- );
23974
- }
24118
+ running.set(c.provider, { signature, stop: startConnectionLoops(c, input, startLoop) });
23975
24119
  }
24120
+ for (const [provider, rc] of running) {
24121
+ if (seen.has(provider)) continue;
24122
+ rc.stop();
24123
+ running.delete(provider);
24124
+ }
24125
+ }
24126
+ async function startHostedConnectors(input) {
24127
+ const startLoop = input.startLoop ?? startConnectorPollLoop;
24128
+ const intervalMs = input.intervalMs ?? DEFAULT_RELIST_INTERVAL_MS;
24129
+ const running = /* @__PURE__ */ new Map();
24130
+ let stopped = false;
24131
+ let inPass = false;
24132
+ const tick = async () => {
24133
+ if (stopped || inPass) return;
24134
+ inPass = true;
24135
+ try {
24136
+ await runHostedConnectorsPass(input, startLoop, running);
24137
+ } finally {
24138
+ inPass = false;
24139
+ }
24140
+ };
24141
+ await tick();
24142
+ const timer = setInterval(() => {
24143
+ void tick();
24144
+ }, intervalMs);
24145
+ if (typeof timer.unref === "function") timer.unref();
23976
24146
  return () => {
23977
- for (const stop of stops) stop();
24147
+ stopped = true;
24148
+ clearInterval(timer);
24149
+ for (const rc of running.values()) rc.stop();
24150
+ running.clear();
23978
24151
  };
23979
24152
  }
23980
24153
  async function maybeStartHostedConnectors(input) {
@@ -23998,7 +24171,8 @@ async function maybeStartHostedConnectors(input) {
23998
24171
  project: input.project,
23999
24172
  ...parseFirebaseServiceMap(env.NEAT_FIREBASE_SERVICE_MAP) ? { firebaseServiceMap: parseFirebaseServiceMap(env.NEAT_FIREBASE_SERVICE_MAP) } : {},
24000
24173
  ...input.errorsPath ? { errorsPath: input.errorsPath } : {},
24001
- ...input.onSkip ? { onSkip: input.onSkip } : {}
24174
+ ...input.onSkip ? { onSkip: input.onSkip } : {},
24175
+ ...input.intervalMs !== void 0 ? { intervalMs: input.intervalMs } : {}
24002
24176
  });
24003
24177
  }
24004
24178