@neat.is/core 0.3.6 → 0.3.7
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-4ASCXBZF.js → chunk-75IBA4UL.js} +34 -5
- package/dist/chunk-75IBA4UL.js.map +1 -0
- package/dist/{chunk-G3PDTGOW.js → chunk-CY67YKNO.js} +2 -2
- package/dist/{chunk-YHQYHFI3.js → chunk-EDHOCFOG.js} +86 -14
- package/dist/chunk-EDHOCFOG.js.map +1 -0
- package/dist/cli.cjs +93 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +62 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +117 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +3 -3
- package/dist/neatd.cjs +117 -16
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/{otel-grpc-J4O2SIBZ.js → otel-grpc-PM4SWPZE.js} +3 -3
- package/dist/server.cjs +33 -4
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +2 -2
- package/package.json +2 -2
- package/dist/chunk-4ASCXBZF.js.map +0 -1
- package/dist/chunk-YHQYHFI3.js.map +0 -1
- /package/dist/{chunk-G3PDTGOW.js.map → chunk-CY67YKNO.js.map} +0 -0
- /package/dist/{otel-grpc-J4O2SIBZ.js.map → otel-grpc-PM4SWPZE.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -257,8 +257,7 @@ function parseOtlpRequest(body) {
|
|
|
257
257
|
}
|
|
258
258
|
return out;
|
|
259
259
|
}
|
|
260
|
-
function
|
|
261
|
-
if (exportTraceServiceRequestType) return exportTraceServiceRequestType;
|
|
260
|
+
function loadProtoRoot() {
|
|
262
261
|
const here = import_node_path35.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
263
262
|
const protoRoot = import_node_path35.default.resolve(here, "..", "proto");
|
|
264
263
|
const root = new import_protobufjs.default.Root();
|
|
@@ -267,11 +266,32 @@ function loadProtobufDecoder() {
|
|
|
267
266
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
268
267
|
{ keepCase: true }
|
|
269
268
|
);
|
|
269
|
+
return root;
|
|
270
|
+
}
|
|
271
|
+
function loadProtobufDecoder() {
|
|
272
|
+
if (exportTraceServiceRequestType) return exportTraceServiceRequestType;
|
|
273
|
+
const root = loadProtoRoot();
|
|
270
274
|
exportTraceServiceRequestType = root.lookupType(
|
|
271
275
|
"opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest"
|
|
272
276
|
);
|
|
273
277
|
return exportTraceServiceRequestType;
|
|
274
278
|
}
|
|
279
|
+
function loadProtobufResponseEncoder() {
|
|
280
|
+
if (exportTraceServiceResponseType) return exportTraceServiceResponseType;
|
|
281
|
+
const root = loadProtoRoot();
|
|
282
|
+
exportTraceServiceResponseType = root.lookupType(
|
|
283
|
+
"opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse"
|
|
284
|
+
);
|
|
285
|
+
return exportTraceServiceResponseType;
|
|
286
|
+
}
|
|
287
|
+
function encodeProtobufResponseBody() {
|
|
288
|
+
if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
|
|
289
|
+
const Type = loadProtobufResponseEncoder();
|
|
290
|
+
const msg = Type.create({});
|
|
291
|
+
const encoded = Type.encode(msg).finish();
|
|
292
|
+
cachedProtobufResponseBody = Buffer.from(encoded);
|
|
293
|
+
return cachedProtobufResponseBody;
|
|
294
|
+
}
|
|
275
295
|
async function decodeProtobufBody(buf) {
|
|
276
296
|
const Type = loadProtobufDecoder();
|
|
277
297
|
const decoded = Type.decode(buf).toJSON();
|
|
@@ -318,7 +338,9 @@ async function buildOtelReceiver(opts) {
|
|
|
318
338
|
app.post("/v1/traces", async (req, reply) => {
|
|
319
339
|
const ct = (req.headers["content-type"] ?? "").toString().split(";")[0].trim().toLowerCase();
|
|
320
340
|
let body;
|
|
341
|
+
let responseFlavor;
|
|
321
342
|
if (ct === "application/x-protobuf") {
|
|
343
|
+
responseFlavor = "protobuf";
|
|
322
344
|
try {
|
|
323
345
|
body = await decodeProtobufBody(req.body);
|
|
324
346
|
} catch (err) {
|
|
@@ -327,6 +349,7 @@ async function buildOtelReceiver(opts) {
|
|
|
327
349
|
});
|
|
328
350
|
}
|
|
329
351
|
} else if (!ct || ct === "application/json") {
|
|
352
|
+
responseFlavor = "json";
|
|
330
353
|
body = req.body ?? {};
|
|
331
354
|
} else {
|
|
332
355
|
return reply.code(415).send({ error: `unsupported content-type: ${ct}` });
|
|
@@ -344,7 +367,11 @@ async function buildOtelReceiver(opts) {
|
|
|
344
367
|
}
|
|
345
368
|
}
|
|
346
369
|
enqueue(spans);
|
|
347
|
-
|
|
370
|
+
if (responseFlavor === "protobuf") {
|
|
371
|
+
const buf = encodeProtobufResponseBody();
|
|
372
|
+
return reply.code(200).header("content-type", "application/x-protobuf").send(buf);
|
|
373
|
+
}
|
|
374
|
+
return reply.code(200).header("content-type", "application/json").send({ partialSuccess: {} });
|
|
348
375
|
});
|
|
349
376
|
const decorated = app;
|
|
350
377
|
decorated.flushPending = async () => {
|
|
@@ -362,7 +389,7 @@ function logSpanHandler(span) {
|
|
|
362
389
|
`otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
|
|
363
390
|
);
|
|
364
391
|
}
|
|
365
|
-
var import_node_path35, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
|
|
392
|
+
var import_node_path35, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
366
393
|
var init_otel = __esm({
|
|
367
394
|
"src/otel.ts"() {
|
|
368
395
|
"use strict";
|
|
@@ -372,6 +399,8 @@ var init_otel = __esm({
|
|
|
372
399
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
373
400
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
374
401
|
exportTraceServiceRequestType = null;
|
|
402
|
+
exportTraceServiceResponseType = null;
|
|
403
|
+
cachedProtobufResponseBody = null;
|
|
375
404
|
}
|
|
376
405
|
});
|
|
377
406
|
|
|
@@ -5259,13 +5288,35 @@ function neatHomeFor(opts) {
|
|
|
5259
5288
|
function routeSpanToProject(serviceName, projects) {
|
|
5260
5289
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
5261
5290
|
for (const entry of projects) {
|
|
5262
|
-
if (entry.status
|
|
5263
|
-
if (entry.languages.length === 0) {
|
|
5264
|
-
}
|
|
5291
|
+
if (entry.status === "paused") continue;
|
|
5265
5292
|
if (entry.name === serviceName) return entry.name;
|
|
5266
5293
|
}
|
|
5294
|
+
const candidates = [];
|
|
5295
|
+
for (const entry of projects) {
|
|
5296
|
+
if (entry.status === "paused") continue;
|
|
5297
|
+
if (isTokenPrefix(entry.name, serviceName)) candidates.push(entry);
|
|
5298
|
+
}
|
|
5299
|
+
if (candidates.length > 0) {
|
|
5300
|
+
candidates.sort((a, b) => b.name.length - a.name.length);
|
|
5301
|
+
return candidates[0].name;
|
|
5302
|
+
}
|
|
5303
|
+
for (const entry of projects) {
|
|
5304
|
+
if (entry.status === "paused") continue;
|
|
5305
|
+
if (isTokenContained(entry.name, serviceName)) return entry.name;
|
|
5306
|
+
}
|
|
5267
5307
|
return DEFAULT_PROJECT;
|
|
5268
5308
|
}
|
|
5309
|
+
function isTokenPrefix(prefix, full) {
|
|
5310
|
+
if (prefix.length >= full.length) return false;
|
|
5311
|
+
if (!full.startsWith(prefix)) return false;
|
|
5312
|
+
const sep = full.charAt(prefix.length);
|
|
5313
|
+
return sep === "-" || sep === "_";
|
|
5314
|
+
}
|
|
5315
|
+
function isTokenContained(needle, haystack) {
|
|
5316
|
+
if (!haystack.includes(needle)) return false;
|
|
5317
|
+
const tokens = haystack.split(/[-_]/);
|
|
5318
|
+
return tokens.includes(needle);
|
|
5319
|
+
}
|
|
5269
5320
|
async function bootstrapProject(entry) {
|
|
5270
5321
|
const paths = pathsForProject(entry.name, import_node_path36.default.join(entry.path, "neat-out"));
|
|
5271
5322
|
try {
|
|
@@ -5345,6 +5396,17 @@ async function startDaemon(opts = {}) {
|
|
|
5345
5396
|
`);
|
|
5346
5397
|
const slots = /* @__PURE__ */ new Map();
|
|
5347
5398
|
const registry = new Projects();
|
|
5399
|
+
const DROP_WARN_INTERVAL_MS = 6e4;
|
|
5400
|
+
const lastDropWarnAt = /* @__PURE__ */ new Map();
|
|
5401
|
+
function warnDroppedSpan(project, reason) {
|
|
5402
|
+
const now = Date.now();
|
|
5403
|
+
const prev = lastDropWarnAt.get(project) ?? 0;
|
|
5404
|
+
if (now - prev < DROP_WARN_INTERVAL_MS) return;
|
|
5405
|
+
lastDropWarnAt.set(project, now);
|
|
5406
|
+
console.warn(
|
|
5407
|
+
`[neatd] dropping span for project "${project}" \u2014 project status: broken (${reason}). Run \`neatd reload\` to retry bootstrap.`
|
|
5408
|
+
);
|
|
5409
|
+
}
|
|
5348
5410
|
function upsertRegistryFromSlot(slot) {
|
|
5349
5411
|
if (slot.status !== "active") return;
|
|
5350
5412
|
registry.set(slot.entry.name, {
|
|
@@ -5353,12 +5415,38 @@ async function startDaemon(opts = {}) {
|
|
|
5353
5415
|
graph: slot.graph
|
|
5354
5416
|
});
|
|
5355
5417
|
}
|
|
5418
|
+
async function tryRecoverSlot(entry) {
|
|
5419
|
+
try {
|
|
5420
|
+
const fresh = await bootstrapProject(entry);
|
|
5421
|
+
slots.set(entry.name, fresh);
|
|
5422
|
+
upsertRegistryFromSlot(fresh);
|
|
5423
|
+
if (fresh.status === "active") {
|
|
5424
|
+
await setStatus(entry.name, "active").catch(() => {
|
|
5425
|
+
});
|
|
5426
|
+
console.log(
|
|
5427
|
+
`neatd: project "${entry.name}" recovered from broken \u2014 active`
|
|
5428
|
+
);
|
|
5429
|
+
}
|
|
5430
|
+
return fresh;
|
|
5431
|
+
} catch (err) {
|
|
5432
|
+
console.warn(
|
|
5433
|
+
`neatd: project "${entry.name}" still broken after recovery attempt \u2014 ${err.message}`
|
|
5434
|
+
);
|
|
5435
|
+
return slots.get(entry.name);
|
|
5436
|
+
}
|
|
5437
|
+
}
|
|
5356
5438
|
async function loadAll() {
|
|
5357
5439
|
const projects = await listProjects();
|
|
5358
5440
|
const seen = /* @__PURE__ */ new Set();
|
|
5359
5441
|
for (const entry of projects) {
|
|
5360
5442
|
seen.add(entry.name);
|
|
5361
|
-
|
|
5443
|
+
const existing = slots.get(entry.name);
|
|
5444
|
+
if (existing) {
|
|
5445
|
+
if (existing.status === "broken") {
|
|
5446
|
+
await tryRecoverSlot(entry);
|
|
5447
|
+
}
|
|
5448
|
+
continue;
|
|
5449
|
+
}
|
|
5362
5450
|
try {
|
|
5363
5451
|
const slot = await bootstrapProject(entry);
|
|
5364
5452
|
slots.set(entry.name, slot);
|
|
@@ -5415,13 +5503,28 @@ async function startDaemon(opts = {}) {
|
|
|
5415
5503
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
5416
5504
|
);
|
|
5417
5505
|
}
|
|
5506
|
+
async function resolveTargetSlot(serviceName) {
|
|
5507
|
+
const liveEntries = await listProjects().catch(() => []);
|
|
5508
|
+
const target = routeSpanToProject(serviceName, liveEntries);
|
|
5509
|
+
let slot = slots.get(target) ?? slots.get(DEFAULT_PROJECT);
|
|
5510
|
+
if (!slot) return null;
|
|
5511
|
+
if (slot.status === "broken") {
|
|
5512
|
+
const entry = liveEntries.find((e) => e.name === slot.entry.name);
|
|
5513
|
+
if (entry) {
|
|
5514
|
+
slot = await tryRecoverSlot(entry);
|
|
5515
|
+
}
|
|
5516
|
+
if (slot.status !== "active") {
|
|
5517
|
+
warnDroppedSpan(slot.entry.name, slot.errorReason ?? "unknown");
|
|
5518
|
+
return null;
|
|
5519
|
+
}
|
|
5520
|
+
}
|
|
5521
|
+
return slot.status === "active" ? slot : null;
|
|
5522
|
+
}
|
|
5418
5523
|
try {
|
|
5419
5524
|
otlpApp = await buildOtelReceiver({
|
|
5420
5525
|
onSpan: async (span) => {
|
|
5421
|
-
const
|
|
5422
|
-
|
|
5423
|
-
const slot = slots.get(target) ?? slots.get(DEFAULT_PROJECT);
|
|
5424
|
-
if (!slot || slot.status !== "active") return;
|
|
5526
|
+
const slot = await resolveTargetSlot(span.service);
|
|
5527
|
+
if (!slot) return;
|
|
5425
5528
|
await handleSpan(
|
|
5426
5529
|
{
|
|
5427
5530
|
graph: slot.graph,
|
|
@@ -5434,10 +5537,8 @@ async function startDaemon(opts = {}) {
|
|
|
5434
5537
|
);
|
|
5435
5538
|
},
|
|
5436
5539
|
onErrorSpanSync: async (span) => {
|
|
5437
|
-
const
|
|
5438
|
-
|
|
5439
|
-
const slot = slots.get(target) ?? slots.get(DEFAULT_PROJECT);
|
|
5440
|
-
if (!slot || slot.status !== "active") return;
|
|
5540
|
+
const slot = await resolveTargetSlot(span.service);
|
|
5541
|
+
if (!slot) return;
|
|
5441
5542
|
await makeErrorSpanWriter(slot.paths.errorsPath)(span);
|
|
5442
5543
|
}
|
|
5443
5544
|
});
|