@nextera.one/axis-server-sdk 1.2.0 → 1.2.1

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.mjs CHANGED
@@ -18,6 +18,7 @@ var __decorateClass = (decorators, target, key, kind) => {
18
18
  if (kind && result) __defProp(target, key, result);
19
19
  return result;
20
20
  };
21
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
21
22
 
22
23
  // src/decorators/handler.decorator.ts
23
24
  import { Injectable, SetMetadata } from "@nestjs/common";
@@ -56,6 +57,24 @@ function Intent(action, options) {
56
57
  };
57
58
  }
58
59
 
60
+ // src/decorators/intent-body.decorator.ts
61
+ import "reflect-metadata";
62
+ var INTENT_BODY_KEY = "axis:intent:body";
63
+ function IntentBody(decoder) {
64
+ return (target, propertyKey) => {
65
+ Reflect.defineMetadata(INTENT_BODY_KEY, decoder, target, propertyKey);
66
+ };
67
+ }
68
+
69
+ // src/decorators/intent-sensors.decorator.ts
70
+ import "reflect-metadata";
71
+ var INTENT_SENSORS_KEY = "axis:intent:sensors";
72
+ function IntentSensors(sensors) {
73
+ return (target, propertyKey) => {
74
+ Reflect.defineMetadata(INTENT_SENSORS_KEY, sensors, target, propertyKey);
75
+ };
76
+ }
77
+
59
78
  // src/decorators/tlv-field.decorator.ts
60
79
  import "reflect-metadata";
61
80
  var TLV_FIELDS_KEY = "axis:tlv:fields";
@@ -269,11 +288,146 @@ __decorateClass([
269
288
  ], AxisResponseDto.prototype, "updated_by", 2);
270
289
 
271
290
  // src/engine/intent.router.ts
272
- import { Injectable as Injectable2 } from "@nestjs/common";
291
+ import { Injectable as Injectable2, Logger, Optional } from "@nestjs/common";
292
+
293
+ // src/sensor/axis-sensor.ts
294
+ var Decision = /* @__PURE__ */ ((Decision2) => {
295
+ Decision2["ALLOW"] = "ALLOW";
296
+ Decision2["DENY"] = "DENY";
297
+ Decision2["THROTTLE"] = "THROTTLE";
298
+ Decision2["FLAG"] = "FLAG";
299
+ return Decision2;
300
+ })(Decision || {});
301
+ function normalizeSensorDecision(sensorDecision) {
302
+ if ("action" in sensorDecision) {
303
+ switch (sensorDecision.action) {
304
+ case "ALLOW":
305
+ return {
306
+ allow: true,
307
+ riskScore: 0,
308
+ reasons: [],
309
+ meta: sensorDecision.meta
310
+ };
311
+ case "DENY":
312
+ return {
313
+ allow: false,
314
+ riskScore: 100,
315
+ reasons: [sensorDecision.code, sensorDecision.reason].filter(
316
+ Boolean
317
+ ),
318
+ meta: sensorDecision.meta,
319
+ retryAfterMs: sensorDecision.retryAfterMs
320
+ };
321
+ case "THROTTLE":
322
+ return {
323
+ allow: false,
324
+ riskScore: 50,
325
+ reasons: ["RATE_LIMIT"],
326
+ retryAfterMs: sensorDecision.retryAfterMs,
327
+ meta: sensorDecision.meta
328
+ };
329
+ case "FLAG":
330
+ return {
331
+ allow: true,
332
+ riskScore: sensorDecision.scoreDelta,
333
+ reasons: sensorDecision.reasons,
334
+ meta: sensorDecision.meta
335
+ };
336
+ }
337
+ }
338
+ return {
339
+ allow: sensorDecision.allow,
340
+ riskScore: sensorDecision.riskScore,
341
+ reasons: sensorDecision.reasons,
342
+ tags: sensorDecision.tags,
343
+ meta: sensorDecision.meta,
344
+ tighten: sensorDecision.tighten,
345
+ retryAfterMs: sensorDecision.retryAfterMs
346
+ };
347
+ }
348
+ var SensorDecisions = {
349
+ allow(meta, tags) {
350
+ return {
351
+ decision: "ALLOW" /* ALLOW */,
352
+ allow: true,
353
+ riskScore: 0,
354
+ reasons: [],
355
+ tags,
356
+ meta
357
+ };
358
+ },
359
+ deny(code, reason, meta) {
360
+ return {
361
+ decision: "DENY" /* DENY */,
362
+ allow: false,
363
+ riskScore: 100,
364
+ code,
365
+ reasons: [code, reason].filter(Boolean),
366
+ meta
367
+ };
368
+ },
369
+ throttle(retryAfterMs, meta) {
370
+ return {
371
+ decision: "THROTTLE" /* THROTTLE */,
372
+ allow: false,
373
+ riskScore: 50,
374
+ retryAfterMs,
375
+ code: "RATE_LIMIT",
376
+ reasons: ["RATE_LIMIT"],
377
+ meta
378
+ };
379
+ },
380
+ flag(scoreDelta, reasons, meta) {
381
+ return {
382
+ decision: "FLAG" /* FLAG */,
383
+ allow: true,
384
+ riskScore: scoreDelta,
385
+ scoreDelta,
386
+ reasons,
387
+ meta
388
+ };
389
+ }
390
+ };
391
+
392
+ // src/engine/intent.router.ts
273
393
  var IntentRouter = class {
274
- constructor() {
394
+ constructor(moduleRef) {
395
+ this.moduleRef = moduleRef;
396
+ this.logger = new Logger(IntentRouter.name);
275
397
  /** Internal registry of dynamic intent handlers */
276
398
  this.handlers = /* @__PURE__ */ new Map();
399
+ /** Per-intent sensor classes (resolved at call time) */
400
+ this.intentSensors = /* @__PURE__ */ new Map();
401
+ /** Per-intent body decoders */
402
+ this.intentDecoders = /* @__PURE__ */ new Map();
403
+ /** Per-intent TLV schemas */
404
+ this.intentSchemas = /* @__PURE__ */ new Map();
405
+ /** Per-intent custom validators */
406
+ this.intentValidators = /* @__PURE__ */ new Map();
407
+ /** Per-intent operation kind */
408
+ this.intentKinds = /* @__PURE__ */ new Map();
409
+ }
410
+ getSchema(intent) {
411
+ return this.intentSchemas.get(intent);
412
+ }
413
+ getValidators(intent) {
414
+ return this.intentValidators.get(intent);
415
+ }
416
+ has(intent) {
417
+ return this.handlers.has(intent) || IntentRouter.BUILTIN_INTENTS.has(intent);
418
+ }
419
+ getRegisteredIntents() {
420
+ return [...IntentRouter.BUILTIN_INTENTS, ...this.handlers.keys()];
421
+ }
422
+ getIntentEntry(intent) {
423
+ if (!this.has(intent)) return null;
424
+ return {
425
+ schema: this.intentSchemas.get(intent),
426
+ validators: this.intentValidators.get(intent),
427
+ hasSensors: this.intentSensors.has(intent),
428
+ builtin: IntentRouter.BUILTIN_INTENTS.has(intent),
429
+ kind: this.intentKinds.get(intent)
430
+ };
277
431
  }
278
432
  /**
279
433
  * Registers a handler for a specific intent.
@@ -309,6 +463,16 @@ var IntentRouter = class {
309
463
  } else {
310
464
  this.register(intentName, fn);
311
465
  }
466
+ this.registerIntentMeta(intentName, Object.getPrototypeOf(instance), String(route.methodName));
467
+ }
468
+ const proto = Object.getPrototypeOf(instance);
469
+ for (const key of Object.getOwnPropertyNames(proto)) {
470
+ const meta = Reflect.getMetadata(INTENT_METADATA_KEY, proto, key);
471
+ if (!meta?.intent) continue;
472
+ if (!this.handlers.has(meta.intent)) {
473
+ this.register(meta.intent, instance[key].bind(instance));
474
+ }
475
+ this.registerIntentMeta(meta.intent, proto, key);
312
476
  }
313
477
  }
314
478
  /**
@@ -332,6 +496,7 @@ var IntentRouter = class {
332
496
  intent = new TextDecoder().decode(intentBytes);
333
497
  let effect;
334
498
  if (intent === "system.ping" || intent === "public.ping") {
499
+ this.logger.debug("PING received");
335
500
  effect = {
336
501
  ok: true,
337
502
  effect: "pong",
@@ -372,6 +537,7 @@ var IntentRouter = class {
372
537
  if (!innerIntent) {
373
538
  throw new Error("INTENT.EXEC missing inner intent");
374
539
  }
540
+ this.logger.debug(`EXEC: routing to inner intent '${innerIntent}'`);
375
541
  const innerFrame = {
376
542
  ...frame,
377
543
  headers: new Map(frame.headers),
@@ -387,8 +553,23 @@ var IntentRouter = class {
387
553
  if (!handler) {
388
554
  throw new Error(`Intent not found: ${intent}`);
389
555
  }
556
+ const sensorClasses = this.intentSensors.get(intent);
557
+ if (sensorClasses && sensorClasses.length > 0) {
558
+ await this.runIntentSensors(sensorClasses, intent, frame);
559
+ }
560
+ const decoder = this.intentDecoders.get(intent);
561
+ let decodedBody = frame.body;
562
+ if (decoder) {
563
+ try {
564
+ decodedBody = decoder(Buffer.from(frame.body));
565
+ } catch (decodeErr) {
566
+ throw new Error(
567
+ `IntentBody decode failed for ${intent}: ${decodeErr.message}`
568
+ );
569
+ }
570
+ }
390
571
  if (typeof handler === "function") {
391
- const resultBody = await handler(frame.body, frame.headers);
572
+ const resultBody = decoder ? await handler(decodedBody, frame.headers) : await handler(frame.body, frame.headers);
392
573
  effect = {
393
574
  ok: true,
394
575
  effect: "complete",
@@ -398,10 +579,7 @@ var IntentRouter = class {
398
579
  if (typeof handler.handle === "function") {
399
580
  effect = await handler.handle(frame);
400
581
  } else if (typeof handler.execute === "function") {
401
- const bodyRes = await handler.execute(
402
- frame.body,
403
- frame.headers
404
- );
582
+ const bodyRes = decoder ? await handler.execute(decodedBody, frame.headers) : await handler.execute(frame.body, frame.headers);
405
583
  effect = {
406
584
  ok: true,
407
585
  effect: "complete",
@@ -414,20 +592,130 @@ var IntentRouter = class {
414
592
  }
415
593
  }
416
594
  }
417
- this.recordLatency(intent, start);
595
+ this.logIntent(intent, start, true);
418
596
  return effect;
419
597
  } catch (e) {
420
- console.error(`Error routing intent ${intent}:`, e.message);
598
+ this.logIntent(intent, start, false, e.message);
421
599
  throw e;
422
600
  }
423
601
  }
424
- recordLatency(intent, start) {
602
+ logIntent(intent, start, ok, error) {
425
603
  const diff = process.hrtime(start);
426
- void diff;
604
+ const ms = (diff[0] * 1e3 + diff[1] / 1e6).toFixed(2);
605
+ if (ok) {
606
+ this.logger.debug(`${intent} completed in ${ms}ms`);
607
+ } else {
608
+ this.logger.warn(`${intent} failed in ${ms}ms - ${error}`);
609
+ }
610
+ }
611
+ registerIntentMeta(intent, proto, methodName) {
612
+ const decoder = Reflect.getMetadata(INTENT_BODY_KEY, proto, methodName);
613
+ if (decoder) {
614
+ this.intentDecoders.set(intent, decoder);
615
+ }
616
+ const sensors = Reflect.getMetadata(INTENT_SENSORS_KEY, proto, methodName);
617
+ if (sensors && Array.isArray(sensors) && sensors.length > 0) {
618
+ this.intentSensors.set(intent, sensors);
619
+ }
620
+ const meta = Reflect.getMetadata(INTENT_METADATA_KEY, proto, methodName);
621
+ if (meta) {
622
+ this.storeSchema(meta);
623
+ if (meta.kind) {
624
+ this.intentKinds.set(intent, meta.kind);
625
+ }
626
+ }
627
+ }
628
+ async runIntentSensors(sensorClasses, intent, frame) {
629
+ if (!this.moduleRef) return;
630
+ for (const SensorClass of sensorClasses) {
631
+ let sensor;
632
+ try {
633
+ sensor = this.moduleRef.get(SensorClass, { strict: false });
634
+ } catch {
635
+ this.logger.warn(
636
+ `@IntentSensors: could not resolve ${SensorClass.name} for ${intent}`
637
+ );
638
+ continue;
639
+ }
640
+ const sensorInput = {
641
+ rawBytes: frame.body,
642
+ intent,
643
+ body: frame.body,
644
+ headerTLVs: frame.headers,
645
+ metadata: { phase: "intent", intent }
646
+ };
647
+ if (sensor.supports && !sensor.supports(sensorInput)) continue;
648
+ const decision = normalizeSensorDecision(await sensor.run(sensorInput));
649
+ if (!decision.allow) {
650
+ const reason = decision.reasons[0] || `${sensor.name}:DENIED`;
651
+ this.logger.warn(
652
+ `Intent sensor ${sensor.name} denied ${intent}: ${reason}`
653
+ );
654
+ throw new Error(`SENSOR_DENY:${reason}`);
655
+ }
656
+ }
657
+ }
658
+ storeSchema(meta) {
659
+ if (meta.dto) {
660
+ if (meta.tlv && meta.tlv.length > 0) {
661
+ this.logger.warn(
662
+ `${meta.intent}: both 'dto' and 'tlv' specified - using dto, ignoring tlv`
663
+ );
664
+ }
665
+ const extracted = extractDtoSchema(meta.dto);
666
+ const schema2 = {
667
+ intent: meta.intent,
668
+ version: 1,
669
+ bodyProfile: meta.bodyProfile || "TLV_MAP",
670
+ fields: extracted.fields.map((f) => ({
671
+ name: f.name,
672
+ tlv: f.tag,
673
+ kind: f.kind,
674
+ required: f.required,
675
+ maxLen: f.maxLen,
676
+ max: f.max,
677
+ scope: f.scope
678
+ }))
679
+ };
680
+ this.intentSchemas.set(meta.intent, schema2);
681
+ if (extracted.validators.size > 0) {
682
+ this.intentValidators.set(meta.intent, extracted.validators);
683
+ }
684
+ if (!this.intentDecoders.has(meta.intent)) {
685
+ this.intentDecoders.set(meta.intent, buildDtoDecoder(meta.dto));
686
+ }
687
+ return;
688
+ }
689
+ if (!meta.tlv || meta.tlv.length === 0) return;
690
+ const schema = {
691
+ intent: meta.intent,
692
+ version: 1,
693
+ bodyProfile: meta.bodyProfile || "TLV_MAP",
694
+ fields: meta.tlv.map((f) => ({
695
+ name: f.name,
696
+ tlv: f.tag,
697
+ kind: f.kind,
698
+ required: f.required,
699
+ maxLen: f.maxLen,
700
+ max: f.max,
701
+ scope: f.scope
702
+ }))
703
+ };
704
+ this.intentSchemas.set(meta.intent, schema);
427
705
  }
428
706
  };
707
+ /** Intents handled inline in route() — not in `handlers` map */
708
+ IntentRouter.BUILTIN_INTENTS = /* @__PURE__ */ new Set([
709
+ "system.ping",
710
+ "public.ping",
711
+ "system.time",
712
+ "system.echo",
713
+ "INTENT.EXEC",
714
+ "axis.intent.exec"
715
+ ]);
429
716
  IntentRouter = __decorateClass([
430
- Injectable2()
717
+ Injectable2(),
718
+ __decorateParam(0, Optional())
431
719
  ], IntentRouter);
432
720
 
433
721
  // src/core/constants.ts
@@ -1866,105 +2154,6 @@ function buildPacket(hdr, body, sig, flags = 0) {
1866
2154
  };
1867
2155
  }
1868
2156
 
1869
- // src/sensor/axis-sensor.ts
1870
- var Decision = /* @__PURE__ */ ((Decision2) => {
1871
- Decision2["ALLOW"] = "ALLOW";
1872
- Decision2["DENY"] = "DENY";
1873
- Decision2["THROTTLE"] = "THROTTLE";
1874
- Decision2["FLAG"] = "FLAG";
1875
- return Decision2;
1876
- })(Decision || {});
1877
- function normalizeSensorDecision(sensorDecision) {
1878
- if ("action" in sensorDecision) {
1879
- switch (sensorDecision.action) {
1880
- case "ALLOW":
1881
- return {
1882
- allow: true,
1883
- riskScore: 0,
1884
- reasons: [],
1885
- meta: sensorDecision.meta
1886
- };
1887
- case "DENY":
1888
- return {
1889
- allow: false,
1890
- riskScore: 100,
1891
- reasons: [sensorDecision.code, sensorDecision.reason].filter(
1892
- Boolean
1893
- ),
1894
- meta: sensorDecision.meta,
1895
- retryAfterMs: sensorDecision.retryAfterMs
1896
- };
1897
- case "THROTTLE":
1898
- return {
1899
- allow: false,
1900
- riskScore: 50,
1901
- reasons: ["RATE_LIMIT"],
1902
- retryAfterMs: sensorDecision.retryAfterMs,
1903
- meta: sensorDecision.meta
1904
- };
1905
- case "FLAG":
1906
- return {
1907
- allow: true,
1908
- riskScore: sensorDecision.scoreDelta,
1909
- reasons: sensorDecision.reasons,
1910
- meta: sensorDecision.meta
1911
- };
1912
- }
1913
- }
1914
- return {
1915
- allow: sensorDecision.allow,
1916
- riskScore: sensorDecision.riskScore,
1917
- reasons: sensorDecision.reasons,
1918
- tags: sensorDecision.tags,
1919
- meta: sensorDecision.meta,
1920
- tighten: sensorDecision.tighten,
1921
- retryAfterMs: sensorDecision.retryAfterMs
1922
- };
1923
- }
1924
- var SensorDecisions = {
1925
- allow(meta, tags) {
1926
- return {
1927
- decision: "ALLOW" /* ALLOW */,
1928
- allow: true,
1929
- riskScore: 0,
1930
- reasons: [],
1931
- tags,
1932
- meta
1933
- };
1934
- },
1935
- deny(code, reason, meta) {
1936
- return {
1937
- decision: "DENY" /* DENY */,
1938
- allow: false,
1939
- riskScore: 100,
1940
- code,
1941
- reasons: [code, reason].filter(Boolean),
1942
- meta
1943
- };
1944
- },
1945
- throttle(retryAfterMs, meta) {
1946
- return {
1947
- decision: "THROTTLE" /* THROTTLE */,
1948
- allow: false,
1949
- riskScore: 50,
1950
- retryAfterMs,
1951
- code: "RATE_LIMIT",
1952
- reasons: ["RATE_LIMIT"],
1953
- meta
1954
- };
1955
- },
1956
- flag(scoreDelta, reasons, meta) {
1957
- return {
1958
- decision: "FLAG" /* FLAG */,
1959
- allow: true,
1960
- riskScore: scoreDelta,
1961
- scoreDelta,
1962
- reasons,
1963
- meta
1964
- };
1965
- }
1966
- };
1967
-
1968
2157
  // src/security/scopes.ts
1969
2158
  function hasScope(scopes, required) {
1970
2159
  if (!Array.isArray(scopes) || scopes.length === 0) {
@@ -2262,13 +2451,245 @@ function isTimestampValid(ts, skewSeconds = 120) {
2262
2451
  const diff = Math.abs(now - ts);
2263
2452
  return diff <= skewSeconds;
2264
2453
  }
2454
+
2455
+ // src/upload/axis-files.handlers.ts
2456
+ import { Inject, Injectable as Injectable3, Logger as Logger2, Optional as Optional2 } from "@nestjs/common";
2457
+ import * as crypto2 from "crypto";
2458
+
2459
+ // src/upload/upload.tokens.ts
2460
+ var AXIS_UPLOAD_SESSION_STORE = "AXIS_UPLOAD_SESSION_STORE";
2461
+ var AXIS_UPLOAD_FILE_STORE = "AXIS_UPLOAD_FILE_STORE";
2462
+ var AXIS_UPLOAD_RECEIPT_SIGNER = "AXIS_UPLOAD_RECEIPT_SIGNER";
2463
+
2464
+ // src/upload/axis-files.handlers.ts
2465
+ var AxisFilesDownloadHandler = class {
2466
+ constructor(sessions, files) {
2467
+ this.sessions = sessions;
2468
+ this.files = files;
2469
+ this.logger = new Logger2(AxisFilesDownloadHandler.name);
2470
+ this.name = "axis.files.download";
2471
+ this.open = true;
2472
+ this.description = "File download handler";
2473
+ }
2474
+ async execute(body, headers) {
2475
+ const h = headers;
2476
+ if (!h) throw new Error("MISSING_HEADERS");
2477
+ const uploadIdBytes = h.get(20);
2478
+ if (!uploadIdBytes) throw new Error("MISSING_UPLOAD_ID");
2479
+ const uploadId = new TextDecoder().decode(uploadIdBytes);
2480
+ let rangeStart = 0;
2481
+ let rangeLen = -1;
2482
+ const startBytes = h.get(21);
2483
+ if (startBytes) {
2484
+ const { value } = decodeVarint(startBytes);
2485
+ rangeStart = value;
2486
+ }
2487
+ const lenBytes = h.get(22);
2488
+ if (lenBytes) {
2489
+ const { value } = decodeVarint(lenBytes);
2490
+ rangeLen = value;
2491
+ }
2492
+ const session = await this.sessions.findByFileId(uploadId);
2493
+ if (!session) {
2494
+ throw new Error(`SESSION_NOT_FOUND: ${uploadId}`);
2495
+ }
2496
+ if (session.status !== "COMPLETE") {
2497
+ throw new Error(`FILE_NOT_READY: Status is ${session.status}`);
2498
+ }
2499
+ const stat = await this.files.statFinal(
2500
+ uploadId,
2501
+ session.filename
2502
+ );
2503
+ const fileSize = stat.size;
2504
+ if (rangeStart < 0) rangeStart = 0;
2505
+ if (rangeStart >= fileSize) throw new Error("RANGE_OUT_OF_BOUNDS");
2506
+ let end = fileSize;
2507
+ if (rangeLen >= 0) {
2508
+ end = Math.min(rangeStart + rangeLen, fileSize);
2509
+ }
2510
+ const actualLen = end - rangeStart;
2511
+ const buffer = await this.files.readFinalRange(
2512
+ uploadId,
2513
+ session.filename,
2514
+ rangeStart,
2515
+ actualLen
2516
+ );
2517
+ const responseHeaders = /* @__PURE__ */ new Map();
2518
+ responseHeaders.set(30, encodeVarint(fileSize));
2519
+ responseHeaders.set(31, encodeVarint(rangeStart));
2520
+ responseHeaders.set(32, encodeVarint(actualLen));
2521
+ return {
2522
+ ok: true,
2523
+ effect: "FILE_PART",
2524
+ body: buffer,
2525
+ headers: responseHeaders
2526
+ };
2527
+ }
2528
+ };
2529
+ __decorateClass([
2530
+ Intent("file.download", { absolute: true, kind: "read" })
2531
+ ], AxisFilesDownloadHandler.prototype, "execute", 1);
2532
+ AxisFilesDownloadHandler = __decorateClass([
2533
+ Handler("axis.files.download"),
2534
+ Injectable3(),
2535
+ __decorateParam(0, Inject(AXIS_UPLOAD_SESSION_STORE)),
2536
+ __decorateParam(1, Inject(AXIS_UPLOAD_FILE_STORE))
2537
+ ], AxisFilesDownloadHandler);
2538
+ var AxisFilesFinalizeHandler = class {
2539
+ constructor(sessions, files, keyring) {
2540
+ this.sessions = sessions;
2541
+ this.files = files;
2542
+ this.keyring = keyring;
2543
+ this.logger = new Logger2(AxisFilesFinalizeHandler.name);
2544
+ this.name = "axis.files.finalize";
2545
+ this.open = false;
2546
+ this.description = "File upload finalization handler";
2547
+ }
2548
+ async execute(body, headers) {
2549
+ const bodyStr = new TextDecoder().decode(body);
2550
+ const req = JSON.parse(bodyStr);
2551
+ const { fileId, expectedHash } = req;
2552
+ if (!fileId) throw new Error("MISSING_FILE_ID");
2553
+ const session = await this.sessions.findByFileId(fileId);
2554
+ if (!session) throw new Error("SESSION_NOT_FOUND");
2555
+ if (!await this.files.hasTemp(fileId)) {
2556
+ throw new Error("CHUNKS_NOT_FOUND");
2557
+ }
2558
+ const hash = crypto2.createHash("sha256");
2559
+ const rs = this.files.createTempReadStream(fileId);
2560
+ for await (const chunk of rs) {
2561
+ hash.update(chunk);
2562
+ }
2563
+ const finalHash = hash.digest("hex");
2564
+ if (expectedHash && finalHash !== expectedHash) {
2565
+ throw new Error("HASH_MISMATCH");
2566
+ }
2567
+ const finalPath = await this.files.moveTempToFinal(
2568
+ fileId,
2569
+ session.filename
2570
+ );
2571
+ await this.sessions.updateStatus(fileId, "COMPLETE", null);
2572
+ if (!this.keyring) {
2573
+ this.logger.warn("Receipt signer not configured; returning unsigned receipt");
2574
+ return {
2575
+ ok: true,
2576
+ effect: "FILE_FINALIZED",
2577
+ body: new TextEncoder().encode(
2578
+ JSON.stringify({
2579
+ uploadId: fileId,
2580
+ sha256_final: finalHash,
2581
+ totalSize: session.totalSize,
2582
+ tsMs: Date.now(),
2583
+ path: finalPath
2584
+ })
2585
+ )
2586
+ };
2587
+ }
2588
+ const receiptData = {
2589
+ uploadId: fileId,
2590
+ sha256_final: finalHash,
2591
+ totalSize: session.totalSize,
2592
+ tsMs: Date.now()
2593
+ };
2594
+ const receiptJson = JSON.stringify(receiptData);
2595
+ const receiptBody = new TextEncoder().encode(receiptJson);
2596
+ const SIG_PRESENT = 1;
2597
+ const responseFrame = {
2598
+ flags: SIG_PRESENT,
2599
+ headers: /* @__PURE__ */ new Map(),
2600
+ body: receiptBody,
2601
+ sig: new Uint8Array(0)
2602
+ };
2603
+ const signTarget = getSignTarget(responseFrame);
2604
+ const { sig, kid } = this.keyring.signActive(signTarget);
2605
+ responseFrame.sig = sig;
2606
+ return {
2607
+ ok: true,
2608
+ effect: "FILE_FINALIZED",
2609
+ data: encodeFrame(responseFrame),
2610
+ headers: /* @__PURE__ */ new Map([[1, new TextEncoder().encode(kid)]])
2611
+ };
2612
+ }
2613
+ };
2614
+ __decorateClass([
2615
+ Intent("file.finalize", { absolute: true, kind: "action" })
2616
+ ], AxisFilesFinalizeHandler.prototype, "execute", 1);
2617
+ AxisFilesFinalizeHandler = __decorateClass([
2618
+ Handler("axis.files.finalize"),
2619
+ Injectable3(),
2620
+ __decorateParam(0, Inject(AXIS_UPLOAD_SESSION_STORE)),
2621
+ __decorateParam(1, Inject(AXIS_UPLOAD_FILE_STORE)),
2622
+ __decorateParam(2, Optional2()),
2623
+ __decorateParam(2, Inject(AXIS_UPLOAD_RECEIPT_SIGNER))
2624
+ ], AxisFilesFinalizeHandler);
2625
+
2626
+ // src/upload/disk-upload-file.store.ts
2627
+ import * as fs from "fs";
2628
+ import * as path from "path";
2629
+ var DiskUploadFileStore = class {
2630
+ constructor(options) {
2631
+ this.uploadDir = options.uploadDir;
2632
+ this.chunkDir = options.chunkDir;
2633
+ }
2634
+ getFinalPath(fileId, filename) {
2635
+ const safeFilename = filename ? path.basename(filename) : fileId;
2636
+ return path.join(this.uploadDir, safeFilename);
2637
+ }
2638
+ getTempPath(fileId) {
2639
+ const safeId = path.basename(fileId);
2640
+ return path.join(this.chunkDir, safeId);
2641
+ }
2642
+ async statFinal(fileId, filename) {
2643
+ const finalPath = this.getFinalPath(fileId, filename);
2644
+ if (!fs.existsSync(finalPath)) {
2645
+ throw new Error("FILE_MISSING_ON_DISK");
2646
+ }
2647
+ const stat = fs.statSync(finalPath);
2648
+ return { path: finalPath, size: stat.size };
2649
+ }
2650
+ async readFinalRange(fileId, filename, start, length) {
2651
+ const finalPath = this.getFinalPath(fileId, filename);
2652
+ const buffer = Buffer.alloc(length);
2653
+ const fd = fs.openSync(finalPath, "r");
2654
+ try {
2655
+ fs.readSync(fd, buffer, 0, length, start);
2656
+ } finally {
2657
+ fs.closeSync(fd);
2658
+ }
2659
+ return buffer;
2660
+ }
2661
+ async hasTemp(fileId) {
2662
+ const tempPath = this.getTempPath(fileId);
2663
+ return fs.existsSync(tempPath);
2664
+ }
2665
+ async moveTempToFinal(fileId, filename) {
2666
+ const tempPath = this.getTempPath(fileId);
2667
+ const finalPath = this.getFinalPath(fileId, filename);
2668
+ try {
2669
+ await fs.promises.rename(tempPath, finalPath);
2670
+ } catch {
2671
+ await fs.promises.copyFile(tempPath, finalPath);
2672
+ await fs.promises.unlink(tempPath);
2673
+ }
2674
+ return finalPath;
2675
+ }
2676
+ createTempReadStream(fileId) {
2677
+ const tempPath = this.getTempPath(fileId);
2678
+ return fs.createReadStream(tempPath);
2679
+ }
2680
+ };
2265
2681
  export {
2266
2682
  ATS1_HDR,
2267
2683
  ATS1_SCHEMA,
2268
2684
  AXIS_MAGIC,
2269
2685
  AXIS_OPCODES,
2686
+ AXIS_UPLOAD_FILE_STORE,
2687
+ AXIS_UPLOAD_RECEIPT_SIGNER,
2688
+ AXIS_UPLOAD_SESSION_STORE,
2270
2689
  AXIS_VERSION,
2271
2690
  ats1_exports as Ats1Codec,
2691
+ AxisFilesDownloadHandler,
2692
+ AxisFilesFinalizeHandler,
2272
2693
  AxisFrameZ,
2273
2694
  AxisIdDto,
2274
2695
  T as AxisPacketTags,
@@ -2281,6 +2702,7 @@ export {
2281
2702
  DEFAULT_CONTRACTS,
2282
2703
  DEFAULT_TIMEOUT,
2283
2704
  Decision,
2705
+ DiskUploadFileStore,
2284
2706
  ERR_BAD_SIGNATURE,
2285
2707
  ERR_CONTRACT_VIOLATION,
2286
2708
  ERR_INVALID_PACKET,
@@ -2292,14 +2714,18 @@ export {
2292
2714
  FLAG_HAS_WITNESS,
2293
2715
  HANDLER_METADATA_KEY,
2294
2716
  Handler,
2717
+ INTENT_BODY_KEY,
2295
2718
  INTENT_METADATA_KEY,
2296
2719
  INTENT_REQUIREMENTS,
2297
2720
  INTENT_ROUTES_KEY,
2298
2721
  INTENT_SENSITIVITY_MAP,
2722
+ INTENT_SENSORS_KEY,
2299
2723
  INTENT_TIMEOUTS,
2300
2724
  Intent,
2725
+ IntentBody,
2301
2726
  IntentRouter,
2302
2727
  IntentSensitivity,
2728
+ IntentSensors,
2303
2729
  MAX_BODY_LEN,
2304
2730
  MAX_FRAME_LEN,
2305
2731
  MAX_HDR_LEN,