@neat.is/core 0.3.8 → 0.4.0
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/{chunk-7TYESDAI.js → chunk-4V23KYOP.js} +33 -4
- package/dist/chunk-4V23KYOP.js.map +1 -0
- package/dist/{chunk-V4TU7OKZ.js → chunk-IXIFJKMM.js} +2 -2
- package/dist/{chunk-LQ3JFBTX.js → chunk-N6RPINEJ.js} +5 -4
- package/dist/{chunk-LQ3JFBTX.js.map → chunk-N6RPINEJ.js.map} +1 -1
- package/dist/{chunk-CZ3T6TE2.js → chunk-UPW4CMOH.js} +120 -28
- package/dist/chunk-UPW4CMOH.js.map +1 -0
- package/dist/cli.cjs +916 -108
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +765 -73
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +153 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +153 -31
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-S3AENOZ6.js → otel-grpc-GGZHR7VM.js} +3 -3
- package/dist/server.cjs +282 -160
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +5 -4
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-7TYESDAI.js.map +0 -1
- package/dist/chunk-CZ3T6TE2.js.map +0 -1
- /package/dist/{chunk-V4TU7OKZ.js.map → chunk-IXIFJKMM.js.map} +0 -0
- /package/dist/{otel-grpc-S3AENOZ6.js.map → otel-grpc-GGZHR7VM.js.map} +0 -0
package/dist/index.d.cts
CHANGED
|
@@ -116,6 +116,7 @@ interface BuildApiOptions {
|
|
|
116
116
|
searchIndex?: SearchIndex;
|
|
117
117
|
authToken?: string;
|
|
118
118
|
trustProxy?: boolean;
|
|
119
|
+
publicRead?: boolean;
|
|
119
120
|
}
|
|
120
121
|
declare function buildApi(opts: BuildApiOptions): Promise<FastifyInstance>;
|
|
121
122
|
|
|
@@ -130,6 +131,7 @@ interface ParsedSpan {
|
|
|
130
131
|
endTimeUnixNano: string;
|
|
131
132
|
startTimeIso?: string;
|
|
132
133
|
durationNanos: bigint;
|
|
134
|
+
env: string;
|
|
133
135
|
attributes: Record<string, AttributeValue>;
|
|
134
136
|
dbSystem?: string;
|
|
135
137
|
dbName?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -116,6 +116,7 @@ interface BuildApiOptions {
|
|
|
116
116
|
searchIndex?: SearchIndex;
|
|
117
117
|
authToken?: string;
|
|
118
118
|
trustProxy?: boolean;
|
|
119
|
+
publicRead?: boolean;
|
|
119
120
|
}
|
|
120
121
|
declare function buildApi(opts: BuildApiOptions): Promise<FastifyInstance>;
|
|
121
122
|
|
|
@@ -130,6 +131,7 @@ interface ParsedSpan {
|
|
|
130
131
|
endTimeUnixNano: string;
|
|
131
132
|
startTimeIso?: string;
|
|
132
133
|
durationNanos: bigint;
|
|
134
|
+
env: string;
|
|
133
135
|
attributes: Record<string, AttributeValue>;
|
|
134
136
|
dbSystem?: string;
|
|
135
137
|
dbName?: string;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
routeSpanToProject,
|
|
3
3
|
startDaemon
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-N6RPINEJ.js";
|
|
5
5
|
import {
|
|
6
6
|
ProjectNameCollisionError,
|
|
7
7
|
addProject,
|
|
@@ -37,15 +37,15 @@ import {
|
|
|
37
37
|
thresholdForEdgeType,
|
|
38
38
|
touchLastSeen,
|
|
39
39
|
writeAtomically
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-UPW4CMOH.js";
|
|
41
41
|
import {
|
|
42
42
|
startOtelGrpcReceiver
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-IXIFJKMM.js";
|
|
44
44
|
import {
|
|
45
45
|
buildOtelReceiver,
|
|
46
46
|
logSpanHandler,
|
|
47
47
|
parseOtlpRequest
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-4V23KYOP.js";
|
|
49
49
|
export {
|
|
50
50
|
ProjectNameCollisionError,
|
|
51
51
|
addProject,
|
package/dist/neatd.cjs
CHANGED
|
@@ -55,6 +55,7 @@ function mountBearerAuth(app, opts) {
|
|
|
55
55
|
if (opts.trustProxy) return;
|
|
56
56
|
const expected = Buffer.from(opts.token, "utf8");
|
|
57
57
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
58
|
+
const publicRead = opts.publicRead === true;
|
|
58
59
|
app.addHook("preHandler", (req2, reply, done) => {
|
|
59
60
|
const path39 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
60
61
|
for (const suffix of suffixes) {
|
|
@@ -63,6 +64,10 @@ function mountBearerAuth(app, opts) {
|
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
66
|
}
|
|
67
|
+
if (publicRead && PUBLIC_READ_METHODS.has(req2.method)) {
|
|
68
|
+
done();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
66
71
|
const header = req2.headers.authorization;
|
|
67
72
|
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
68
73
|
void reply.code(401).send({ error: "unauthorized" });
|
|
@@ -76,16 +81,21 @@ function mountBearerAuth(app, opts) {
|
|
|
76
81
|
done();
|
|
77
82
|
});
|
|
78
83
|
}
|
|
84
|
+
function parseBoolEnv(v) {
|
|
85
|
+
if (!v) return false;
|
|
86
|
+
return v === "true" || v === "1";
|
|
87
|
+
}
|
|
79
88
|
function readAuthEnv(env = process.env) {
|
|
80
89
|
const t = env.NEAT_AUTH_TOKEN;
|
|
81
90
|
const ot = env.NEAT_OTEL_TOKEN;
|
|
82
91
|
return {
|
|
83
92
|
authToken: t && t.length > 0 ? t : void 0,
|
|
84
93
|
otelToken: ot && ot.length > 0 ? ot : t && t.length > 0 ? t : void 0,
|
|
85
|
-
trustProxy: env.NEAT_AUTH_PROXY === "true"
|
|
94
|
+
trustProxy: env.NEAT_AUTH_PROXY === "true",
|
|
95
|
+
publicRead: parseBoolEnv(env.NEAT_PUBLIC_READ)
|
|
86
96
|
};
|
|
87
97
|
}
|
|
88
|
-
var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, DEFAULT_UNAUTH_SUFFIXES;
|
|
98
|
+
var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS, DEFAULT_UNAUTH_SUFFIXES;
|
|
89
99
|
var init_auth = __esm({
|
|
90
100
|
"src/auth.ts"() {
|
|
91
101
|
"use strict";
|
|
@@ -105,7 +115,13 @@ var init_auth = __esm({
|
|
|
105
115
|
this.name = "BindAuthorityError";
|
|
106
116
|
}
|
|
107
117
|
};
|
|
108
|
-
|
|
118
|
+
PUBLIC_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
119
|
+
DEFAULT_UNAUTH_SUFFIXES = [
|
|
120
|
+
"/health",
|
|
121
|
+
"/healthz",
|
|
122
|
+
"/readyz",
|
|
123
|
+
"/api/config"
|
|
124
|
+
];
|
|
109
125
|
}
|
|
110
126
|
});
|
|
111
127
|
|
|
@@ -308,6 +324,15 @@ function isoFromUnixNano(nanos) {
|
|
|
308
324
|
return void 0;
|
|
309
325
|
}
|
|
310
326
|
}
|
|
327
|
+
function pickEnv(spanAttrs, resourceAttrs) {
|
|
328
|
+
for (const attrs of [spanAttrs, resourceAttrs]) {
|
|
329
|
+
for (const key of [ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT]) {
|
|
330
|
+
const v = attrs[key];
|
|
331
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return ENV_FALLBACK;
|
|
335
|
+
}
|
|
311
336
|
function parseOtlpRequest(body) {
|
|
312
337
|
const out = [];
|
|
313
338
|
for (const rs of body.resourceSpans ?? []) {
|
|
@@ -327,6 +352,7 @@ function parseOtlpRequest(body) {
|
|
|
327
352
|
endTimeUnixNano: span.endTimeUnixNano ?? "0",
|
|
328
353
|
startTimeIso: isoFromUnixNano(span.startTimeUnixNano),
|
|
329
354
|
durationNanos: durationNanos(span.startTimeUnixNano, span.endTimeUnixNano),
|
|
355
|
+
env: pickEnv(attrs, resourceAttrs),
|
|
330
356
|
attributes: attrs,
|
|
331
357
|
dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
|
|
332
358
|
dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
|
|
@@ -465,7 +491,7 @@ async function buildOtelReceiver(opts) {
|
|
|
465
491
|
};
|
|
466
492
|
return decorated;
|
|
467
493
|
}
|
|
468
|
-
var import_node_path35, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
494
|
+
var import_node_path35, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
469
495
|
var init_otel = __esm({
|
|
470
496
|
"src/otel.ts"() {
|
|
471
497
|
"use strict";
|
|
@@ -475,6 +501,9 @@ var init_otel = __esm({
|
|
|
475
501
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
476
502
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
477
503
|
init_auth();
|
|
504
|
+
ENV_ATTR_CANONICAL = "deployment.environment.name";
|
|
505
|
+
ENV_ATTR_COMPAT = "deployment.environment";
|
|
506
|
+
ENV_FALLBACK = "unknown";
|
|
478
507
|
exportTraceServiceRequestType = null;
|
|
479
508
|
exportTraceServiceResponseType = null;
|
|
480
509
|
cachedProtobufResponseBody = null;
|
|
@@ -1513,52 +1542,64 @@ function cacheSpanService(span, now) {
|
|
|
1513
1542
|
if (!span.traceId || !span.spanId) return;
|
|
1514
1543
|
const key = parentSpanKey(span.traceId, span.spanId);
|
|
1515
1544
|
parentSpanCache.delete(key);
|
|
1516
|
-
parentSpanCache.set(key, {
|
|
1545
|
+
parentSpanCache.set(key, {
|
|
1546
|
+
service: span.service,
|
|
1547
|
+
env: span.env ?? "unknown",
|
|
1548
|
+
expiresAt: now + PARENT_SPAN_CACHE_TTL_MS
|
|
1549
|
+
});
|
|
1517
1550
|
while (parentSpanCache.size > PARENT_SPAN_CACHE_SIZE) {
|
|
1518
1551
|
const oldest = parentSpanCache.keys().next().value;
|
|
1519
1552
|
if (!oldest) break;
|
|
1520
1553
|
parentSpanCache.delete(oldest);
|
|
1521
1554
|
}
|
|
1522
1555
|
}
|
|
1523
|
-
function
|
|
1556
|
+
function lookupParentSpan(traceId, parentSpanId, now) {
|
|
1524
1557
|
const entry2 = parentSpanCache.get(parentSpanKey(traceId, parentSpanId));
|
|
1525
1558
|
if (!entry2) return null;
|
|
1526
1559
|
if (entry2.expiresAt <= now) {
|
|
1527
1560
|
parentSpanCache.delete(parentSpanKey(traceId, parentSpanId));
|
|
1528
1561
|
return null;
|
|
1529
1562
|
}
|
|
1530
|
-
return entry2.service;
|
|
1563
|
+
return { service: entry2.service, env: entry2.env };
|
|
1531
1564
|
}
|
|
1532
|
-
function resolveServiceId(graph, host) {
|
|
1533
|
-
const
|
|
1534
|
-
if (graph.hasNode(
|
|
1535
|
-
|
|
1565
|
+
function resolveServiceId(graph, host, env) {
|
|
1566
|
+
const envTagged = (0, import_types3.serviceId)(host, env);
|
|
1567
|
+
if (graph.hasNode(envTagged)) return envTagged;
|
|
1568
|
+
const envLess = (0, import_types3.serviceId)(host);
|
|
1569
|
+
if (envLess !== envTagged && graph.hasNode(envLess)) return envLess;
|
|
1570
|
+
let sameEnv = null;
|
|
1571
|
+
let envLessMatch = null;
|
|
1572
|
+
let anyMatch = null;
|
|
1536
1573
|
graph.forEachNode((id, attrs) => {
|
|
1537
|
-
if (
|
|
1574
|
+
if (sameEnv) return;
|
|
1538
1575
|
const a = attrs;
|
|
1539
1576
|
if (a.type !== import_types3.NodeType.ServiceNode) return;
|
|
1540
|
-
|
|
1541
|
-
|
|
1577
|
+
const matchesByName = a.name === host;
|
|
1578
|
+
const matchesByAlias = a.aliases ? a.aliases.includes(host) : false;
|
|
1579
|
+
if (!matchesByName && !matchesByAlias) return;
|
|
1580
|
+
const nodeEnv = a.env ?? "unknown";
|
|
1581
|
+
if (nodeEnv === env) {
|
|
1582
|
+
sameEnv = id;
|
|
1542
1583
|
return;
|
|
1543
1584
|
}
|
|
1544
|
-
if (
|
|
1545
|
-
|
|
1546
|
-
}
|
|
1585
|
+
if (nodeEnv === "unknown" && !envLessMatch) envLessMatch = id;
|
|
1586
|
+
else if (!anyMatch) anyMatch = id;
|
|
1547
1587
|
});
|
|
1548
|
-
return
|
|
1588
|
+
return sameEnv ?? envLessMatch ?? anyMatch;
|
|
1549
1589
|
}
|
|
1550
1590
|
function frontierIdFor(host) {
|
|
1551
1591
|
return (0, import_types3.frontierId)(host);
|
|
1552
1592
|
}
|
|
1553
|
-
function ensureServiceNode(graph, serviceName) {
|
|
1554
|
-
const id = (0, import_types3.serviceId)(serviceName);
|
|
1593
|
+
function ensureServiceNode(graph, serviceName, env) {
|
|
1594
|
+
const id = (0, import_types3.serviceId)(serviceName, env);
|
|
1555
1595
|
if (graph.hasNode(id)) return id;
|
|
1556
1596
|
const node = {
|
|
1557
1597
|
id,
|
|
1558
1598
|
type: import_types3.NodeType.ServiceNode,
|
|
1559
1599
|
name: serviceName,
|
|
1560
1600
|
language: "unknown",
|
|
1561
|
-
discoveredVia: "otel"
|
|
1601
|
+
discoveredVia: "otel",
|
|
1602
|
+
...env !== "unknown" ? { env } : {}
|
|
1562
1603
|
};
|
|
1563
1604
|
graph.addNode(id, node);
|
|
1564
1605
|
return id;
|
|
@@ -1704,7 +1745,7 @@ function buildErrorEventForReceiver(span) {
|
|
|
1704
1745
|
...span.exception?.type ? { exceptionType: span.exception.type } : {},
|
|
1705
1746
|
...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
|
|
1706
1747
|
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
1707
|
-
affectedNode: (0, import_types3.serviceId)(span.service)
|
|
1748
|
+
affectedNode: (0, import_types3.serviceId)(span.service, span.env)
|
|
1708
1749
|
};
|
|
1709
1750
|
}
|
|
1710
1751
|
function makeErrorSpanWriter(errorsPath) {
|
|
@@ -1718,7 +1759,8 @@ function makeErrorSpanWriter(errorsPath) {
|
|
|
1718
1759
|
async function handleSpan(ctx, span) {
|
|
1719
1760
|
const ts = span.startTimeIso ?? nowIso(ctx);
|
|
1720
1761
|
const nowMs = ctx.now ? ctx.now() : Date.now();
|
|
1721
|
-
const
|
|
1762
|
+
const env = span.env ?? "unknown";
|
|
1763
|
+
const sourceId = ensureServiceNode(ctx.graph, span.service, env);
|
|
1722
1764
|
const isError = span.statusCode === 2;
|
|
1723
1765
|
cacheSpanService(span, nowMs);
|
|
1724
1766
|
let affectedNode = sourceId;
|
|
@@ -1741,7 +1783,7 @@ async function handleSpan(ctx, span) {
|
|
|
1741
1783
|
const host = pickAddress(span);
|
|
1742
1784
|
let resolvedViaAddress = false;
|
|
1743
1785
|
if (host && host !== span.service) {
|
|
1744
|
-
const targetId = resolveServiceId(ctx.graph, host);
|
|
1786
|
+
const targetId = resolveServiceId(ctx.graph, host, env);
|
|
1745
1787
|
if (targetId && targetId !== sourceId) {
|
|
1746
1788
|
upsertObservedEdge(
|
|
1747
1789
|
ctx.graph,
|
|
@@ -1768,9 +1810,9 @@ async function handleSpan(ctx, span) {
|
|
|
1768
1810
|
}
|
|
1769
1811
|
}
|
|
1770
1812
|
if (!resolvedViaAddress && span.parentSpanId) {
|
|
1771
|
-
const
|
|
1772
|
-
if (
|
|
1773
|
-
const parentId = ensureServiceNode(ctx.graph,
|
|
1813
|
+
const parent = lookupParentSpan(span.traceId, span.parentSpanId, nowMs);
|
|
1814
|
+
if (parent && parent.service !== span.service) {
|
|
1815
|
+
const parentId = ensureServiceNode(ctx.graph, parent.service, parent.env);
|
|
1774
1816
|
upsertObservedEdge(
|
|
1775
1817
|
ctx.graph,
|
|
1776
1818
|
import_types3.EdgeType.CALLS,
|
|
@@ -1893,6 +1935,28 @@ async function readErrorEvents(errorsPath) {
|
|
|
1893
1935
|
throw err;
|
|
1894
1936
|
}
|
|
1895
1937
|
}
|
|
1938
|
+
function mergeSnapshot(graph, snapshot) {
|
|
1939
|
+
const exported = snapshot.graph;
|
|
1940
|
+
let nodesAdded = 0;
|
|
1941
|
+
let edgesAdded = 0;
|
|
1942
|
+
for (const node of exported.nodes ?? []) {
|
|
1943
|
+
if (graph.hasNode(node.key)) continue;
|
|
1944
|
+
if (!node.attributes) continue;
|
|
1945
|
+
graph.addNode(node.key, node.attributes);
|
|
1946
|
+
nodesAdded++;
|
|
1947
|
+
}
|
|
1948
|
+
for (const edge of exported.edges ?? []) {
|
|
1949
|
+
const attrs = edge.attributes;
|
|
1950
|
+
if (!attrs) continue;
|
|
1951
|
+
const id = edge.key ?? attrs.id;
|
|
1952
|
+
if (!id) continue;
|
|
1953
|
+
if (graph.hasEdge(id)) continue;
|
|
1954
|
+
if (!graph.hasNode(edge.source) || !graph.hasNode(edge.target)) continue;
|
|
1955
|
+
graph.addEdgeWithKey(id, edge.source, edge.target, attrs);
|
|
1956
|
+
edgesAdded++;
|
|
1957
|
+
}
|
|
1958
|
+
return { nodesAdded, edgesAdded };
|
|
1959
|
+
}
|
|
1896
1960
|
|
|
1897
1961
|
// src/extract/services.ts
|
|
1898
1962
|
init_cjs_shims();
|
|
@@ -2289,6 +2353,18 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
2289
2353
|
}
|
|
2290
2354
|
return [...found];
|
|
2291
2355
|
}
|
|
2356
|
+
function detectJsFramework(pkg) {
|
|
2357
|
+
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
2358
|
+
if (deps["next"] !== void 0) return "next";
|
|
2359
|
+
if (deps["remix"] !== void 0) return "remix";
|
|
2360
|
+
for (const k of Object.keys(deps)) {
|
|
2361
|
+
if (k.startsWith("@remix-run/")) return "remix";
|
|
2362
|
+
}
|
|
2363
|
+
if (deps["@sveltejs/kit"] !== void 0) return "sveltekit";
|
|
2364
|
+
if (deps["nuxt"] !== void 0) return "nuxt";
|
|
2365
|
+
if (deps["astro"] !== void 0) return "astro";
|
|
2366
|
+
return void 0;
|
|
2367
|
+
}
|
|
2292
2368
|
async function discoverNodeService(scanPath, dir) {
|
|
2293
2369
|
const pkgPath = import_node_path8.default.join(dir, "package.json");
|
|
2294
2370
|
if (!await exists(pkgPath)) return null;
|
|
@@ -2300,6 +2376,7 @@ async function discoverNodeService(scanPath, dir) {
|
|
|
2300
2376
|
return null;
|
|
2301
2377
|
}
|
|
2302
2378
|
if (!pkg.name) return null;
|
|
2379
|
+
const framework = detectJsFramework(pkg);
|
|
2303
2380
|
const node = {
|
|
2304
2381
|
id: (0, import_types5.serviceId)(pkg.name),
|
|
2305
2382
|
type: import_types5.NodeType.ServiceNode,
|
|
@@ -2308,7 +2385,8 @@ async function discoverNodeService(scanPath, dir) {
|
|
|
2308
2385
|
version: pkg.version,
|
|
2309
2386
|
dependencies: pkg.dependencies ?? {},
|
|
2310
2387
|
repoPath: import_node_path8.default.relative(scanPath, dir),
|
|
2311
|
-
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {}
|
|
2388
|
+
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
|
|
2389
|
+
...framework ? { framework } : {}
|
|
2312
2390
|
};
|
|
2313
2391
|
return { pkg, dir, node };
|
|
2314
2392
|
}
|
|
@@ -4091,7 +4169,7 @@ init_cjs_shims();
|
|
|
4091
4169
|
var import_node_fs18 = require("fs");
|
|
4092
4170
|
var import_node_path31 = __toESM(require("path"), 1);
|
|
4093
4171
|
var import_types19 = require("@neat.is/types");
|
|
4094
|
-
var SCHEMA_VERSION =
|
|
4172
|
+
var SCHEMA_VERSION = 4;
|
|
4095
4173
|
function migrateV1ToV2(payload) {
|
|
4096
4174
|
const nodes = payload.graph.nodes;
|
|
4097
4175
|
if (Array.isArray(nodes)) {
|
|
@@ -4103,6 +4181,9 @@ function migrateV1ToV2(payload) {
|
|
|
4103
4181
|
}
|
|
4104
4182
|
return { ...payload, schemaVersion: 2 };
|
|
4105
4183
|
}
|
|
4184
|
+
function migrateV3ToV4(payload) {
|
|
4185
|
+
return { ...payload, schemaVersion: 4 };
|
|
4186
|
+
}
|
|
4106
4187
|
function migrateV2ToV3(payload) {
|
|
4107
4188
|
const edges = payload.graph.edges;
|
|
4108
4189
|
if (Array.isArray(edges)) {
|
|
@@ -4151,6 +4232,9 @@ async function loadGraphFromDisk(graph, outPath) {
|
|
|
4151
4232
|
if (payload.schemaVersion === 2) {
|
|
4152
4233
|
payload = migrateV2ToV3(payload);
|
|
4153
4234
|
}
|
|
4235
|
+
if (payload.schemaVersion === 3) {
|
|
4236
|
+
payload = migrateV3ToV4(payload);
|
|
4237
|
+
}
|
|
4154
4238
|
if (payload.schemaVersion !== SCHEMA_VERSION) {
|
|
4155
4239
|
throw new Error(
|
|
4156
4240
|
`persist: unsupported snapshot schemaVersion ${payload.schemaVersion} (expected ${SCHEMA_VERSION})`
|
|
@@ -5004,6 +5088,35 @@ function registerRoutes(scope, ctx) {
|
|
|
5004
5088
|
}
|
|
5005
5089
|
}
|
|
5006
5090
|
);
|
|
5091
|
+
scope.post("/snapshot", async (req2, reply) => {
|
|
5092
|
+
const proj = resolveProject(registry, req2, reply);
|
|
5093
|
+
if (!proj) return;
|
|
5094
|
+
const body = req2.body;
|
|
5095
|
+
if (!body || typeof body !== "object" || !body.snapshot) {
|
|
5096
|
+
return reply.code(400).send({ error: "request body must be { snapshot: <persisted-graph> }" });
|
|
5097
|
+
}
|
|
5098
|
+
const snap = body.snapshot;
|
|
5099
|
+
if (typeof snap.schemaVersion !== "number" || snap.schemaVersion !== SCHEMA_VERSION) {
|
|
5100
|
+
return reply.code(400).send({
|
|
5101
|
+
error: `unsupported snapshot schemaVersion ${snap.schemaVersion} (expected ${SCHEMA_VERSION})`
|
|
5102
|
+
});
|
|
5103
|
+
}
|
|
5104
|
+
try {
|
|
5105
|
+
const result = mergeSnapshot(proj.graph, snap);
|
|
5106
|
+
return {
|
|
5107
|
+
project: proj.name,
|
|
5108
|
+
nodesAdded: result.nodesAdded,
|
|
5109
|
+
edgesAdded: result.edgesAdded,
|
|
5110
|
+
nodeCount: proj.graph.order,
|
|
5111
|
+
edgeCount: proj.graph.size
|
|
5112
|
+
};
|
|
5113
|
+
} catch (err) {
|
|
5114
|
+
return reply.code(400).send({
|
|
5115
|
+
error: "snapshot merge failed",
|
|
5116
|
+
details: err.message
|
|
5117
|
+
});
|
|
5118
|
+
}
|
|
5119
|
+
});
|
|
5007
5120
|
scope.post("/graph/scan", async (req2, reply) => {
|
|
5008
5121
|
const proj = resolveProject(registry, req2, reply);
|
|
5009
5122
|
if (!proj) return;
|
|
@@ -5097,7 +5210,15 @@ function registerRoutes(scope, ctx) {
|
|
|
5097
5210
|
async function buildApi(opts) {
|
|
5098
5211
|
const app = (0, import_fastify.default)({ logger: false });
|
|
5099
5212
|
await app.register(import_cors.default, { origin: true });
|
|
5100
|
-
mountBearerAuth(app, {
|
|
5213
|
+
mountBearerAuth(app, {
|
|
5214
|
+
token: opts.authToken,
|
|
5215
|
+
trustProxy: opts.trustProxy,
|
|
5216
|
+
publicRead: opts.publicRead
|
|
5217
|
+
});
|
|
5218
|
+
app.get("/api/config", async () => ({
|
|
5219
|
+
publicRead: opts.publicRead === true,
|
|
5220
|
+
authProxy: opts.trustProxy === true
|
|
5221
|
+
}));
|
|
5101
5222
|
const startedAt = opts.startedAt ?? Date.now();
|
|
5102
5223
|
const registry = buildLegacyRegistry(opts);
|
|
5103
5224
|
const legacyErrorsExplicit = !opts.projects && opts.errorsPath !== void 0;
|
|
@@ -5374,7 +5495,8 @@ async function startDaemon(opts = {}) {
|
|
|
5374
5495
|
restApp = await buildApi({
|
|
5375
5496
|
projects: registry,
|
|
5376
5497
|
authToken: auth.authToken,
|
|
5377
|
-
trustProxy: auth.trustProxy
|
|
5498
|
+
trustProxy: auth.trustProxy,
|
|
5499
|
+
publicRead: auth.publicRead
|
|
5378
5500
|
});
|
|
5379
5501
|
restAddress = await restApp.listen({ port: restPort, host });
|
|
5380
5502
|
console.log(`neatd: REST listening on ${restAddress}`);
|