@neat.is/core 0.4.2 → 0.4.4

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/neatd.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startDaemon
4
- } from "./chunk-CB2UK4EH.js";
4
+ } from "./chunk-J4YBTD24.js";
5
5
  import {
6
6
  listProjects,
7
7
  registryPath
8
- } from "./chunk-NTQHMXWE.js";
8
+ } from "./chunk-G3YGPWJL.js";
9
9
  import {
10
10
  BindAuthorityError,
11
11
  __require
12
- } from "./chunk-KYRIQIPG.js";
12
+ } from "./chunk-ZVNP3ZDH.js";
13
13
 
14
14
  // src/neatd.ts
15
15
  import { promises as fs } from "fs";
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  reshapeGrpcRequest,
3
3
  startOtelGrpcReceiver
4
- } from "./chunk-D5PIJFBE.js";
5
- import "./chunk-KYRIQIPG.js";
4
+ } from "./chunk-YJOA7BBF.js";
5
+ import "./chunk-ZVNP3ZDH.js";
6
6
  export {
7
7
  reshapeGrpcRequest,
8
8
  startOtelGrpcReceiver
9
9
  };
10
- //# sourceMappingURL=otel-grpc-QTX2YQJZ.js.map
10
+ //# sourceMappingURL=otel-grpc-FIERFRGD.js.map
package/dist/server.cjs CHANGED
@@ -436,6 +436,64 @@ async function buildOtelReceiver(opts) {
436
436
  for (const s of spans) queue.push(s);
437
437
  drainPromise = drainPromise.then(() => drain());
438
438
  };
439
+ const projectQueue = [];
440
+ let projectDraining = false;
441
+ let projectDrainPromise = Promise.resolve();
442
+ const drainProject = async () => {
443
+ if (projectDraining) return;
444
+ projectDraining = true;
445
+ try {
446
+ while (projectQueue.length > 0) {
447
+ const { project, span } = projectQueue.shift();
448
+ try {
449
+ if (opts.onProjectSpan) {
450
+ await opts.onProjectSpan(project, span);
451
+ } else {
452
+ await opts.onSpan(span);
453
+ }
454
+ } catch (err) {
455
+ console.warn(`[neat] otel handler error: ${err.message}`);
456
+ }
457
+ }
458
+ } finally {
459
+ projectDraining = false;
460
+ }
461
+ };
462
+ const enqueueProject = (project, spans) => {
463
+ if (spans.length === 0) return;
464
+ for (const s of spans) projectQueue.push({ project, span: s });
465
+ projectDrainPromise = projectDrainPromise.then(() => drainProject());
466
+ };
467
+ const legacyEndpointWarned = /* @__PURE__ */ new Set();
468
+ function warnLegacyEndpoint(serviceName) {
469
+ if (legacyEndpointWarned.has(serviceName)) return;
470
+ legacyEndpointWarned.add(serviceName);
471
+ console.warn(
472
+ `[neatd] received span on the global endpoint; migrate OTEL_EXPORTER_OTLP_TRACES_ENDPOINT to /projects/<name>/v1/traces (service.name="${serviceName}").`
473
+ );
474
+ }
475
+ async function readOtlpBody(req) {
476
+ const ct = (req.headers["content-type"] ?? "").toString().split(";")[0].trim().toLowerCase();
477
+ if (ct === "application/x-protobuf") {
478
+ try {
479
+ const body = await decodeProtobufBody(req.body);
480
+ return { ok: true, body, flavor: "protobuf" };
481
+ } catch (err) {
482
+ return { ok: false, code: 400, error: `protobuf decode failed: ${err.message}` };
483
+ }
484
+ }
485
+ if (!ct || ct === "application/json") {
486
+ return { ok: true, body: req.body ?? {}, flavor: "json" };
487
+ }
488
+ return { ok: false, code: 415, error: `unsupported content-type: ${ct}` };
489
+ }
490
+ function sendOtlpSuccess(reply, flavor) {
491
+ if (flavor === "protobuf") {
492
+ const buf = encodeProtobufResponseBody();
493
+ return reply.code(200).header("content-type", "application/x-protobuf").send(buf);
494
+ }
495
+ return reply.code(200).header("content-type", "application/json").send({ partialSuccess: {} });
496
+ }
439
497
  app.addContentTypeParser(
440
498
  "application/x-protobuf",
441
499
  { parseAs: "buffer", bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024 },
@@ -445,26 +503,44 @@ async function buildOtelReceiver(opts) {
445
503
  );
446
504
  app.get("/health", async () => ({ ok: true }));
447
505
  app.post("/v1/traces", async (req, reply) => {
448
- const ct = (req.headers["content-type"] ?? "").toString().split(";")[0].trim().toLowerCase();
449
- let body;
450
- let responseFlavor;
451
- if (ct === "application/x-protobuf") {
452
- responseFlavor = "protobuf";
506
+ const result = await readOtlpBody(req);
507
+ if (!result.ok) {
508
+ return reply.code(result.code).send({ error: result.error });
509
+ }
510
+ const spans = parseOtlpRequest(result.body);
511
+ for (const s of spans) warnLegacyEndpoint(s.service);
512
+ if (opts.onErrorSpanSync) {
453
513
  try {
454
- body = await decodeProtobufBody(req.body);
514
+ for (const span of spans) {
515
+ if (span.statusCode === 2) await opts.onErrorSpanSync(span);
516
+ }
455
517
  } catch (err) {
456
- return reply.code(400).send({
457
- error: `protobuf decode failed: ${err.message}`
518
+ return reply.code(500).send({
519
+ error: `error-event write failed: ${err.message}`
458
520
  });
459
521
  }
460
- } else if (!ct || ct === "application/json") {
461
- responseFlavor = "json";
462
- body = req.body ?? {};
463
- } else {
464
- return reply.code(415).send({ error: `unsupported content-type: ${ct}` });
465
522
  }
466
- const spans = parseOtlpRequest(body);
467
- if (opts.onErrorSpanSync) {
523
+ enqueue(spans);
524
+ return sendOtlpSuccess(reply, result.flavor);
525
+ });
526
+ app.post("/projects/:project/v1/traces", async (req, reply) => {
527
+ const project = req.params.project;
528
+ const result = await readOtlpBody(req);
529
+ if (!result.ok) {
530
+ return reply.code(result.code).send({ error: result.error });
531
+ }
532
+ const spans = parseOtlpRequest(result.body);
533
+ if (opts.onProjectErrorSpanSync) {
534
+ try {
535
+ for (const span of spans) {
536
+ if (span.statusCode === 2) await opts.onProjectErrorSpanSync(project, span);
537
+ }
538
+ } catch (err) {
539
+ return reply.code(500).send({
540
+ error: `error-event write failed: ${err.message}`
541
+ });
542
+ }
543
+ } else if (opts.onErrorSpanSync) {
468
544
  try {
469
545
  for (const span of spans) {
470
546
  if (span.statusCode === 2) await opts.onErrorSpanSync(span);
@@ -475,17 +551,13 @@ async function buildOtelReceiver(opts) {
475
551
  });
476
552
  }
477
553
  }
478
- enqueue(spans);
479
- if (responseFlavor === "protobuf") {
480
- const buf = encodeProtobufResponseBody();
481
- return reply.code(200).header("content-type", "application/x-protobuf").send(buf);
482
- }
483
- return reply.code(200).header("content-type", "application/json").send({ partialSuccess: {} });
554
+ enqueueProject(project, spans);
555
+ return sendOtlpSuccess(reply, result.flavor);
484
556
  });
485
557
  const decorated = app;
486
558
  decorated.flushPending = async () => {
487
- while (queue.length > 0 || draining) {
488
- await drainPromise;
559
+ while (queue.length > 0 || draining || projectQueue.length > 0 || projectDraining) {
560
+ await Promise.all([drainPromise, projectDrainPromise]);
489
561
  }
490
562
  };
491
563
  return decorated;