optropic 2.3.0 → 3.0.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/index.cjs +2105 -2
- package/dist/index.d.cts +2824 -14
- package/dist/index.d.ts +2824 -14
- package/dist/index.js +2027 -2
- package/package.json +5 -3
package/dist/index.cjs
CHANGED
|
@@ -34,17 +34,21 @@ __export(index_exports, {
|
|
|
34
34
|
AuditResource: () => AuditResource,
|
|
35
35
|
AuthenticationError: () => AuthenticationError,
|
|
36
36
|
BatchNotFoundError: () => BatchNotFoundError,
|
|
37
|
+
BatchesResource: () => BatchesResource,
|
|
37
38
|
CodeNotFoundError: () => CodeNotFoundError,
|
|
38
39
|
ComplianceResource: () => ComplianceResource,
|
|
40
|
+
DPPAccessLevel: () => DPPAccessLevel,
|
|
39
41
|
DocumentsResource: () => DocumentsResource,
|
|
40
42
|
InvalidCodeError: () => InvalidCodeError,
|
|
41
43
|
InvalidGTINError: () => InvalidGTINError,
|
|
42
44
|
InvalidSerialError: () => InvalidSerialError,
|
|
43
45
|
KeysResource: () => KeysResource,
|
|
44
46
|
KeysetsResource: () => KeysetsResource,
|
|
47
|
+
M2MResource: () => M2MResource,
|
|
45
48
|
NetworkError: () => NetworkError,
|
|
46
49
|
OptropicClient: () => OptropicClient,
|
|
47
50
|
OptropicError: () => OptropicError,
|
|
51
|
+
Permission: () => Permission,
|
|
48
52
|
ProvenanceResource: () => ProvenanceResource,
|
|
49
53
|
QuotaExceededError: () => QuotaExceededError,
|
|
50
54
|
RateLimitedError: () => RateLimitedError,
|
|
@@ -53,13 +57,87 @@ __export(index_exports, {
|
|
|
53
57
|
SchemasResource: () => SchemasResource,
|
|
54
58
|
ServiceUnavailableError: () => ServiceUnavailableError,
|
|
55
59
|
StaleFilterError: () => StaleFilterError,
|
|
60
|
+
TenantsResource: () => TenantsResource,
|
|
56
61
|
TimeoutError: () => TimeoutError,
|
|
62
|
+
appendRecord: () => appendRecord,
|
|
63
|
+
buildBatteryPassportQR: () => buildBatteryPassportQR,
|
|
57
64
|
buildDPPConfig: () => buildDPPConfig,
|
|
65
|
+
buildDefenceMetadata: () => buildDefenceMetadata,
|
|
66
|
+
buildEPCISDocument: () => buildEPCISDocument,
|
|
67
|
+
buildGS1DigitalLink: () => buildGS1DigitalLink,
|
|
68
|
+
calculateReadiness: () => calculateReadiness,
|
|
69
|
+
calibrateThreshold: () => calibrateThreshold,
|
|
70
|
+
computeDistance: () => computeDistance,
|
|
71
|
+
computeSimilarity: () => computeSimilarity,
|
|
72
|
+
createAuditChain: () => createAuditChain,
|
|
58
73
|
createClient: () => createClient,
|
|
74
|
+
createConsensusResultMessage: () => createConsensusResultMessage,
|
|
75
|
+
createDescriptorExchange: () => createDescriptorExchange,
|
|
59
76
|
createErrorFromResponse: () => createErrorFromResponse,
|
|
77
|
+
createHandshakeAccept: () => createHandshakeAccept,
|
|
78
|
+
createHandshakeInit: () => createHandshakeInit,
|
|
79
|
+
createOrganization: () => createOrganization,
|
|
80
|
+
createRLSPolicy: () => createRLSPolicy,
|
|
81
|
+
createTemplate: () => createTemplate,
|
|
82
|
+
decodeNATOUID: () => decodeNATOUID,
|
|
83
|
+
deleteRLSPolicy: () => deleteRLSPolicy,
|
|
84
|
+
deprecateTemplate: () => deprecateTemplate,
|
|
85
|
+
deserializeMessage: () => deserializeMessage,
|
|
86
|
+
encodeNATOUID: () => encodeNATOUID,
|
|
87
|
+
evaluateConsensus: () => evaluateConsensus,
|
|
88
|
+
exportChain: () => exportChain,
|
|
89
|
+
filterDPPByAccess: () => filterDPPByAccess,
|
|
90
|
+
generatePartnerApiKey: () => generatePartnerApiKey,
|
|
91
|
+
generateQRCodePayload: () => generateQRCodePayload,
|
|
92
|
+
getAILabel: () => getAILabel,
|
|
93
|
+
getDPPAccessPolicy: () => getDPPAccessPolicy,
|
|
94
|
+
getEPCISMappingTable: () => getEPCISMappingTable,
|
|
95
|
+
getOrganization: () => getOrganization,
|
|
96
|
+
getPartner: () => getPartner,
|
|
97
|
+
getPartnerAnalytics: () => getPartnerAnalytics,
|
|
98
|
+
getRolePermissions: () => getRolePermissions,
|
|
99
|
+
getSupportedAIs: () => getSupportedAIs,
|
|
100
|
+
getTemplate: () => getTemplate,
|
|
101
|
+
getUsageQuota: () => getUsageQuota,
|
|
102
|
+
importChain: () => importChain,
|
|
103
|
+
installTemplate: () => installTemplate,
|
|
104
|
+
inviteMember: () => inviteMember,
|
|
105
|
+
isValidAI: () => isValidAI,
|
|
106
|
+
listMembers: () => listMembers,
|
|
107
|
+
listPartners: () => listPartners,
|
|
108
|
+
listRLSPolicies: () => listRLSPolicies,
|
|
109
|
+
listTemplates: () => listTemplates,
|
|
110
|
+
mapMovementToProvenance: () => mapMovementToProvenance,
|
|
111
|
+
mapNATOUIDToOptropic: () => mapNATOUIDToOptropic,
|
|
112
|
+
mapOptropicToNATOUID: () => mapOptropicToNATOUID,
|
|
113
|
+
mapToEPCIS: () => mapToEPCIS,
|
|
114
|
+
mapToOptropicAsset: () => mapToOptropicAsset,
|
|
60
115
|
parseFilterHeader: () => parseFilterHeader,
|
|
116
|
+
parseGS1DigitalLink: () => parseGS1DigitalLink,
|
|
61
117
|
parseSaltsHeader: () => parseSaltsHeader,
|
|
118
|
+
provenanceChainToEPCIS: () => provenanceChainToEPCIS,
|
|
119
|
+
publishTemplate: () => publishTemplate,
|
|
120
|
+
rateTemplate: () => rateTemplate,
|
|
121
|
+
registerPartner: () => registerPartner,
|
|
122
|
+
removeMember: () => removeMember,
|
|
123
|
+
searchTemplates: () => searchTemplates,
|
|
124
|
+
serializeMessage: () => serializeMessage,
|
|
125
|
+
toEPCISEvent: () => toEPCISEvent,
|
|
126
|
+
uninstallTemplate: () => uninstallTemplate,
|
|
127
|
+
updateMemberRole: () => updateMemberRole,
|
|
128
|
+
updateOrganization: () => updateOrganization,
|
|
129
|
+
updatePartner: () => updatePartner,
|
|
130
|
+
updateTemplate: () => updateTemplate,
|
|
131
|
+
updateUsageQuota: () => updateUsageQuota,
|
|
132
|
+
validateAgainstTemplate: () => validateAgainstTemplate,
|
|
133
|
+
validateBatteryPassport: () => validateBatteryPassport,
|
|
62
134
|
validateDPPMetadata: () => validateDPPMetadata,
|
|
135
|
+
validateDefenceMetadata: () => validateDefenceMetadata,
|
|
136
|
+
validateGTIN: () => validateGTIN,
|
|
137
|
+
validateLogisticsMovement: () => validateLogisticsMovement,
|
|
138
|
+
validateMessage: () => validateMessage,
|
|
139
|
+
validateNATOUID: () => validateNATOUID,
|
|
140
|
+
verifyChain: () => verifyChain,
|
|
63
141
|
verifyOffline: () => verifyOffline,
|
|
64
142
|
verifyWebhookSignature: () => verifyWebhookSignature
|
|
65
143
|
});
|
|
@@ -453,6 +531,8 @@ var AssetsResource = class {
|
|
|
453
531
|
this.request = request;
|
|
454
532
|
this.client = client;
|
|
455
533
|
}
|
|
534
|
+
request;
|
|
535
|
+
client;
|
|
456
536
|
async create(params) {
|
|
457
537
|
return this.request({ method: "POST", path: "/v1/assets", body: params });
|
|
458
538
|
}
|
|
@@ -512,6 +592,20 @@ var AssetsResource = class {
|
|
|
512
592
|
async batchCreate(params) {
|
|
513
593
|
return this.request({ method: "POST", path: "/v1/assets/batch", body: params });
|
|
514
594
|
}
|
|
595
|
+
async batchVerify(assetIds) {
|
|
596
|
+
return this.request({
|
|
597
|
+
method: "POST",
|
|
598
|
+
path: "/v1/assets/batch-verify",
|
|
599
|
+
body: { asset_ids: assetIds }
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
async batchRevoke(assetIds, reason) {
|
|
603
|
+
return this.request({
|
|
604
|
+
method: "POST",
|
|
605
|
+
path: "/v1/assets/batch-revoke",
|
|
606
|
+
body: { asset_ids: assetIds, reason }
|
|
607
|
+
});
|
|
608
|
+
}
|
|
515
609
|
buildQuery(params) {
|
|
516
610
|
const entries = Object.entries(params).filter(([, v]) => v !== void 0);
|
|
517
611
|
if (entries.length === 0) return "";
|
|
@@ -524,6 +618,7 @@ var AuditResource = class {
|
|
|
524
618
|
constructor(request) {
|
|
525
619
|
this.request = request;
|
|
526
620
|
}
|
|
621
|
+
request;
|
|
527
622
|
/**
|
|
528
623
|
* List audit events with optional filtering and pagination.
|
|
529
624
|
*/
|
|
@@ -562,11 +657,101 @@ var AuditResource = class {
|
|
|
562
657
|
}
|
|
563
658
|
};
|
|
564
659
|
|
|
660
|
+
// src/resources/batches.ts
|
|
661
|
+
var BatchesResource = class {
|
|
662
|
+
constructor(request) {
|
|
663
|
+
this.request = request;
|
|
664
|
+
}
|
|
665
|
+
request;
|
|
666
|
+
/**
|
|
667
|
+
* Create a new batch job with the given operation and items.
|
|
668
|
+
*/
|
|
669
|
+
async create(params) {
|
|
670
|
+
return this.request({ method: "POST", path: "/v1/batches", body: params });
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Get the status and details of a specific batch job.
|
|
674
|
+
*/
|
|
675
|
+
async get(batchId) {
|
|
676
|
+
return this.request({
|
|
677
|
+
method: "GET",
|
|
678
|
+
path: `/v1/batches/${encodeURIComponent(batchId)}`
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* List batch jobs with optional limiting.
|
|
683
|
+
*/
|
|
684
|
+
async list(params) {
|
|
685
|
+
const query = params ? this.buildQuery(params) : "";
|
|
686
|
+
const result = await this.request({
|
|
687
|
+
method: "GET",
|
|
688
|
+
path: `/v1/batches${query}`
|
|
689
|
+
});
|
|
690
|
+
return result.data;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Wait for a batch job to complete, polling at regular intervals.
|
|
694
|
+
*
|
|
695
|
+
* @param batchId - The ID of the batch to wait for
|
|
696
|
+
* @param opts - Polling configuration (pollInterval in ms, timeout in ms)
|
|
697
|
+
* @returns The final batch result once completed
|
|
698
|
+
* @throws TimeoutError if the operation exceeds the timeout
|
|
699
|
+
*/
|
|
700
|
+
async wait(batchId, opts) {
|
|
701
|
+
const pollInterval = opts?.pollInterval ?? 1e3;
|
|
702
|
+
const timeout = opts?.timeout ?? 3e5;
|
|
703
|
+
const startTime = Date.now();
|
|
704
|
+
while (true) {
|
|
705
|
+
const batch = await this.get(batchId);
|
|
706
|
+
if (batch.status === "completed" || batch.status === "failed" || batch.status === "cancelled") {
|
|
707
|
+
return {
|
|
708
|
+
batchId: batch.batchId,
|
|
709
|
+
operation: batch.operation,
|
|
710
|
+
status: batch.status,
|
|
711
|
+
totalItems: batch.totalItems,
|
|
712
|
+
processedItems: batch.processedItems,
|
|
713
|
+
failedItems: batch.failedItems,
|
|
714
|
+
completedAt: batch.completedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
const elapsed = Date.now() - startTime;
|
|
718
|
+
if (elapsed > timeout) {
|
|
719
|
+
throw new Error(`Batch ${batchId} did not complete within ${timeout}ms`);
|
|
720
|
+
}
|
|
721
|
+
await this.sleep(pollInterval);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Cancel a batch job that is still pending or processing.
|
|
726
|
+
*/
|
|
727
|
+
async cancel(batchId) {
|
|
728
|
+
return this.request({
|
|
729
|
+
method: "POST",
|
|
730
|
+
path: `/v1/batches/${encodeURIComponent(batchId)}/cancel`
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
734
|
+
// PRIVATE HELPERS
|
|
735
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
736
|
+
buildQuery(params) {
|
|
737
|
+
const entries = Object.entries(params).filter(([, v]) => v !== void 0);
|
|
738
|
+
if (entries.length === 0) return "";
|
|
739
|
+
const qs = new URLSearchParams(
|
|
740
|
+
entries.map(([k, v]) => [k, String(v)])
|
|
741
|
+
);
|
|
742
|
+
return `?${qs.toString()}`;
|
|
743
|
+
}
|
|
744
|
+
sleep(ms) {
|
|
745
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
|
|
565
749
|
// src/resources/compliance.ts
|
|
566
750
|
var ComplianceResource = class {
|
|
567
751
|
constructor(request) {
|
|
568
752
|
this.request = request;
|
|
569
753
|
}
|
|
754
|
+
request;
|
|
570
755
|
/**
|
|
571
756
|
* Verify the integrity of the full audit chain.
|
|
572
757
|
*/
|
|
@@ -635,6 +820,7 @@ var DocumentsResource = class {
|
|
|
635
820
|
constructor(request) {
|
|
636
821
|
this.request = request;
|
|
637
822
|
}
|
|
823
|
+
request;
|
|
638
824
|
/**
|
|
639
825
|
* Enroll a new document (substrate fingerprint) linked to an asset.
|
|
640
826
|
*
|
|
@@ -727,6 +913,7 @@ var KeysResource = class {
|
|
|
727
913
|
constructor(request) {
|
|
728
914
|
this.request = request;
|
|
729
915
|
}
|
|
916
|
+
request;
|
|
730
917
|
async create(params) {
|
|
731
918
|
return this.request({ method: "POST", path: "/v1/keys", body: params });
|
|
732
919
|
}
|
|
@@ -737,6 +924,26 @@ var KeysResource = class {
|
|
|
737
924
|
async revoke(keyId) {
|
|
738
925
|
await this.request({ method: "DELETE", path: `/v1/keys/${encodeURIComponent(keyId)}` });
|
|
739
926
|
}
|
|
927
|
+
/**
|
|
928
|
+
* Get information about the current API key being used.
|
|
929
|
+
*/
|
|
930
|
+
async current() {
|
|
931
|
+
return this.request({ method: "GET", path: "/v1/keys/current" });
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Rotate the specified API key to a new one.
|
|
935
|
+
*
|
|
936
|
+
* @param keyId - The ID of the key to rotate
|
|
937
|
+
* @param opts - Optional rotation parameters (gracePeriodHours)
|
|
938
|
+
* @returns New key information and the new key string
|
|
939
|
+
*/
|
|
940
|
+
async rotate(keyId, opts) {
|
|
941
|
+
return this.request({
|
|
942
|
+
method: "POST",
|
|
943
|
+
path: `/v1/keys/${encodeURIComponent(keyId)}/rotate`,
|
|
944
|
+
body: opts
|
|
945
|
+
});
|
|
946
|
+
}
|
|
740
947
|
};
|
|
741
948
|
|
|
742
949
|
// src/resources/keysets.ts
|
|
@@ -744,6 +951,7 @@ var KeysetsResource = class {
|
|
|
744
951
|
constructor(request) {
|
|
745
952
|
this.request = request;
|
|
746
953
|
}
|
|
954
|
+
request;
|
|
747
955
|
async create(params) {
|
|
748
956
|
return this.request({ method: "POST", path: "/v1/keysets", body: params });
|
|
749
957
|
}
|
|
@@ -758,11 +966,539 @@ var KeysetsResource = class {
|
|
|
758
966
|
}
|
|
759
967
|
};
|
|
760
968
|
|
|
969
|
+
// src/m2m/consensus.ts
|
|
970
|
+
function computeDistance(a, b) {
|
|
971
|
+
if (a.length !== b.length) {
|
|
972
|
+
throw new Error("Vectors must have the same length");
|
|
973
|
+
}
|
|
974
|
+
let sum = 0;
|
|
975
|
+
for (let i = 0; i < a.length; i++) {
|
|
976
|
+
const diff = a[i] - b[i];
|
|
977
|
+
sum += diff * diff;
|
|
978
|
+
}
|
|
979
|
+
return Math.sqrt(sum);
|
|
980
|
+
}
|
|
981
|
+
function computeSimilarity(a, b) {
|
|
982
|
+
const distance = computeDistance(a, b);
|
|
983
|
+
return 1 / (1 + distance);
|
|
984
|
+
}
|
|
985
|
+
function calibrateThreshold(knownMatches) {
|
|
986
|
+
if (knownMatches.length === 0) {
|
|
987
|
+
return 0.85;
|
|
988
|
+
}
|
|
989
|
+
const similarities = knownMatches.map((pair) => ({
|
|
990
|
+
similarity: computeSimilarity(pair.a.dimensions, pair.b.dimensions),
|
|
991
|
+
isMatch: pair.isMatch
|
|
992
|
+
}));
|
|
993
|
+
similarities.sort((a, b) => a.similarity - b.similarity);
|
|
994
|
+
let bestThreshold = 0.85;
|
|
995
|
+
let bestAccuracy = 0;
|
|
996
|
+
for (const item of similarities) {
|
|
997
|
+
const threshold = item.similarity;
|
|
998
|
+
let correct = 0;
|
|
999
|
+
for (const pair of similarities) {
|
|
1000
|
+
const predicted = pair.similarity >= threshold;
|
|
1001
|
+
if (predicted === pair.isMatch) {
|
|
1002
|
+
correct++;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
const accuracy = correct / similarities.length;
|
|
1006
|
+
if (accuracy > bestAccuracy) {
|
|
1007
|
+
bestAccuracy = accuracy;
|
|
1008
|
+
bestThreshold = threshold;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
return bestThreshold;
|
|
1012
|
+
}
|
|
1013
|
+
function evaluateConsensus(descriptorA, descriptorB, config) {
|
|
1014
|
+
const cfg = {
|
|
1015
|
+
threshold: config?.threshold ?? 0.85,
|
|
1016
|
+
minDimensions: config?.minDimensions ?? 3,
|
|
1017
|
+
maxTimeDeltaMs: config?.maxTimeDeltaMs ?? 3e4,
|
|
1018
|
+
requireAllDimensions: config?.requireAllDimensions ?? true
|
|
1019
|
+
};
|
|
1020
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1021
|
+
const matchDetails = [];
|
|
1022
|
+
let trustEstablished = false;
|
|
1023
|
+
let confidence = 0;
|
|
1024
|
+
let distance = 0;
|
|
1025
|
+
const dimCountA = descriptorA.dimensions.length;
|
|
1026
|
+
const dimCountB = descriptorB.dimensions.length;
|
|
1027
|
+
const descriptorDimensions = Math.min(dimCountA, dimCountB);
|
|
1028
|
+
if (descriptorDimensions < cfg.minDimensions) {
|
|
1029
|
+
return {
|
|
1030
|
+
trustEstablished: false,
|
|
1031
|
+
confidence: 0,
|
|
1032
|
+
distance: Infinity,
|
|
1033
|
+
threshold: cfg.threshold,
|
|
1034
|
+
descriptorDimensions,
|
|
1035
|
+
matchDetails: [],
|
|
1036
|
+
auditToken: generateAuditToken({ descriptorA, descriptorB, error: "insufficient_dimensions" }),
|
|
1037
|
+
timestamp
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
if (cfg.requireAllDimensions && dimCountA !== dimCountB) {
|
|
1041
|
+
return {
|
|
1042
|
+
trustEstablished: false,
|
|
1043
|
+
confidence: 0,
|
|
1044
|
+
distance: Infinity,
|
|
1045
|
+
threshold: cfg.threshold,
|
|
1046
|
+
descriptorDimensions,
|
|
1047
|
+
matchDetails: [],
|
|
1048
|
+
auditToken: generateAuditToken({ descriptorA, descriptorB, error: "dimension_mismatch" }),
|
|
1049
|
+
timestamp
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
const timeA = new Date(descriptorA.timestamp).getTime();
|
|
1053
|
+
const timeB = new Date(descriptorB.timestamp).getTime();
|
|
1054
|
+
const timeDelta = Math.abs(timeA - timeB);
|
|
1055
|
+
if (timeDelta > cfg.maxTimeDeltaMs) {
|
|
1056
|
+
return {
|
|
1057
|
+
trustEstablished: false,
|
|
1058
|
+
confidence: 0,
|
|
1059
|
+
distance: Infinity,
|
|
1060
|
+
threshold: cfg.threshold,
|
|
1061
|
+
descriptorDimensions,
|
|
1062
|
+
matchDetails: [],
|
|
1063
|
+
auditToken: generateAuditToken({ descriptorA, descriptorB, error: "time_delta_exceeded" }),
|
|
1064
|
+
timestamp
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
for (let i = 0; i < descriptorDimensions; i++) {
|
|
1068
|
+
const delta = Math.abs(descriptorA.dimensions[i] - descriptorB.dimensions[i]);
|
|
1069
|
+
const withinTolerance = delta < cfg.threshold;
|
|
1070
|
+
matchDetails.push({
|
|
1071
|
+
dimension: i,
|
|
1072
|
+
delta,
|
|
1073
|
+
withinTolerance
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
const truncatedA = descriptorA.dimensions.slice(0, descriptorDimensions);
|
|
1077
|
+
const truncatedB = descriptorB.dimensions.slice(0, descriptorDimensions);
|
|
1078
|
+
distance = computeDistance(truncatedA, truncatedB);
|
|
1079
|
+
confidence = computeSimilarity(truncatedA, truncatedB);
|
|
1080
|
+
trustEstablished = confidence >= cfg.threshold;
|
|
1081
|
+
return {
|
|
1082
|
+
trustEstablished,
|
|
1083
|
+
confidence,
|
|
1084
|
+
distance,
|
|
1085
|
+
threshold: cfg.threshold,
|
|
1086
|
+
descriptorDimensions,
|
|
1087
|
+
matchDetails,
|
|
1088
|
+
auditToken: generateAuditToken({
|
|
1089
|
+
descriptorA,
|
|
1090
|
+
descriptorB,
|
|
1091
|
+
trustEstablished,
|
|
1092
|
+
confidence,
|
|
1093
|
+
distance
|
|
1094
|
+
}),
|
|
1095
|
+
timestamp
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
function generateAuditToken(data) {
|
|
1099
|
+
const input = JSON.stringify(data);
|
|
1100
|
+
let hash = 0;
|
|
1101
|
+
for (let i = 0; i < input.length; i++) {
|
|
1102
|
+
const char = input.charCodeAt(i);
|
|
1103
|
+
hash = (hash << 5) - hash + char;
|
|
1104
|
+
hash = hash & hash;
|
|
1105
|
+
}
|
|
1106
|
+
return Math.abs(hash).toString(16).padStart(8, "0");
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// src/m2m/p2p-protocol.ts
|
|
1110
|
+
function generateNonce() {
|
|
1111
|
+
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
1112
|
+
}
|
|
1113
|
+
function createHandshakeInit(deviceId, publicKey, assetId, supportedAlgorithms = ["ed25519"]) {
|
|
1114
|
+
const payload = {
|
|
1115
|
+
deviceId,
|
|
1116
|
+
publicKey,
|
|
1117
|
+
supportedAlgorithms,
|
|
1118
|
+
assetId
|
|
1119
|
+
};
|
|
1120
|
+
return {
|
|
1121
|
+
type: "handshake_init",
|
|
1122
|
+
version: "1.0",
|
|
1123
|
+
senderId: deviceId,
|
|
1124
|
+
payload,
|
|
1125
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1126
|
+
nonce: generateNonce()
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
function createHandshakeAccept(initMessage, deviceId, publicKey, selectedAlgorithm = "ed25519") {
|
|
1130
|
+
const initPayload = initMessage.payload;
|
|
1131
|
+
const payload = {
|
|
1132
|
+
deviceId,
|
|
1133
|
+
publicKey,
|
|
1134
|
+
selectedAlgorithm,
|
|
1135
|
+
assetId: initPayload.assetId
|
|
1136
|
+
};
|
|
1137
|
+
return {
|
|
1138
|
+
type: "handshake_accept",
|
|
1139
|
+
version: "1.0",
|
|
1140
|
+
senderId: deviceId,
|
|
1141
|
+
recipientId: initMessage.senderId,
|
|
1142
|
+
payload,
|
|
1143
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1144
|
+
nonce: generateNonce()
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
function createDescriptorExchange(deviceId, descriptor, assetId, captureId, recipientId) {
|
|
1148
|
+
const payload = {
|
|
1149
|
+
descriptor,
|
|
1150
|
+
assetId,
|
|
1151
|
+
captureId: captureId || generateNonce()
|
|
1152
|
+
};
|
|
1153
|
+
return {
|
|
1154
|
+
type: "descriptor_exchange",
|
|
1155
|
+
version: "1.0",
|
|
1156
|
+
senderId: deviceId,
|
|
1157
|
+
recipientId,
|
|
1158
|
+
payload,
|
|
1159
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1160
|
+
nonce: generateNonce()
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
function createConsensusResultMessage(deviceId, result, agreedByBoth = true, recipientId) {
|
|
1164
|
+
const payload = {
|
|
1165
|
+
consensusResult: result,
|
|
1166
|
+
agreedByBoth
|
|
1167
|
+
};
|
|
1168
|
+
return {
|
|
1169
|
+
type: "consensus_result",
|
|
1170
|
+
version: "1.0",
|
|
1171
|
+
senderId: deviceId,
|
|
1172
|
+
recipientId,
|
|
1173
|
+
payload,
|
|
1174
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1175
|
+
nonce: generateNonce()
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
function validateMessage(message) {
|
|
1179
|
+
const errors = [];
|
|
1180
|
+
if (!message.type) {
|
|
1181
|
+
errors.push({ field: "type", error: "Message type is required" });
|
|
1182
|
+
} else if (!["handshake_init", "handshake_accept", "descriptor_exchange", "consensus_result", "error"].includes(message.type)) {
|
|
1183
|
+
errors.push({ field: "type", error: "Invalid message type" });
|
|
1184
|
+
}
|
|
1185
|
+
if (message.version !== "1.0") {
|
|
1186
|
+
errors.push({ field: "version", error: "Unsupported protocol version" });
|
|
1187
|
+
}
|
|
1188
|
+
if (!message.senderId) {
|
|
1189
|
+
errors.push({ field: "senderId", error: "Sender ID is required" });
|
|
1190
|
+
}
|
|
1191
|
+
if (!message.payload) {
|
|
1192
|
+
errors.push({ field: "payload", error: "Payload is required" });
|
|
1193
|
+
}
|
|
1194
|
+
if (!message.timestamp) {
|
|
1195
|
+
errors.push({ field: "timestamp", error: "Timestamp is required" });
|
|
1196
|
+
} else {
|
|
1197
|
+
const date = new Date(message.timestamp);
|
|
1198
|
+
if (isNaN(date.getTime())) {
|
|
1199
|
+
errors.push({ field: "timestamp", error: "Invalid timestamp format" });
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (!message.nonce) {
|
|
1203
|
+
errors.push({ field: "nonce", error: "Nonce is required" });
|
|
1204
|
+
}
|
|
1205
|
+
if (message.type === "handshake_init") {
|
|
1206
|
+
const payload = message.payload;
|
|
1207
|
+
if (!payload.deviceId) errors.push({ field: "payload.deviceId", error: "Device ID is required" });
|
|
1208
|
+
if (!payload.publicKey) errors.push({ field: "payload.publicKey", error: "Public key is required" });
|
|
1209
|
+
if (!Array.isArray(payload.supportedAlgorithms)) {
|
|
1210
|
+
errors.push({ field: "payload.supportedAlgorithms", error: "Supported algorithms must be an array" });
|
|
1211
|
+
}
|
|
1212
|
+
if (!payload.assetId) errors.push({ field: "payload.assetId", error: "Asset ID is required" });
|
|
1213
|
+
} else if (message.type === "handshake_accept") {
|
|
1214
|
+
const payload = message.payload;
|
|
1215
|
+
if (!payload.deviceId) errors.push({ field: "payload.deviceId", error: "Device ID is required" });
|
|
1216
|
+
if (!payload.publicKey) errors.push({ field: "payload.publicKey", error: "Public key is required" });
|
|
1217
|
+
if (!payload.selectedAlgorithm) errors.push({ field: "payload.selectedAlgorithm", error: "Selected algorithm is required" });
|
|
1218
|
+
if (!payload.assetId) errors.push({ field: "payload.assetId", error: "Asset ID is required" });
|
|
1219
|
+
} else if (message.type === "descriptor_exchange") {
|
|
1220
|
+
const payload = message.payload;
|
|
1221
|
+
if (!payload.descriptor) errors.push({ field: "payload.descriptor", error: "Descriptor is required" });
|
|
1222
|
+
if (!payload.assetId) errors.push({ field: "payload.assetId", error: "Asset ID is required" });
|
|
1223
|
+
if (!payload.captureId) errors.push({ field: "payload.captureId", error: "Capture ID is required" });
|
|
1224
|
+
} else if (message.type === "consensus_result") {
|
|
1225
|
+
const payload = message.payload;
|
|
1226
|
+
if (!payload.consensusResult) errors.push({ field: "payload.consensusResult", error: "Consensus result is required" });
|
|
1227
|
+
} else if (message.type === "error") {
|
|
1228
|
+
const payload = message.payload;
|
|
1229
|
+
if (!payload.code) errors.push({ field: "payload.code", error: "Error code is required" });
|
|
1230
|
+
if (!payload.message) errors.push({ field: "payload.message", error: "Error message is required" });
|
|
1231
|
+
}
|
|
1232
|
+
return {
|
|
1233
|
+
valid: errors.length === 0,
|
|
1234
|
+
errors
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
function serializeMessage(message) {
|
|
1238
|
+
const json = JSON.stringify(message);
|
|
1239
|
+
try {
|
|
1240
|
+
return btoa(json);
|
|
1241
|
+
} catch {
|
|
1242
|
+
if (typeof globalThis.Buffer !== "undefined") {
|
|
1243
|
+
return globalThis.Buffer.from(json).toString("base64");
|
|
1244
|
+
}
|
|
1245
|
+
throw new Error("No base64 encoder available");
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
function deserializeMessage(data) {
|
|
1249
|
+
let json;
|
|
1250
|
+
try {
|
|
1251
|
+
try {
|
|
1252
|
+
json = atob(data);
|
|
1253
|
+
} catch {
|
|
1254
|
+
if (typeof globalThis.Buffer !== "undefined") {
|
|
1255
|
+
json = globalThis.Buffer.from(data, "base64").toString("utf-8");
|
|
1256
|
+
} else {
|
|
1257
|
+
throw new Error("Failed to decode base64 message");
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
} catch {
|
|
1261
|
+
throw new Error("Failed to decode base64 message");
|
|
1262
|
+
}
|
|
1263
|
+
try {
|
|
1264
|
+
return JSON.parse(json);
|
|
1265
|
+
} catch {
|
|
1266
|
+
throw new Error("Failed to parse message JSON");
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// src/m2m/audit-trail.ts
|
|
1271
|
+
function simpleHash(data) {
|
|
1272
|
+
let hash = 0;
|
|
1273
|
+
for (let i = 0; i < data.length; i++) {
|
|
1274
|
+
const char = data.charCodeAt(i);
|
|
1275
|
+
hash = (hash << 5) - hash + char;
|
|
1276
|
+
hash = hash & hash;
|
|
1277
|
+
}
|
|
1278
|
+
return Math.abs(hash).toString(16).padStart(16, "0");
|
|
1279
|
+
}
|
|
1280
|
+
function generateRecordId() {
|
|
1281
|
+
return `rec_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
1282
|
+
}
|
|
1283
|
+
async function computeRecordHash(record) {
|
|
1284
|
+
const data = JSON.stringify({
|
|
1285
|
+
id: record.id,
|
|
1286
|
+
type: record.type,
|
|
1287
|
+
assetId: record.assetId,
|
|
1288
|
+
deviceIds: record.deviceIds,
|
|
1289
|
+
timestamp: record.timestamp,
|
|
1290
|
+
previousHash: record.previousHash,
|
|
1291
|
+
data: record.data
|
|
1292
|
+
});
|
|
1293
|
+
if (typeof globalThis !== "undefined" && globalThis.crypto?.subtle) {
|
|
1294
|
+
try {
|
|
1295
|
+
const encoder = new TextEncoder();
|
|
1296
|
+
const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoder.encode(data));
|
|
1297
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1298
|
+
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1299
|
+
} catch {
|
|
1300
|
+
return simpleHash(data);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
return simpleHash(data);
|
|
1304
|
+
}
|
|
1305
|
+
function createAuditChain(chainId) {
|
|
1306
|
+
return {
|
|
1307
|
+
records: [],
|
|
1308
|
+
chainId: chainId || `chain_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
1309
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1310
|
+
lastHash: "0"
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
async function appendRecord(chain, type, assetId, deviceIds, data) {
|
|
1314
|
+
const record = {
|
|
1315
|
+
id: generateRecordId(),
|
|
1316
|
+
type,
|
|
1317
|
+
assetId,
|
|
1318
|
+
deviceIds,
|
|
1319
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1320
|
+
previousHash: chain.lastHash,
|
|
1321
|
+
data
|
|
1322
|
+
};
|
|
1323
|
+
const hash = await computeRecordHash(record);
|
|
1324
|
+
const auditRecord = {
|
|
1325
|
+
...record,
|
|
1326
|
+
hash
|
|
1327
|
+
};
|
|
1328
|
+
chain.records.push(auditRecord);
|
|
1329
|
+
chain.lastHash = hash;
|
|
1330
|
+
return auditRecord;
|
|
1331
|
+
}
|
|
1332
|
+
async function verifyChain(chain) {
|
|
1333
|
+
if (chain.records.length === 0) {
|
|
1334
|
+
return { valid: true };
|
|
1335
|
+
}
|
|
1336
|
+
let previousHash = "0";
|
|
1337
|
+
for (let i = 0; i < chain.records.length; i++) {
|
|
1338
|
+
const record = chain.records[i];
|
|
1339
|
+
if (record.previousHash !== previousHash) {
|
|
1340
|
+
return {
|
|
1341
|
+
valid: false,
|
|
1342
|
+
brokenAt: i,
|
|
1343
|
+
error: `Chain broken at record ${i}: previousHash mismatch`
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
const recordData = {
|
|
1347
|
+
id: record.id,
|
|
1348
|
+
type: record.type,
|
|
1349
|
+
assetId: record.assetId,
|
|
1350
|
+
deviceIds: record.deviceIds,
|
|
1351
|
+
timestamp: record.timestamp,
|
|
1352
|
+
previousHash: record.previousHash,
|
|
1353
|
+
data: record.data
|
|
1354
|
+
};
|
|
1355
|
+
const computedHash = await computeRecordHash(recordData);
|
|
1356
|
+
if (record.hash !== computedHash) {
|
|
1357
|
+
return {
|
|
1358
|
+
valid: false,
|
|
1359
|
+
brokenAt: i,
|
|
1360
|
+
error: `Chain broken at record ${i}: hash mismatch`
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
previousHash = record.hash;
|
|
1364
|
+
}
|
|
1365
|
+
if (previousHash !== chain.lastHash) {
|
|
1366
|
+
return {
|
|
1367
|
+
valid: false,
|
|
1368
|
+
error: "Final record hash does not match chain lastHash"
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
return { valid: true };
|
|
1372
|
+
}
|
|
1373
|
+
function exportChain(chain) {
|
|
1374
|
+
return JSON.stringify(chain, null, 2);
|
|
1375
|
+
}
|
|
1376
|
+
async function importChain(data) {
|
|
1377
|
+
try {
|
|
1378
|
+
const chain = JSON.parse(data);
|
|
1379
|
+
if (!chain.chainId) {
|
|
1380
|
+
throw new Error("Invalid chain: missing chainId");
|
|
1381
|
+
}
|
|
1382
|
+
if (!Array.isArray(chain.records)) {
|
|
1383
|
+
throw new Error("Invalid chain: records is not an array");
|
|
1384
|
+
}
|
|
1385
|
+
if (!chain.createdAt) {
|
|
1386
|
+
throw new Error("Invalid chain: missing createdAt");
|
|
1387
|
+
}
|
|
1388
|
+
if (!chain.lastHash) {
|
|
1389
|
+
throw new Error("Invalid chain: missing lastHash");
|
|
1390
|
+
}
|
|
1391
|
+
const verification = await verifyChain(chain);
|
|
1392
|
+
if (!verification.valid) {
|
|
1393
|
+
throw new Error(`Invalid chain: ${verification.error}`);
|
|
1394
|
+
}
|
|
1395
|
+
return chain;
|
|
1396
|
+
} catch (error) {
|
|
1397
|
+
if (error instanceof SyntaxError) {
|
|
1398
|
+
throw new Error("Failed to parse chain JSON");
|
|
1399
|
+
}
|
|
1400
|
+
throw error;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
// src/resources/m2m.ts
|
|
1405
|
+
var M2MResource = class {
|
|
1406
|
+
constructor(request) {
|
|
1407
|
+
this.request = request;
|
|
1408
|
+
}
|
|
1409
|
+
request;
|
|
1410
|
+
/**
|
|
1411
|
+
* Initiate an M2M challenge for an asset.
|
|
1412
|
+
*/
|
|
1413
|
+
async initiateChallenge(params) {
|
|
1414
|
+
return this.request({
|
|
1415
|
+
method: "POST",
|
|
1416
|
+
path: "/v1/m2m/challenge",
|
|
1417
|
+
body: {
|
|
1418
|
+
asset_id: params.assetId,
|
|
1419
|
+
algorithm: params.algorithm ?? "ed25519",
|
|
1420
|
+
ttl_seconds: params.ttlSeconds ?? 60
|
|
1421
|
+
}
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Verify an M2M challenge response.
|
|
1426
|
+
*/
|
|
1427
|
+
async verify(params) {
|
|
1428
|
+
return this.request({
|
|
1429
|
+
method: "POST",
|
|
1430
|
+
path: "/v1/m2m/verify",
|
|
1431
|
+
body: {
|
|
1432
|
+
challenge_id: params.challengeId,
|
|
1433
|
+
response: params.response,
|
|
1434
|
+
device_id: params.deviceId
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Register a verifier device.
|
|
1440
|
+
*/
|
|
1441
|
+
async registerDevice(params) {
|
|
1442
|
+
return this.request({
|
|
1443
|
+
method: "POST",
|
|
1444
|
+
path: "/v1/m2m/devices",
|
|
1445
|
+
body: params
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Get a verifier device by ID.
|
|
1450
|
+
*/
|
|
1451
|
+
async getDevice(deviceId) {
|
|
1452
|
+
return this.request({
|
|
1453
|
+
method: "GET",
|
|
1454
|
+
path: `/v1/m2m/devices/${encodeURIComponent(deviceId)}`
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* List all registered verifier devices.
|
|
1459
|
+
*/
|
|
1460
|
+
async listDevices() {
|
|
1461
|
+
const response = await this.request({
|
|
1462
|
+
method: "GET",
|
|
1463
|
+
path: "/v1/m2m/devices"
|
|
1464
|
+
});
|
|
1465
|
+
return response.data;
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Revoke a verifier device.
|
|
1469
|
+
*/
|
|
1470
|
+
async revokeDevice(deviceId) {
|
|
1471
|
+
return this.request({
|
|
1472
|
+
method: "DELETE",
|
|
1473
|
+
path: `/v1/m2m/devices/${encodeURIComponent(deviceId)}`
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Evaluate consensus between two physical descriptors (local operation, no API call)
|
|
1478
|
+
*/
|
|
1479
|
+
consensus(descriptorA, descriptorB, config) {
|
|
1480
|
+
return evaluateConsensus(descriptorA, descriptorB, config);
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Create a new local audit trail (no API call)
|
|
1484
|
+
*/
|
|
1485
|
+
createAuditTrail(chainId) {
|
|
1486
|
+
return createAuditChain(chainId);
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Verify audit trail integrity (local operation, no API call)
|
|
1490
|
+
*/
|
|
1491
|
+
async verifyAuditTrail(chain) {
|
|
1492
|
+
return verifyChain(chain);
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
|
|
761
1496
|
// src/resources/provenance.ts
|
|
762
1497
|
var ProvenanceResource = class {
|
|
763
1498
|
constructor(request) {
|
|
764
1499
|
this.request = request;
|
|
765
1500
|
}
|
|
1501
|
+
request;
|
|
766
1502
|
/**
|
|
767
1503
|
* Record a new provenance event in the chain.
|
|
768
1504
|
*
|
|
@@ -868,6 +1604,7 @@ var SchemasResource = class {
|
|
|
868
1604
|
constructor(request) {
|
|
869
1605
|
this.request = request;
|
|
870
1606
|
}
|
|
1607
|
+
request;
|
|
871
1608
|
/**
|
|
872
1609
|
* Register or update a vertical config schema.
|
|
873
1610
|
* If a schema already exists for the verticalId, it will be updated.
|
|
@@ -970,10 +1707,59 @@ var SchemasResource = class {
|
|
|
970
1707
|
}
|
|
971
1708
|
};
|
|
972
1709
|
|
|
1710
|
+
// src/resources/tenants.ts
|
|
1711
|
+
var TenantsResource = class {
|
|
1712
|
+
constructor(request) {
|
|
1713
|
+
this.request = request;
|
|
1714
|
+
}
|
|
1715
|
+
request;
|
|
1716
|
+
/**
|
|
1717
|
+
* Get information about the current tenant.
|
|
1718
|
+
*/
|
|
1719
|
+
async getCurrent() {
|
|
1720
|
+
return this.request({ method: "GET", path: "/v1/tenant" });
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Get resource limits for the current tenant based on their plan.
|
|
1724
|
+
*/
|
|
1725
|
+
async getLimits() {
|
|
1726
|
+
return this.request({ method: "GET", path: "/v1/tenant/limits" });
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Get usage metrics for a specified period.
|
|
1730
|
+
*/
|
|
1731
|
+
async getUsage(params) {
|
|
1732
|
+
const query = params ? this.buildQuery(params) : "";
|
|
1733
|
+
return this.request({ method: "GET", path: `/v1/tenant/usage${query}` });
|
|
1734
|
+
}
|
|
1735
|
+
/**
|
|
1736
|
+
* Get usage metrics broken down by endpoint, key, or resource.
|
|
1737
|
+
*/
|
|
1738
|
+
async getUsageBreakdown(params) {
|
|
1739
|
+
const query = params ? this.buildQuery(params) : "";
|
|
1740
|
+
const result = await this.request({
|
|
1741
|
+
method: "GET",
|
|
1742
|
+
path: `/v1/tenant/usage/breakdown${query}`
|
|
1743
|
+
});
|
|
1744
|
+
return result.data;
|
|
1745
|
+
}
|
|
1746
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1747
|
+
// PRIVATE HELPERS
|
|
1748
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1749
|
+
buildQuery(params) {
|
|
1750
|
+
const entries = Object.entries(params).filter(([, v]) => v !== void 0);
|
|
1751
|
+
if (entries.length === 0) return "";
|
|
1752
|
+
const qs = new URLSearchParams(
|
|
1753
|
+
entries.map(([k, v]) => [k, String(v)])
|
|
1754
|
+
);
|
|
1755
|
+
return `?${qs.toString()}`;
|
|
1756
|
+
}
|
|
1757
|
+
};
|
|
1758
|
+
|
|
973
1759
|
// src/client.ts
|
|
974
1760
|
var DEFAULT_BASE_URL = "https://api.optropic.com";
|
|
975
1761
|
var DEFAULT_TIMEOUT = 3e4;
|
|
976
|
-
var SDK_VERSION = "2.
|
|
1762
|
+
var SDK_VERSION = "2.5.0";
|
|
977
1763
|
var SANDBOX_PREFIXES = ["optr_test_"];
|
|
978
1764
|
var DEFAULT_RETRY_CONFIG = {
|
|
979
1765
|
maxRetries: 3,
|
|
@@ -987,14 +1773,18 @@ var OptropicClient = class {
|
|
|
987
1773
|
retryConfig;
|
|
988
1774
|
_sandbox;
|
|
989
1775
|
_debug;
|
|
1776
|
+
_rateLimit = null;
|
|
990
1777
|
assets;
|
|
991
1778
|
audit;
|
|
1779
|
+
batches;
|
|
992
1780
|
compliance;
|
|
993
1781
|
documents;
|
|
994
1782
|
keys;
|
|
995
1783
|
keysets;
|
|
1784
|
+
m2m;
|
|
996
1785
|
provenance;
|
|
997
1786
|
schemas;
|
|
1787
|
+
tenants;
|
|
998
1788
|
constructor(config) {
|
|
999
1789
|
if (!config.apiKey || !this.isValidApiKey(config.apiKey)) {
|
|
1000
1790
|
throw new AuthenticationError(
|
|
@@ -1023,12 +1813,15 @@ var OptropicClient = class {
|
|
|
1023
1813
|
const boundRequest = this.request.bind(this);
|
|
1024
1814
|
this.assets = new AssetsResource(boundRequest, this);
|
|
1025
1815
|
this.audit = new AuditResource(boundRequest);
|
|
1816
|
+
this.batches = new BatchesResource(boundRequest);
|
|
1026
1817
|
this.compliance = new ComplianceResource(boundRequest);
|
|
1027
1818
|
this.documents = new DocumentsResource(boundRequest);
|
|
1028
1819
|
this.keys = new KeysResource(boundRequest);
|
|
1029
1820
|
this.keysets = new KeysetsResource(boundRequest);
|
|
1821
|
+
this.m2m = new M2MResource(boundRequest);
|
|
1030
1822
|
this.provenance = new ProvenanceResource(boundRequest);
|
|
1031
1823
|
this.schemas = new SchemasResource(boundRequest);
|
|
1824
|
+
this.tenants = new TenantsResource(boundRequest);
|
|
1032
1825
|
}
|
|
1033
1826
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1034
1827
|
// ENVIRONMENT DETECTION
|
|
@@ -1045,6 +1838,22 @@ var OptropicClient = class {
|
|
|
1045
1838
|
get environment() {
|
|
1046
1839
|
return this._sandbox ? "sandbox" : "live";
|
|
1047
1840
|
}
|
|
1841
|
+
/** Last known rate limit status, updated after every API call. Returns null until the first request. */
|
|
1842
|
+
get rateLimit() {
|
|
1843
|
+
return this._rateLimit;
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Get quota information for the current API key.
|
|
1847
|
+
* Based on the last known rate limit from previous API calls.
|
|
1848
|
+
*/
|
|
1849
|
+
getQuota() {
|
|
1850
|
+
return {
|
|
1851
|
+
limit: this._rateLimit?.limit ?? 0,
|
|
1852
|
+
remaining: this._rateLimit?.remaining ?? 0,
|
|
1853
|
+
reset: this._rateLimit?.reset,
|
|
1854
|
+
resetAt: this._rateLimit?.reset
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1048
1857
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1049
1858
|
// DEBUG LOGGING
|
|
1050
1859
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -1157,6 +1966,15 @@ var OptropicClient = class {
|
|
|
1157
1966
|
const durationMs = performance.now() - t0;
|
|
1158
1967
|
const serverRequestId = response.headers.get("x-request-id") ?? "";
|
|
1159
1968
|
this.logResponse(method, path, response.status, durationMs, requestId, serverRequestId, attempt);
|
|
1969
|
+
const rlLimit = response.headers.get("x-ratelimit-limit");
|
|
1970
|
+
const rlRemaining = response.headers.get("x-ratelimit-remaining");
|
|
1971
|
+
if (rlLimit !== null || rlRemaining !== null) {
|
|
1972
|
+
this._rateLimit = {
|
|
1973
|
+
limit: rlLimit ? parseInt(rlLimit, 10) : 0,
|
|
1974
|
+
remaining: rlRemaining ? parseInt(rlRemaining, 10) : 0,
|
|
1975
|
+
reset: response.headers.get("x-ratelimit-reset") ?? void 0
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1160
1978
|
if (!response.ok) {
|
|
1161
1979
|
let errorBody;
|
|
1162
1980
|
try {
|
|
@@ -1226,6 +2044,39 @@ function createClient(config) {
|
|
|
1226
2044
|
return new OptropicClient(config);
|
|
1227
2045
|
}
|
|
1228
2046
|
|
|
2047
|
+
// src/types.ts
|
|
2048
|
+
var Permission = {
|
|
2049
|
+
ASSETS_READ: "assets:read",
|
|
2050
|
+
ASSETS_WRITE: "assets:write",
|
|
2051
|
+
ASSETS_VERIFY: "assets:verify",
|
|
2052
|
+
AUDIT_READ: "audit:read",
|
|
2053
|
+
COMPLIANCE_READ: "compliance:read",
|
|
2054
|
+
KEYS_MANAGE: "keys:manage",
|
|
2055
|
+
SCHEMAS_MANAGE: "schemas:manage",
|
|
2056
|
+
DOCUMENTS_ENROLL: "documents:enroll",
|
|
2057
|
+
DOCUMENTS_VERIFY: "documents:verify",
|
|
2058
|
+
PROVENANCE_READ: "provenance:read",
|
|
2059
|
+
PROVENANCE_WRITE: "provenance:write",
|
|
2060
|
+
WEBHOOKS_MANAGE: "webhooks:manage",
|
|
2061
|
+
/** All read permissions. */
|
|
2062
|
+
ALL_READ: ["assets:read", "audit:read", "compliance:read", "provenance:read"],
|
|
2063
|
+
/** All write permissions. */
|
|
2064
|
+
ALL_WRITE: [
|
|
2065
|
+
"assets:write",
|
|
2066
|
+
"assets:verify",
|
|
2067
|
+
"documents:enroll",
|
|
2068
|
+
"documents:verify",
|
|
2069
|
+
"provenance:write",
|
|
2070
|
+
"webhooks:manage",
|
|
2071
|
+
"keys:manage",
|
|
2072
|
+
"schemas:manage"
|
|
2073
|
+
],
|
|
2074
|
+
/** All permissions combined. */
|
|
2075
|
+
all() {
|
|
2076
|
+
return [...this.ALL_READ, ...this.ALL_WRITE];
|
|
2077
|
+
}
|
|
2078
|
+
};
|
|
2079
|
+
|
|
1229
2080
|
// src/filter-verify.ts
|
|
1230
2081
|
var HEADER_SIZE = 19;
|
|
1231
2082
|
var SIGNATURE_SIZE = 64;
|
|
@@ -1422,6 +2273,594 @@ function validateDPPMetadata(metadata) {
|
|
|
1422
2273
|
}
|
|
1423
2274
|
return { valid: errors.length === 0, errors };
|
|
1424
2275
|
}
|
|
2276
|
+
function validateBatteryPassport(data) {
|
|
2277
|
+
const errors = [];
|
|
2278
|
+
const warnings = [];
|
|
2279
|
+
if (!data.manufacturerIdentification) {
|
|
2280
|
+
errors.push("manufacturerIdentification is required (Annex XIII)");
|
|
2281
|
+
}
|
|
2282
|
+
if (!data.manufacturingDate) {
|
|
2283
|
+
errors.push("manufacturingDate is required (Annex XIII)");
|
|
2284
|
+
} else if (!/^\d{4}-\d{2}-\d{2}/.test(data.manufacturingDate)) {
|
|
2285
|
+
errors.push("manufacturingDate must be in ISO 8601 format (YYYY-MM-DD)");
|
|
2286
|
+
}
|
|
2287
|
+
if (!data.manufacturingPlace) {
|
|
2288
|
+
errors.push("manufacturingPlace is required (Annex XIII)");
|
|
2289
|
+
}
|
|
2290
|
+
if (data.batteryWeight === void 0 || data.batteryWeight <= 0) {
|
|
2291
|
+
errors.push("batteryWeight must be a positive number (Annex XIII)");
|
|
2292
|
+
}
|
|
2293
|
+
if (!data.batteryStatus) {
|
|
2294
|
+
errors.push("batteryStatus is required (Annex XIII)");
|
|
2295
|
+
} else if (!["original", "repurposed", "remanufactured", "waste"].includes(data.batteryStatus)) {
|
|
2296
|
+
errors.push(
|
|
2297
|
+
"batteryStatus must be one of: original, repurposed, remanufactured, waste (Annex XIII)"
|
|
2298
|
+
);
|
|
2299
|
+
}
|
|
2300
|
+
if (data.ratedCapacityAh === void 0 || data.ratedCapacityAh <= 0) {
|
|
2301
|
+
errors.push("ratedCapacityAh must be a positive number (Annex XIII)");
|
|
2302
|
+
}
|
|
2303
|
+
if (data.voltageMinV === void 0 || data.voltageMinV < 0) {
|
|
2304
|
+
errors.push("voltageMinV must be a non-negative number (Annex XIII)");
|
|
2305
|
+
}
|
|
2306
|
+
if (data.voltageMaxV === void 0 || data.voltageMaxV < 0) {
|
|
2307
|
+
errors.push("voltageMaxV must be a non-negative number (Annex XIII)");
|
|
2308
|
+
}
|
|
2309
|
+
if (data.voltageNominalV === void 0 || data.voltageNominalV < 0) {
|
|
2310
|
+
errors.push("voltageNominalV must be a non-negative number (Annex XIII)");
|
|
2311
|
+
}
|
|
2312
|
+
if (data.voltageMinV !== void 0 && data.voltageMaxV !== void 0 && data.voltageMinV > data.voltageMaxV) {
|
|
2313
|
+
errors.push("voltageMinV must be less than or equal to voltageMaxV");
|
|
2314
|
+
}
|
|
2315
|
+
if (data.temperatureRangeMinC === void 0) {
|
|
2316
|
+
errors.push("temperatureRangeMinC is required (Annex XIII)");
|
|
2317
|
+
}
|
|
2318
|
+
if (data.temperatureRangeMaxC === void 0) {
|
|
2319
|
+
errors.push("temperatureRangeMaxC is required (Annex XIII)");
|
|
2320
|
+
}
|
|
2321
|
+
if (data.temperatureRangeMinC !== void 0 && data.temperatureRangeMaxC !== void 0 && data.temperatureRangeMinC > data.temperatureRangeMaxC) {
|
|
2322
|
+
errors.push("temperatureRangeMinC must be less than or equal to temperatureRangeMaxC");
|
|
2323
|
+
}
|
|
2324
|
+
if (data.originalPowerCapabilityW === void 0 || data.originalPowerCapabilityW < 0) {
|
|
2325
|
+
errors.push("originalPowerCapabilityW must be a non-negative number (Annex XIII)");
|
|
2326
|
+
}
|
|
2327
|
+
if (data.roundTripEfficiency === void 0 || data.roundTripEfficiency < 0 || data.roundTripEfficiency > 100) {
|
|
2328
|
+
errors.push("roundTripEfficiency must be between 0 and 100 (Annex XIII)");
|
|
2329
|
+
}
|
|
2330
|
+
if (data.internalResistanceOhm === void 0 || data.internalResistanceOhm < 0) {
|
|
2331
|
+
errors.push("internalResistanceOhm must be a non-negative number (Annex XIII)");
|
|
2332
|
+
}
|
|
2333
|
+
if (!data.cellChemistryDetail) {
|
|
2334
|
+
errors.push("cellChemistryDetail is required (Annex XIII)");
|
|
2335
|
+
}
|
|
2336
|
+
if (!data.hazardousSubstances || !Array.isArray(data.hazardousSubstances)) {
|
|
2337
|
+
errors.push("hazardousSubstances must be an array (Annex XIII)");
|
|
2338
|
+
}
|
|
2339
|
+
if (data.carbonFootprintPerKwh === void 0 || data.carbonFootprintPerKwh < 0) {
|
|
2340
|
+
errors.push("carbonFootprintPerKwh must be a non-negative number (Annex XIII)");
|
|
2341
|
+
}
|
|
2342
|
+
if (!data.carbonFootprintStudyUrl) {
|
|
2343
|
+
errors.push("carbonFootprintStudyUrl is required (Annex XIII)");
|
|
2344
|
+
} else if (!isValidUrl(data.carbonFootprintStudyUrl)) {
|
|
2345
|
+
warnings.push("carbonFootprintStudyUrl appears to be invalid");
|
|
2346
|
+
}
|
|
2347
|
+
if (!data.supplyChainDueDiligencePolicy) {
|
|
2348
|
+
errors.push("supplyChainDueDiligencePolicy is required (Annex XIII)");
|
|
2349
|
+
} else if (!isValidUrl(data.supplyChainDueDiligencePolicy)) {
|
|
2350
|
+
warnings.push("supplyChainDueDiligencePolicy appears to be invalid");
|
|
2351
|
+
}
|
|
2352
|
+
if (!data.thirdPartyVerifierId) {
|
|
2353
|
+
errors.push("thirdPartyVerifierId is required (Annex XIII)");
|
|
2354
|
+
}
|
|
2355
|
+
if (!data.dismantlingInstructions) {
|
|
2356
|
+
errors.push("dismantlingInstructions is required (Annex XIII)");
|
|
2357
|
+
} else if (!isValidUrl(data.dismantlingInstructions)) {
|
|
2358
|
+
warnings.push("dismantlingInstructions appears to be invalid");
|
|
2359
|
+
}
|
|
2360
|
+
if (!data.safetyInstructions) {
|
|
2361
|
+
errors.push("safetyInstructions is required (Annex XIII)");
|
|
2362
|
+
} else if (!isValidUrl(data.safetyInstructions)) {
|
|
2363
|
+
warnings.push("safetyInstructions appears to be invalid");
|
|
2364
|
+
}
|
|
2365
|
+
if (!data.extinguishingAgent) {
|
|
2366
|
+
errors.push("extinguishingAgent is required (Annex XIII)");
|
|
2367
|
+
}
|
|
2368
|
+
if (data.chemistry && !data.cellChemistryDetail) {
|
|
2369
|
+
warnings.push("cellChemistryDetail should provide more detail than chemistry type");
|
|
2370
|
+
}
|
|
2371
|
+
if (data.hazardousSubstances && data.hazardousSubstances.length === 0) {
|
|
2372
|
+
warnings.push("hazardousSubstances array is empty; all batteries contain some hazardous materials");
|
|
2373
|
+
}
|
|
2374
|
+
return {
|
|
2375
|
+
valid: errors.length === 0,
|
|
2376
|
+
errors,
|
|
2377
|
+
warnings
|
|
2378
|
+
};
|
|
2379
|
+
}
|
|
2380
|
+
function buildBatteryPassportQR(metadata) {
|
|
2381
|
+
if (!metadata.productId) {
|
|
2382
|
+
throw new Error("productId is required to build battery passport QR");
|
|
2383
|
+
}
|
|
2384
|
+
let serialNumber;
|
|
2385
|
+
if (metadata.sectorData && "type" in metadata.sectorData && metadata.sectorData.type === "battery") {
|
|
2386
|
+
}
|
|
2387
|
+
let uri = `https://id.gs1.org/01/${metadata.productId}`;
|
|
2388
|
+
if (serialNumber) {
|
|
2389
|
+
uri += `/21/${serialNumber}`;
|
|
2390
|
+
}
|
|
2391
|
+
return uri;
|
|
2392
|
+
}
|
|
2393
|
+
function isValidUrl(urlString) {
|
|
2394
|
+
try {
|
|
2395
|
+
new URL(urlString);
|
|
2396
|
+
return true;
|
|
2397
|
+
} catch {
|
|
2398
|
+
return false;
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
// src/gs1-resolver.ts
|
|
2403
|
+
var DEFAULT_GS1_DOMAIN = "https://id.gs1.org";
|
|
2404
|
+
var AI_LABELS = {
|
|
2405
|
+
"01": "GTIN",
|
|
2406
|
+
"21": "Serial Number",
|
|
2407
|
+
"10": "Batch/Lot Number",
|
|
2408
|
+
"11": "Production Date",
|
|
2409
|
+
"17": "Expiry Date",
|
|
2410
|
+
"3103": "Net Weight (kg)",
|
|
2411
|
+
"3922": "Price",
|
|
2412
|
+
"8200": "URL",
|
|
2413
|
+
"254": "Extension",
|
|
2414
|
+
"414": "Global Location Number"
|
|
2415
|
+
};
|
|
2416
|
+
function validateGTIN(gtin) {
|
|
2417
|
+
const cleaned = gtin.replace(/\D/g, "");
|
|
2418
|
+
if (![8, 12, 13, 14].includes(cleaned.length)) {
|
|
2419
|
+
return {
|
|
2420
|
+
valid: false,
|
|
2421
|
+
checkDigit: -1,
|
|
2422
|
+
message: `Invalid GTIN length: ${cleaned.length}. Must be 8, 12, 13, or 14 digits.`
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
const digits = cleaned.split("").map(Number);
|
|
2426
|
+
const checkDigitProvided = digits[digits.length - 1];
|
|
2427
|
+
const contentDigits = digits.slice(0, -1);
|
|
2428
|
+
let sum = 0;
|
|
2429
|
+
let multiplier = 3;
|
|
2430
|
+
for (let i = contentDigits.length - 1; i >= 0; i--) {
|
|
2431
|
+
sum += contentDigits[i] * multiplier;
|
|
2432
|
+
multiplier = multiplier === 3 ? 1 : 3;
|
|
2433
|
+
}
|
|
2434
|
+
const checkDigitCalculated = (10 - sum % 10) % 10;
|
|
2435
|
+
const valid = checkDigitCalculated === checkDigitProvided;
|
|
2436
|
+
return {
|
|
2437
|
+
valid,
|
|
2438
|
+
checkDigit: checkDigitCalculated,
|
|
2439
|
+
message: valid ? `Valid GTIN check digit: ${checkDigitCalculated}` : `Invalid check digit. Expected ${checkDigitCalculated}, got ${checkDigitProvided}`
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
function parseGS1DigitalLink(uri) {
|
|
2443
|
+
const url = new URL(uri);
|
|
2444
|
+
const domain = `${url.protocol}//${url.hostname}`;
|
|
2445
|
+
const pathname = url.pathname;
|
|
2446
|
+
const allComponents = [];
|
|
2447
|
+
let gtin;
|
|
2448
|
+
let serialNumber;
|
|
2449
|
+
let batchLot;
|
|
2450
|
+
let productionDate;
|
|
2451
|
+
let expiryDate;
|
|
2452
|
+
let netWeightKg;
|
|
2453
|
+
let price;
|
|
2454
|
+
let urlValue;
|
|
2455
|
+
let gln;
|
|
2456
|
+
let extension;
|
|
2457
|
+
let queryParams;
|
|
2458
|
+
const pathParts = pathname.split("/").filter((p) => p);
|
|
2459
|
+
let i = 0;
|
|
2460
|
+
while (i < pathParts.length - 1) {
|
|
2461
|
+
const ai = pathParts[i];
|
|
2462
|
+
const value = pathParts[i + 1];
|
|
2463
|
+
const label = AI_LABELS[ai] || `AI ${ai}`;
|
|
2464
|
+
allComponents.push({ ai, value, label });
|
|
2465
|
+
switch (ai) {
|
|
2466
|
+
case "01":
|
|
2467
|
+
gtin = value;
|
|
2468
|
+
break;
|
|
2469
|
+
case "21":
|
|
2470
|
+
serialNumber = value;
|
|
2471
|
+
break;
|
|
2472
|
+
case "10":
|
|
2473
|
+
batchLot = value;
|
|
2474
|
+
break;
|
|
2475
|
+
case "11":
|
|
2476
|
+
productionDate = parseGS1Date(value);
|
|
2477
|
+
break;
|
|
2478
|
+
case "17":
|
|
2479
|
+
expiryDate = parseGS1Date(value);
|
|
2480
|
+
break;
|
|
2481
|
+
case "3103":
|
|
2482
|
+
netWeightKg = parseFloat(value);
|
|
2483
|
+
break;
|
|
2484
|
+
case "3922":
|
|
2485
|
+
price = value;
|
|
2486
|
+
break;
|
|
2487
|
+
case "8200":
|
|
2488
|
+
urlValue = value;
|
|
2489
|
+
break;
|
|
2490
|
+
case "414":
|
|
2491
|
+
gln = value;
|
|
2492
|
+
break;
|
|
2493
|
+
case "254":
|
|
2494
|
+
extension = value;
|
|
2495
|
+
break;
|
|
2496
|
+
}
|
|
2497
|
+
i += 2;
|
|
2498
|
+
}
|
|
2499
|
+
const params = {};
|
|
2500
|
+
url.searchParams.forEach((value, key) => {
|
|
2501
|
+
params[key] = value;
|
|
2502
|
+
});
|
|
2503
|
+
if (Object.keys(params).length > 0) {
|
|
2504
|
+
queryParams = params;
|
|
2505
|
+
}
|
|
2506
|
+
const result = {
|
|
2507
|
+
domain,
|
|
2508
|
+
gtin,
|
|
2509
|
+
serialNumber,
|
|
2510
|
+
batchLot,
|
|
2511
|
+
productionDate,
|
|
2512
|
+
expiryDate,
|
|
2513
|
+
netWeightKg,
|
|
2514
|
+
price,
|
|
2515
|
+
url: urlValue,
|
|
2516
|
+
gln,
|
|
2517
|
+
extension,
|
|
2518
|
+
allComponents,
|
|
2519
|
+
queryParams
|
|
2520
|
+
};
|
|
2521
|
+
return result;
|
|
2522
|
+
}
|
|
2523
|
+
function buildGS1DigitalLink(input) {
|
|
2524
|
+
const domain = input.domain || DEFAULT_GS1_DOMAIN;
|
|
2525
|
+
const parts = [];
|
|
2526
|
+
if (!input.gtin) {
|
|
2527
|
+
throw new Error("GTIN is required to build a GS1 Digital Link URI");
|
|
2528
|
+
}
|
|
2529
|
+
const validation = validateGTIN(input.gtin);
|
|
2530
|
+
if (!validation.valid) {
|
|
2531
|
+
throw new Error(`Invalid GTIN: ${validation.message}`);
|
|
2532
|
+
}
|
|
2533
|
+
parts.push("01", input.gtin);
|
|
2534
|
+
if (input.serialNumber) {
|
|
2535
|
+
parts.push("21", input.serialNumber);
|
|
2536
|
+
}
|
|
2537
|
+
if (input.batchLot) {
|
|
2538
|
+
parts.push("10", input.batchLot);
|
|
2539
|
+
}
|
|
2540
|
+
if (input.productionDate) {
|
|
2541
|
+
parts.push("11", formatGS1Date(input.productionDate));
|
|
2542
|
+
}
|
|
2543
|
+
if (input.expiryDate) {
|
|
2544
|
+
parts.push("17", formatGS1Date(input.expiryDate));
|
|
2545
|
+
}
|
|
2546
|
+
if (input.netWeightKg !== void 0) {
|
|
2547
|
+
parts.push("3103", input.netWeightKg.toString());
|
|
2548
|
+
}
|
|
2549
|
+
if (input.price) {
|
|
2550
|
+
parts.push("3922", input.price);
|
|
2551
|
+
}
|
|
2552
|
+
if (input.url) {
|
|
2553
|
+
parts.push("8200", input.url);
|
|
2554
|
+
}
|
|
2555
|
+
if (input.gln) {
|
|
2556
|
+
parts.push("414", input.gln);
|
|
2557
|
+
}
|
|
2558
|
+
if (input.extension) {
|
|
2559
|
+
parts.push("254", input.extension);
|
|
2560
|
+
}
|
|
2561
|
+
let uri = `${domain}/${parts.join("/")}`;
|
|
2562
|
+
if (input.queryParams && Object.keys(input.queryParams).length > 0) {
|
|
2563
|
+
const params = new URLSearchParams(input.queryParams);
|
|
2564
|
+
uri += `?${params.toString()}`;
|
|
2565
|
+
}
|
|
2566
|
+
return uri;
|
|
2567
|
+
}
|
|
2568
|
+
function mapToOptropicAsset(parsed) {
|
|
2569
|
+
if (!parsed.gtin) {
|
|
2570
|
+
throw new Error("GTIN is required for Optropic asset mapping");
|
|
2571
|
+
}
|
|
2572
|
+
let lookupQuery = `gtin:"${parsed.gtin}"`;
|
|
2573
|
+
if (parsed.serialNumber) {
|
|
2574
|
+
lookupQuery += ` AND serial:"${parsed.serialNumber}"`;
|
|
2575
|
+
}
|
|
2576
|
+
if (parsed.batchLot) {
|
|
2577
|
+
lookupQuery += ` AND batch:"${parsed.batchLot}"`;
|
|
2578
|
+
}
|
|
2579
|
+
const lookup = {
|
|
2580
|
+
productId: parsed.gtin,
|
|
2581
|
+
serialNumber: parsed.serialNumber,
|
|
2582
|
+
batchId: parsed.batchLot,
|
|
2583
|
+
lookupQuery
|
|
2584
|
+
};
|
|
2585
|
+
return lookup;
|
|
2586
|
+
}
|
|
2587
|
+
function generateQRCodePayload(input) {
|
|
2588
|
+
const uri = buildGS1DigitalLink(input);
|
|
2589
|
+
return {
|
|
2590
|
+
type: "gs1-digital-link",
|
|
2591
|
+
data: uri,
|
|
2592
|
+
encodingMode: "url"
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
function isValidAI(ai) {
|
|
2596
|
+
return Object.prototype.hasOwnProperty.call(AI_LABELS, ai);
|
|
2597
|
+
}
|
|
2598
|
+
function getAILabel(ai) {
|
|
2599
|
+
return AI_LABELS[ai];
|
|
2600
|
+
}
|
|
2601
|
+
function getSupportedAIs() {
|
|
2602
|
+
return Object.keys(AI_LABELS);
|
|
2603
|
+
}
|
|
2604
|
+
function parseGS1Date(gs1Date) {
|
|
2605
|
+
if (gs1Date.length !== 6) {
|
|
2606
|
+
return gs1Date;
|
|
2607
|
+
}
|
|
2608
|
+
const yy = parseInt(gs1Date.substring(0, 2), 10);
|
|
2609
|
+
const mm = gs1Date.substring(2, 4);
|
|
2610
|
+
const dd = gs1Date.substring(4, 6);
|
|
2611
|
+
const yyyy = 2e3 + yy;
|
|
2612
|
+
return `${yyyy}-${mm}-${dd}`;
|
|
2613
|
+
}
|
|
2614
|
+
function formatGS1Date(isoDate) {
|
|
2615
|
+
let cleaned = isoDate.replace(/-/g, "");
|
|
2616
|
+
if (cleaned.length !== 8) {
|
|
2617
|
+
return isoDate;
|
|
2618
|
+
}
|
|
2619
|
+
const yyyy = cleaned.substring(0, 4);
|
|
2620
|
+
const mm = cleaned.substring(4, 6);
|
|
2621
|
+
const dd = cleaned.substring(6, 8);
|
|
2622
|
+
const yy = (parseInt(yyyy, 10) % 100).toString().padStart(2, "0");
|
|
2623
|
+
return `${yy}${mm}${dd}`;
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
// src/dpp-access.ts
|
|
2627
|
+
var DPPAccessLevel = {
|
|
2628
|
+
/** Consumer-facing public data. */
|
|
2629
|
+
PUBLIC: "public",
|
|
2630
|
+
/** Verified supply chain partner. */
|
|
2631
|
+
AUTHENTICATED: "authenticated",
|
|
2632
|
+
/** Market surveillance authority. */
|
|
2633
|
+
REGULATORY: "regulatory",
|
|
2634
|
+
/** Economic operator (data owner). */
|
|
2635
|
+
OWNER: "owner",
|
|
2636
|
+
/**
|
|
2637
|
+
* Check if a level has at least the privilege of another.
|
|
2638
|
+
* Useful for authorization checks.
|
|
2639
|
+
*
|
|
2640
|
+
* @example
|
|
2641
|
+
* ```typescript
|
|
2642
|
+
* DPPAccessLevel.isAtLeast('regulatory', 'authenticated'); // true
|
|
2643
|
+
* DPPAccessLevel.isAtLeast('public', 'regulatory'); // false
|
|
2644
|
+
* ```
|
|
2645
|
+
*/
|
|
2646
|
+
isAtLeast(level, required) {
|
|
2647
|
+
return LEVEL_RANK[level] >= LEVEL_RANK[required];
|
|
2648
|
+
}
|
|
2649
|
+
};
|
|
2650
|
+
var LEVEL_RANK = {
|
|
2651
|
+
public: 0,
|
|
2652
|
+
authenticated: 1,
|
|
2653
|
+
regulatory: 2,
|
|
2654
|
+
owner: 3
|
|
2655
|
+
};
|
|
2656
|
+
var DEFAULT_FIELD_VISIBILITY = [
|
|
2657
|
+
// ── Public fields ─────────────────────────────────────────────────────
|
|
2658
|
+
{ field: "productId", minLevel: "public" },
|
|
2659
|
+
{ field: "productName", minLevel: "public" },
|
|
2660
|
+
{ field: "manufacturer", minLevel: "public" },
|
|
2661
|
+
{ field: "countryOfOrigin", minLevel: "public" },
|
|
2662
|
+
{ field: "category", minLevel: "public" },
|
|
2663
|
+
{ field: "carbonFootprint", minLevel: "public" },
|
|
2664
|
+
{ field: "recycledContent", minLevel: "public" },
|
|
2665
|
+
{ field: "durabilityYears", minLevel: "public" },
|
|
2666
|
+
{ field: "repairabilityScore", minLevel: "public" },
|
|
2667
|
+
{ field: "dppRegistryId", minLevel: "public" },
|
|
2668
|
+
// ── Authenticated fields ──────────────────────────────────────────────
|
|
2669
|
+
{
|
|
2670
|
+
field: "substancesOfConcern",
|
|
2671
|
+
minLevel: "authenticated",
|
|
2672
|
+
// Battery regulation requires public disclosure of substances
|
|
2673
|
+
categoryOverride: { battery: "public" }
|
|
2674
|
+
},
|
|
2675
|
+
{ field: "conformityDeclarations", minLevel: "authenticated" },
|
|
2676
|
+
{
|
|
2677
|
+
field: "sectorData",
|
|
2678
|
+
minLevel: "authenticated",
|
|
2679
|
+
// Battery basic data (chemistry, capacity) is public per Battery Regulation Art. 13
|
|
2680
|
+
categoryOverride: { battery: "public" }
|
|
2681
|
+
},
|
|
2682
|
+
// ── Regulatory fields ─────────────────────────────────────────────────
|
|
2683
|
+
// (These map to DPPMetadata extensions that may be added; included for future-proofing)
|
|
2684
|
+
{ field: "internalBatchId", minLevel: "regulatory" },
|
|
2685
|
+
{ field: "auditTrailRef", minLevel: "regulatory" },
|
|
2686
|
+
{ field: "manufacturingDate", minLevel: "regulatory" },
|
|
2687
|
+
{ field: "facilityId", minLevel: "regulatory" },
|
|
2688
|
+
// ── Owner-only fields ─────────────────────────────────────────────────
|
|
2689
|
+
{ field: "costData", minLevel: "owner" },
|
|
2690
|
+
{ field: "supplierContracts", minLevel: "owner" },
|
|
2691
|
+
{ field: "internalNotes", minLevel: "owner" }
|
|
2692
|
+
];
|
|
2693
|
+
function getDPPAccessPolicy(overrides) {
|
|
2694
|
+
const fields = overrides ?? DEFAULT_FIELD_VISIBILITY;
|
|
2695
|
+
return {
|
|
2696
|
+
defaultLevel: "public",
|
|
2697
|
+
fields,
|
|
2698
|
+
redactionMode: "omit"
|
|
2699
|
+
};
|
|
2700
|
+
}
|
|
2701
|
+
function filterDPPByAccess(metadata, context, policy) {
|
|
2702
|
+
const effectivePolicy = policy ?? getDPPAccessPolicy();
|
|
2703
|
+
const result = {};
|
|
2704
|
+
const showRedacted = context.showRedacted ?? effectivePolicy.redactionMode === "placeholder";
|
|
2705
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
2706
|
+
const rule = effectivePolicy.fields.find((f) => f.field === key);
|
|
2707
|
+
if (!rule) {
|
|
2708
|
+
if (DPPAccessLevel.isAtLeast(context.level, "owner")) {
|
|
2709
|
+
result[key] = value;
|
|
2710
|
+
} else if (showRedacted) {
|
|
2711
|
+
result[key] = "[REDACTED]";
|
|
2712
|
+
}
|
|
2713
|
+
continue;
|
|
2714
|
+
}
|
|
2715
|
+
let effectiveMinLevel = rule.minLevel;
|
|
2716
|
+
if (context.category && rule.categoryOverride?.[context.category]) {
|
|
2717
|
+
effectiveMinLevel = rule.categoryOverride[context.category];
|
|
2718
|
+
}
|
|
2719
|
+
if (DPPAccessLevel.isAtLeast(context.level, effectiveMinLevel)) {
|
|
2720
|
+
result[key] = value;
|
|
2721
|
+
} else if (showRedacted) {
|
|
2722
|
+
result[key] = "[REDACTED]";
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
return result;
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
// src/epcis.ts
|
|
2729
|
+
var MAPPING_TABLE = {
|
|
2730
|
+
manufactured: {
|
|
2731
|
+
eventType: "TransformationEvent",
|
|
2732
|
+
bizStep: "urn:epcglobal:cbv:bizstep:commissioning",
|
|
2733
|
+
disposition: "urn:epcglobal:cbv:disp:active",
|
|
2734
|
+
exact: true,
|
|
2735
|
+
note: "Raw materials transformed into finished product; new EPC commissioned."
|
|
2736
|
+
},
|
|
2737
|
+
labeled: {
|
|
2738
|
+
eventType: "ObjectEvent",
|
|
2739
|
+
bizStep: "urn:epcglobal:cbv:bizstep:encoding",
|
|
2740
|
+
disposition: "urn:epcglobal:cbv:disp:encoded",
|
|
2741
|
+
exact: true,
|
|
2742
|
+
note: "Physical label or tag applied/encoded on the product."
|
|
2743
|
+
},
|
|
2744
|
+
enrolled: {
|
|
2745
|
+
eventType: "ObjectEvent",
|
|
2746
|
+
bizStep: "urn:epcglobal:cbv:bizstep:commissioning",
|
|
2747
|
+
disposition: "urn:epcglobal:cbv:disp:active",
|
|
2748
|
+
exact: false,
|
|
2749
|
+
note: "Optropic-specific: substrate fingerprint captured. Mapped to commissioning as closest EPCIS equivalent."
|
|
2750
|
+
},
|
|
2751
|
+
shipped: {
|
|
2752
|
+
eventType: "ObjectEvent",
|
|
2753
|
+
bizStep: "urn:epcglobal:cbv:bizstep:shipping",
|
|
2754
|
+
disposition: "urn:epcglobal:cbv:disp:in_transit",
|
|
2755
|
+
exact: true,
|
|
2756
|
+
note: "Product shipped from origin; enters transit."
|
|
2757
|
+
},
|
|
2758
|
+
received: {
|
|
2759
|
+
eventType: "ObjectEvent",
|
|
2760
|
+
bizStep: "urn:epcglobal:cbv:bizstep:receiving",
|
|
2761
|
+
disposition: "urn:epcglobal:cbv:disp:active",
|
|
2762
|
+
exact: true,
|
|
2763
|
+
note: "Product received at destination."
|
|
2764
|
+
},
|
|
2765
|
+
transferred: {
|
|
2766
|
+
eventType: "TransactionEvent",
|
|
2767
|
+
bizStep: "urn:epcglobal:cbv:bizstep:holding",
|
|
2768
|
+
disposition: "urn:epcglobal:cbv:disp:active",
|
|
2769
|
+
exact: false,
|
|
2770
|
+
note: "Ownership transfer. TransactionEvent captures business transaction linkage; disposition remains active."
|
|
2771
|
+
},
|
|
2772
|
+
verified: {
|
|
2773
|
+
eventType: "ObjectEvent",
|
|
2774
|
+
bizStep: "urn:epcglobal:cbv:bizstep:inspecting",
|
|
2775
|
+
disposition: "urn:epcglobal:cbv:disp:active",
|
|
2776
|
+
exact: true,
|
|
2777
|
+
note: "Product authenticity or integrity verified."
|
|
2778
|
+
},
|
|
2779
|
+
recalled: {
|
|
2780
|
+
eventType: "ObjectEvent",
|
|
2781
|
+
bizStep: "urn:epcglobal:cbv:bizstep:holding",
|
|
2782
|
+
disposition: "urn:epcglobal:cbv:disp:recalled",
|
|
2783
|
+
exact: true,
|
|
2784
|
+
note: "Product recalled; removed from distribution."
|
|
2785
|
+
},
|
|
2786
|
+
destroyed: {
|
|
2787
|
+
eventType: "ObjectEvent",
|
|
2788
|
+
bizStep: "urn:epcglobal:cbv:bizstep:decommissioning",
|
|
2789
|
+
disposition: "urn:epcglobal:cbv:disp:destroyed",
|
|
2790
|
+
exact: true,
|
|
2791
|
+
note: "Product destroyed; EPC decommissioned."
|
|
2792
|
+
},
|
|
2793
|
+
custom: {
|
|
2794
|
+
eventType: "ObjectEvent",
|
|
2795
|
+
bizStep: "urn:epcglobal:cbv:bizstep:holding",
|
|
2796
|
+
disposition: "urn:epcglobal:cbv:disp:active",
|
|
2797
|
+
exact: false,
|
|
2798
|
+
note: "Custom event with no direct EPCIS equivalent; mapped to generic observation."
|
|
2799
|
+
}
|
|
2800
|
+
};
|
|
2801
|
+
function mapToEPCIS(eventType) {
|
|
2802
|
+
return MAPPING_TABLE[eventType] ?? MAPPING_TABLE.custom;
|
|
2803
|
+
}
|
|
2804
|
+
function getEPCISMappingTable() {
|
|
2805
|
+
return MAPPING_TABLE;
|
|
2806
|
+
}
|
|
2807
|
+
function toEPCISEvent(event, options) {
|
|
2808
|
+
const mapping = mapToEPCIS(event.eventType);
|
|
2809
|
+
const timezoneOffset = options?.timezoneOffset ?? "+00:00";
|
|
2810
|
+
const epcisEvent = {
|
|
2811
|
+
type: mapping.eventType,
|
|
2812
|
+
eventTime: event.timestamp,
|
|
2813
|
+
eventTimeZoneOffset: timezoneOffset,
|
|
2814
|
+
bizStep: mapping.bizStep,
|
|
2815
|
+
disposition: mapping.disposition,
|
|
2816
|
+
epcList: [assetIdToEPC(event.assetId, options?.gs1CompanyPrefix)],
|
|
2817
|
+
...event.location?.facility && {
|
|
2818
|
+
readPoint: {
|
|
2819
|
+
id: buildLocationUri(event.location, "readPoint", options?.gs1CompanyPrefix)
|
|
2820
|
+
}
|
|
2821
|
+
},
|
|
2822
|
+
...event.location?.facility && {
|
|
2823
|
+
bizLocation: {
|
|
2824
|
+
id: buildLocationUri(event.location, "bizLocation", options?.gs1CompanyPrefix)
|
|
2825
|
+
}
|
|
2826
|
+
},
|
|
2827
|
+
"optropic:provenanceEventId": event.id,
|
|
2828
|
+
"optropic:chainSequence": event.chainSequence,
|
|
2829
|
+
"optropic:eventHash": event.eventHash
|
|
2830
|
+
};
|
|
2831
|
+
return epcisEvent;
|
|
2832
|
+
}
|
|
2833
|
+
function buildEPCISDocument(events) {
|
|
2834
|
+
return {
|
|
2835
|
+
"@context": [
|
|
2836
|
+
"https://ref.gs1.org/standards/epcis/2.0.0/epcis-context.jsonld",
|
|
2837
|
+
{ optropic: "https://api.optropic.com/ns/epcis/" }
|
|
2838
|
+
],
|
|
2839
|
+
type: "EPCISDocument",
|
|
2840
|
+
schemaVersion: "2.0",
|
|
2841
|
+
creationDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2842
|
+
epcisBody: {
|
|
2843
|
+
eventList: events
|
|
2844
|
+
}
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
function provenanceChainToEPCIS(events, options) {
|
|
2848
|
+
const epcisEvents = events.map((e) => toEPCISEvent(e, options));
|
|
2849
|
+
return buildEPCISDocument(epcisEvents);
|
|
2850
|
+
}
|
|
2851
|
+
function assetIdToEPC(assetId, gs1CompanyPrefix) {
|
|
2852
|
+
if (gs1CompanyPrefix && /^\d{8,14}$/.test(assetId)) {
|
|
2853
|
+
return `urn:epc:id:sgtin:${gs1CompanyPrefix}.${assetId}`;
|
|
2854
|
+
}
|
|
2855
|
+
return `urn:optropic:asset:${assetId}`;
|
|
2856
|
+
}
|
|
2857
|
+
function buildLocationUri(location, _type, gs1CompanyPrefix) {
|
|
2858
|
+
if (gs1CompanyPrefix && location.facility) {
|
|
2859
|
+
return `urn:epc:id:sgln:${gs1CompanyPrefix}.${encodeURIComponent(location.facility)}`;
|
|
2860
|
+
}
|
|
2861
|
+
const parts = [location.country, location.region, location.facility].filter(Boolean);
|
|
2862
|
+
return `urn:optropic:location:${parts.join(":")}`;
|
|
2863
|
+
}
|
|
1425
2864
|
|
|
1426
2865
|
// src/webhooks.ts
|
|
1427
2866
|
async function computeHmacSha256(secret, message) {
|
|
@@ -1468,25 +2907,615 @@ async function verifyWebhookSignature(options) {
|
|
|
1468
2907
|
return { valid: true };
|
|
1469
2908
|
}
|
|
1470
2909
|
|
|
2910
|
+
// src/stanag/uid.ts
|
|
2911
|
+
function encodeNATOUID(data) {
|
|
2912
|
+
if (!isValidCAGE(data.cage_ncage)) {
|
|
2913
|
+
throw new Error(`Invalid CAGE/NCAGE code: ${data.cage_ncage}`);
|
|
2914
|
+
}
|
|
2915
|
+
const parts = [
|
|
2916
|
+
`25S${data.cage_ncage}`,
|
|
2917
|
+
data.partNumber,
|
|
2918
|
+
data.serialNumber
|
|
2919
|
+
];
|
|
2920
|
+
if (data.batchLot) {
|
|
2921
|
+
parts.push(`LOT:${data.batchLot}`);
|
|
2922
|
+
}
|
|
2923
|
+
if (data.enterpriseId) {
|
|
2924
|
+
parts.push(`ENT:${data.enterpriseId}`);
|
|
2925
|
+
}
|
|
2926
|
+
if (data.encoding && data.encoding !== "IUID") {
|
|
2927
|
+
parts.push(`ENC:${data.encoding}`);
|
|
2928
|
+
}
|
|
2929
|
+
return parts.join("|");
|
|
2930
|
+
}
|
|
2931
|
+
function decodeNATOUID(encoded) {
|
|
2932
|
+
const parts = encoded.split("|");
|
|
2933
|
+
if (!parts[0]?.startsWith("25S")) {
|
|
2934
|
+
throw new Error('Invalid NATO UID format: must start with DI "25S"');
|
|
2935
|
+
}
|
|
2936
|
+
const firstPart = parts[0].substring(3);
|
|
2937
|
+
const cage_ncage = firstPart.substring(0, 5);
|
|
2938
|
+
if (parts.length < 3) {
|
|
2939
|
+
throw new Error("Invalid NATO UID: insufficient components");
|
|
2940
|
+
}
|
|
2941
|
+
const partNumber = parts[1];
|
|
2942
|
+
const serialNumber = parts[2];
|
|
2943
|
+
let batchLot;
|
|
2944
|
+
let enterpriseId;
|
|
2945
|
+
let encoding = "IUID";
|
|
2946
|
+
for (let i = 3; i < parts.length; i++) {
|
|
2947
|
+
const part = parts[i];
|
|
2948
|
+
if (part?.startsWith("LOT:")) {
|
|
2949
|
+
batchLot = part.substring(4);
|
|
2950
|
+
} else if (part?.startsWith("ENT:")) {
|
|
2951
|
+
enterpriseId = part.substring(4);
|
|
2952
|
+
} else if (part?.startsWith("ENC:")) {
|
|
2953
|
+
encoding = part.substring(4);
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
if (!isValidCAGE(cage_ncage)) {
|
|
2957
|
+
throw new Error(`Invalid CAGE/NCAGE in decoded UID: ${cage_ncage}`);
|
|
2958
|
+
}
|
|
2959
|
+
return {
|
|
2960
|
+
cage_ncage,
|
|
2961
|
+
partNumber,
|
|
2962
|
+
serialNumber,
|
|
2963
|
+
batchLot,
|
|
2964
|
+
enterpriseId,
|
|
2965
|
+
encoding
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2968
|
+
function validateNATOUID(uid) {
|
|
2969
|
+
const errors = [];
|
|
2970
|
+
if (!uid) {
|
|
2971
|
+
return { valid: false, errors: ["UID cannot be empty"] };
|
|
2972
|
+
}
|
|
2973
|
+
if (!uid.startsWith("25S")) {
|
|
2974
|
+
errors.push('UID must start with DI "25S"');
|
|
2975
|
+
}
|
|
2976
|
+
const parts = uid.split("|");
|
|
2977
|
+
if (parts.length < 3) {
|
|
2978
|
+
errors.push("UID must contain at least CAGE, part number, and serial");
|
|
2979
|
+
}
|
|
2980
|
+
if (parts[0]) {
|
|
2981
|
+
const cage = parts[0].substring(3);
|
|
2982
|
+
if (!isValidCAGE(cage)) {
|
|
2983
|
+
errors.push(`Invalid CAGE/NCAGE code: ${cage}`);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
return {
|
|
2987
|
+
valid: errors.length === 0,
|
|
2988
|
+
errors
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
function isValidCAGE(code) {
|
|
2992
|
+
return /^[A-Z0-9]{5}$/.test(code);
|
|
2993
|
+
}
|
|
2994
|
+
function mapNATOUIDToOptropic(uid) {
|
|
2995
|
+
const assetId = encodeNATOUID(uid);
|
|
2996
|
+
return {
|
|
2997
|
+
assetId,
|
|
2998
|
+
metadata: {
|
|
2999
|
+
type: "nato_uid",
|
|
3000
|
+
cage_ncage: uid.cage_ncage,
|
|
3001
|
+
partNumber: uid.partNumber,
|
|
3002
|
+
serialNumber: uid.serialNumber,
|
|
3003
|
+
batchLot: uid.batchLot,
|
|
3004
|
+
enterpriseId: uid.enterpriseId,
|
|
3005
|
+
encoding: uid.encoding,
|
|
3006
|
+
standard: "STANAG_2290"
|
|
3007
|
+
}
|
|
3008
|
+
};
|
|
3009
|
+
}
|
|
3010
|
+
function mapOptropicToNATOUID(_assetId, metadata) {
|
|
3011
|
+
const cage_ncage = metadata.cage_ncage;
|
|
3012
|
+
const partNumber = metadata.partNumber;
|
|
3013
|
+
const serialNumber = metadata.serialNumber;
|
|
3014
|
+
const encoding = metadata.encoding || "IUID";
|
|
3015
|
+
if (!cage_ncage || !partNumber || !serialNumber) {
|
|
3016
|
+
throw new Error("Missing required NATO UID fields in metadata");
|
|
3017
|
+
}
|
|
3018
|
+
return {
|
|
3019
|
+
cage_ncage,
|
|
3020
|
+
partNumber,
|
|
3021
|
+
serialNumber,
|
|
3022
|
+
batchLot: metadata.batchLot,
|
|
3023
|
+
enterpriseId: metadata.enterpriseId,
|
|
3024
|
+
encoding
|
|
3025
|
+
};
|
|
3026
|
+
}
|
|
3027
|
+
function buildDefenceMetadata(classification, caveats, shelfLife) {
|
|
3028
|
+
return {
|
|
3029
|
+
classification,
|
|
3030
|
+
handlingCaveats: caveats || [],
|
|
3031
|
+
releasableTo: getNATOReleasability(classification),
|
|
3032
|
+
shelfLife
|
|
3033
|
+
};
|
|
3034
|
+
}
|
|
3035
|
+
function getNATOReleasability(classification) {
|
|
3036
|
+
switch (classification) {
|
|
3037
|
+
case "UNCLASSIFIED":
|
|
3038
|
+
return ["NATO", "PUBLIC"];
|
|
3039
|
+
case "RESTRICTED":
|
|
3040
|
+
return ["NATO", "EU"];
|
|
3041
|
+
case "CONFIDENTIAL":
|
|
3042
|
+
return ["NATO"];
|
|
3043
|
+
case "SECRET":
|
|
3044
|
+
return ["NATO"];
|
|
3045
|
+
case "NATO_SECRET":
|
|
3046
|
+
return ["NATO"];
|
|
3047
|
+
case "COSMIC_TOP_SECRET":
|
|
3048
|
+
return ["NATO"];
|
|
3049
|
+
default:
|
|
3050
|
+
return [];
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
function validateDefenceMetadata(metadata) {
|
|
3054
|
+
const errors = [];
|
|
3055
|
+
if (!metadata.classification) {
|
|
3056
|
+
errors.push("Classification level is required");
|
|
3057
|
+
}
|
|
3058
|
+
if (!Array.isArray(metadata.handlingCaveats)) {
|
|
3059
|
+
errors.push("Handling caveats must be an array");
|
|
3060
|
+
}
|
|
3061
|
+
if (!Array.isArray(metadata.releasableTo)) {
|
|
3062
|
+
errors.push("Releasable to must be an array");
|
|
3063
|
+
}
|
|
3064
|
+
if (metadata.shelfLife) {
|
|
3065
|
+
const mfg = new Date(metadata.shelfLife.manufacturedDate);
|
|
3066
|
+
const exp = new Date(metadata.shelfLife.expiryDate);
|
|
3067
|
+
if (mfg >= exp) {
|
|
3068
|
+
errors.push("Expiry date must be after manufactured date");
|
|
3069
|
+
}
|
|
3070
|
+
if (!["TYPE_I", "TYPE_II", "TYPE_III"].includes(metadata.shelfLife.type)) {
|
|
3071
|
+
errors.push("Invalid shelf life type");
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
if (metadata.nsn && !/^\d{13}$/.test(metadata.nsn)) {
|
|
3075
|
+
errors.push("NSN must be 13 digits");
|
|
3076
|
+
}
|
|
3077
|
+
return {
|
|
3078
|
+
valid: errors.length === 0,
|
|
3079
|
+
errors
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
// src/stanag/logistics.ts
|
|
3084
|
+
function mapMovementToProvenance(movement) {
|
|
3085
|
+
const typeMap = {
|
|
3086
|
+
manufacture: "MANUFACTURE",
|
|
3087
|
+
receipt: "RECEIPT",
|
|
3088
|
+
inspection: "INSPECTION",
|
|
3089
|
+
storage: "STORAGE",
|
|
3090
|
+
issue: "ISSUED",
|
|
3091
|
+
transport: "TRANSPORT",
|
|
3092
|
+
delivery: "DELIVERY",
|
|
3093
|
+
maintenance: "MAINTENANCE",
|
|
3094
|
+
repair: "REPAIR",
|
|
3095
|
+
disposal: "DISPOSAL",
|
|
3096
|
+
destruction: "DESTRUCTION",
|
|
3097
|
+
transfer: "TRANSFER",
|
|
3098
|
+
inventory: "INVENTORY"
|
|
3099
|
+
};
|
|
3100
|
+
return {
|
|
3101
|
+
eventType: typeMap[movement.movementType] ?? "UNKNOWN",
|
|
3102
|
+
timestamp: movement.timestamp,
|
|
3103
|
+
actor: movement.custodian,
|
|
3104
|
+
location: {
|
|
3105
|
+
facility: movement.location.facility,
|
|
3106
|
+
coordinates: movement.location.coordinates ? {
|
|
3107
|
+
lat: movement.location.coordinates.lat,
|
|
3108
|
+
lng: movement.location.coordinates.lon
|
|
3109
|
+
} : void 0
|
|
3110
|
+
},
|
|
3111
|
+
metadata: {
|
|
3112
|
+
movementId: movement.movementId,
|
|
3113
|
+
itemUID: movement.itemUID,
|
|
3114
|
+
supplyClass: movement.supplyClass,
|
|
3115
|
+
priority: movement.priority,
|
|
3116
|
+
condition: movement.condition,
|
|
3117
|
+
authorizedBy: movement.authorizedBy,
|
|
3118
|
+
transportMethod: movement.transportMethod,
|
|
3119
|
+
receivingParty: movement.receivingParty,
|
|
3120
|
+
remarks: movement.remarks
|
|
3121
|
+
}
|
|
3122
|
+
};
|
|
3123
|
+
}
|
|
3124
|
+
function calculateReadiness(movements, thresholds = {}) {
|
|
3125
|
+
if (movements.length === 0) {
|
|
3126
|
+
return {
|
|
3127
|
+
overall: 0,
|
|
3128
|
+
serviceablePercent: 0,
|
|
3129
|
+
maintenanceCompliance: 0,
|
|
3130
|
+
daysSinceInspection: 0,
|
|
3131
|
+
transportEfficiency: 0,
|
|
3132
|
+
recommendations: ["No movement data available for readiness assessment"],
|
|
3133
|
+
assessmentAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3134
|
+
};
|
|
3135
|
+
}
|
|
3136
|
+
const maxInventoryAge = thresholds.maxInventoryAgeDays ?? 365;
|
|
3137
|
+
void thresholds.maxUnserviceablePercent;
|
|
3138
|
+
const requiredMaintenance = thresholds.requiredMaintenancePercent ?? 95;
|
|
3139
|
+
const conditions = movements.map((m) => m.condition);
|
|
3140
|
+
const serviceableCount = conditions.filter((c) => c === "serviceable").length;
|
|
3141
|
+
const serviceablePercent = serviceableCount / conditions.length * 100;
|
|
3142
|
+
const maintenances = movements.filter((m) => m.movementType === "maintenance" || m.movementType === "repair");
|
|
3143
|
+
const maintenancePercent = maintenances.length / Math.max(movements.length, 1) * 100;
|
|
3144
|
+
const inspections = movements.filter((m) => m.movementType === "inspection");
|
|
3145
|
+
const now = /* @__PURE__ */ new Date();
|
|
3146
|
+
let daysSinceInspection = 0;
|
|
3147
|
+
if (inspections.length > 0) {
|
|
3148
|
+
const lastInspection = new Date(inspections[inspections.length - 1].timestamp);
|
|
3149
|
+
daysSinceInspection = Math.floor((now.getTime() - lastInspection.getTime()) / (1e3 * 60 * 60 * 24));
|
|
3150
|
+
} else {
|
|
3151
|
+
daysSinceInspection = maxInventoryAge;
|
|
3152
|
+
}
|
|
3153
|
+
const transports = movements.filter((m) => m.movementType === "transport");
|
|
3154
|
+
let transportEfficiency = 100;
|
|
3155
|
+
if (transports.length > 0) {
|
|
3156
|
+
const avgDelay = transports.reduce((sum, m) => {
|
|
3157
|
+
const timestamp = new Date(m.timestamp);
|
|
3158
|
+
const delay = Math.floor((now.getTime() - timestamp.getTime()) / (1e3 * 60 * 60));
|
|
3159
|
+
return sum + delay;
|
|
3160
|
+
}, 0) / transports.length;
|
|
3161
|
+
transportEfficiency = Math.max(0, 100 - avgDelay / (thresholds.maxTransportHours ?? 48) * 100);
|
|
3162
|
+
}
|
|
3163
|
+
const weightedScore = serviceablePercent * 0.4 + Math.min(maintenancePercent, requiredMaintenance) * 0.3 + (100 - Math.min(daysSinceInspection, maxInventoryAge) / maxInventoryAge * 100) * 0.2 + transportEfficiency * 0.1;
|
|
3164
|
+
const overall = Math.round(Math.max(0, Math.min(100, weightedScore)));
|
|
3165
|
+
const recommendations = [];
|
|
3166
|
+
if (serviceablePercent < 85) {
|
|
3167
|
+
recommendations.push(`Low serviceable percentage (${Math.round(serviceablePercent)}%). Increase maintenance activities.`);
|
|
3168
|
+
}
|
|
3169
|
+
if (maintenancePercent < requiredMaintenance) {
|
|
3170
|
+
recommendations.push(`Maintenance compliance below threshold (${Math.round(maintenancePercent)}% vs ${requiredMaintenance}% required).`);
|
|
3171
|
+
}
|
|
3172
|
+
if (daysSinceInspection > maxInventoryAge * 0.8) {
|
|
3173
|
+
recommendations.push(`Items overdue for inspection. Last inspection ${daysSinceInspection} days ago.`);
|
|
3174
|
+
}
|
|
3175
|
+
if (transportEfficiency < 70) {
|
|
3176
|
+
recommendations.push("Transport efficiency is degraded. Review logistics network.");
|
|
3177
|
+
}
|
|
3178
|
+
if (recommendations.length === 0) {
|
|
3179
|
+
recommendations.push("Readiness levels are optimal. Continue routine maintenance.");
|
|
3180
|
+
}
|
|
3181
|
+
return {
|
|
3182
|
+
overall,
|
|
3183
|
+
serviceablePercent: Math.round(serviceablePercent * 10) / 10,
|
|
3184
|
+
maintenanceCompliance: Math.round(maintenancePercent * 10) / 10,
|
|
3185
|
+
daysSinceInspection,
|
|
3186
|
+
transportEfficiency: Math.round(transportEfficiency * 10) / 10,
|
|
3187
|
+
recommendations,
|
|
3188
|
+
assessmentAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3189
|
+
};
|
|
3190
|
+
}
|
|
3191
|
+
function validateLogisticsMovement(movement) {
|
|
3192
|
+
const errors = [];
|
|
3193
|
+
if (!movement.movementId) {
|
|
3194
|
+
errors.push("Movement ID is required");
|
|
3195
|
+
}
|
|
3196
|
+
if (!movement.itemUID) {
|
|
3197
|
+
errors.push("Item UID is required");
|
|
3198
|
+
}
|
|
3199
|
+
if (!["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"].includes(movement.supplyClass)) {
|
|
3200
|
+
errors.push("Invalid supply class");
|
|
3201
|
+
}
|
|
3202
|
+
if (!movement.timestamp) {
|
|
3203
|
+
errors.push("Timestamp is required");
|
|
3204
|
+
} else {
|
|
3205
|
+
const d = new Date(movement.timestamp);
|
|
3206
|
+
if (isNaN(d.getTime())) {
|
|
3207
|
+
errors.push("Invalid timestamp format");
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
if (!movement.custodian) {
|
|
3211
|
+
errors.push("Custodian is required");
|
|
3212
|
+
}
|
|
3213
|
+
return {
|
|
3214
|
+
valid: errors.length === 0,
|
|
3215
|
+
errors
|
|
3216
|
+
};
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
// src/multi-tenant/platform.ts
|
|
3220
|
+
async function createOrganization(params) {
|
|
3221
|
+
if (!params.name || !params.slug) {
|
|
3222
|
+
throw new Error("Name and slug are required");
|
|
3223
|
+
}
|
|
3224
|
+
if (!/^[a-z0-9-]+$/.test(params.slug)) {
|
|
3225
|
+
throw new Error("Slug must contain only lowercase letters, numbers, and hyphens");
|
|
3226
|
+
}
|
|
3227
|
+
return {
|
|
3228
|
+
id: `org_${generateId()}`,
|
|
3229
|
+
name: params.name,
|
|
3230
|
+
slug: params.slug,
|
|
3231
|
+
plan: params.plan,
|
|
3232
|
+
status: "active",
|
|
3233
|
+
settings: params.settings,
|
|
3234
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3235
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3236
|
+
};
|
|
3237
|
+
}
|
|
3238
|
+
async function getOrganization(orgId) {
|
|
3239
|
+
if (!orgId) {
|
|
3240
|
+
throw new Error("Organization ID is required");
|
|
3241
|
+
}
|
|
3242
|
+
throw new Error("getOrganization requires API integration");
|
|
3243
|
+
}
|
|
3244
|
+
async function updateOrganization(orgId, updates) {
|
|
3245
|
+
if (!orgId) {
|
|
3246
|
+
throw new Error("Organization ID is required");
|
|
3247
|
+
}
|
|
3248
|
+
if (updates.plan && !["starter", "professional", "enterprise", "sovereign"].includes(updates.plan)) {
|
|
3249
|
+
throw new Error("Invalid plan tier");
|
|
3250
|
+
}
|
|
3251
|
+
if (updates.status && !["active", "suspended", "pending"].includes(updates.status)) {
|
|
3252
|
+
throw new Error("Invalid organization status");
|
|
3253
|
+
}
|
|
3254
|
+
throw new Error("updateOrganization requires API integration");
|
|
3255
|
+
}
|
|
3256
|
+
async function listMembers(orgId) {
|
|
3257
|
+
if (!orgId) {
|
|
3258
|
+
throw new Error("Organization ID is required");
|
|
3259
|
+
}
|
|
3260
|
+
throw new Error("listMembers requires API integration");
|
|
3261
|
+
}
|
|
3262
|
+
async function inviteMember(orgId, email, role) {
|
|
3263
|
+
if (!orgId || !email || !role) {
|
|
3264
|
+
throw new Error("Organization ID, email, and role are required");
|
|
3265
|
+
}
|
|
3266
|
+
if (!isValidEmail(email)) {
|
|
3267
|
+
throw new Error("Invalid email address");
|
|
3268
|
+
}
|
|
3269
|
+
if (!["owner", "admin", "member", "viewer", "auditor"].includes(role)) {
|
|
3270
|
+
throw new Error("Invalid role");
|
|
3271
|
+
}
|
|
3272
|
+
return {
|
|
3273
|
+
id: `invite_${generateId()}`,
|
|
3274
|
+
email,
|
|
3275
|
+
role,
|
|
3276
|
+
invitedBy: "current_user",
|
|
3277
|
+
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3).toISOString(),
|
|
3278
|
+
// 30 days
|
|
3279
|
+
status: "pending"
|
|
3280
|
+
};
|
|
3281
|
+
}
|
|
3282
|
+
async function removeMember(orgId, userId) {
|
|
3283
|
+
if (!orgId || !userId) {
|
|
3284
|
+
throw new Error("Organization ID and user ID are required");
|
|
3285
|
+
}
|
|
3286
|
+
throw new Error("removeMember requires API integration");
|
|
3287
|
+
}
|
|
3288
|
+
async function updateMemberRole(orgId, userId, newRole) {
|
|
3289
|
+
if (!orgId || !userId || !newRole) {
|
|
3290
|
+
throw new Error("Organization ID, user ID, and new role are required");
|
|
3291
|
+
}
|
|
3292
|
+
if (!["owner", "admin", "member", "viewer", "auditor"].includes(newRole)) {
|
|
3293
|
+
throw new Error("Invalid role");
|
|
3294
|
+
}
|
|
3295
|
+
throw new Error("updateMemberRole requires API integration");
|
|
3296
|
+
}
|
|
3297
|
+
async function createRLSPolicy(params) {
|
|
3298
|
+
if (!params.tenantId || !params.resourceType || !params.action || !params.condition) {
|
|
3299
|
+
throw new Error("All policy parameters are required");
|
|
3300
|
+
}
|
|
3301
|
+
validateRLSCondition(params.condition);
|
|
3302
|
+
return {
|
|
3303
|
+
id: `policy_${generateId()}`,
|
|
3304
|
+
tenantId: params.tenantId,
|
|
3305
|
+
resourceType: params.resourceType,
|
|
3306
|
+
action: params.action,
|
|
3307
|
+
condition: params.condition,
|
|
3308
|
+
priority: params.priority ?? 100,
|
|
3309
|
+
enabled: true
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
async function listRLSPolicies(tenantId) {
|
|
3313
|
+
if (!tenantId) {
|
|
3314
|
+
throw new Error("Tenant ID is required");
|
|
3315
|
+
}
|
|
3316
|
+
throw new Error("listRLSPolicies requires API integration");
|
|
3317
|
+
}
|
|
3318
|
+
async function deleteRLSPolicy(policyId) {
|
|
3319
|
+
if (!policyId) {
|
|
3320
|
+
throw new Error("Policy ID is required");
|
|
3321
|
+
}
|
|
3322
|
+
throw new Error("deleteRLSPolicy requires API integration");
|
|
3323
|
+
}
|
|
3324
|
+
function validateRLSCondition(condition) {
|
|
3325
|
+
if (!condition.field || !condition.operator || condition.value === void 0) {
|
|
3326
|
+
throw new Error("RLS condition must have field, operator, and value");
|
|
3327
|
+
}
|
|
3328
|
+
const validOperators = ["eq", "neq", "in", "not_in", "contains", "starts_with", "gt", "lt"];
|
|
3329
|
+
if (!validOperators.includes(condition.operator)) {
|
|
3330
|
+
throw new Error(`Invalid RLS operator: ${condition.operator}`);
|
|
3331
|
+
}
|
|
3332
|
+
if (["in", "not_in"].includes(condition.operator) && !Array.isArray(condition.value)) {
|
|
3333
|
+
throw new Error(`Operator ${condition.operator} requires an array value`);
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
async function getUsageQuota(tenantId) {
|
|
3337
|
+
if (!tenantId) {
|
|
3338
|
+
throw new Error("Tenant ID is required");
|
|
3339
|
+
}
|
|
3340
|
+
throw new Error("getUsageQuota requires API integration");
|
|
3341
|
+
}
|
|
3342
|
+
async function updateUsageQuota(tenantId, limits) {
|
|
3343
|
+
if (!tenantId) {
|
|
3344
|
+
throw new Error("Tenant ID is required");
|
|
3345
|
+
}
|
|
3346
|
+
if (limits.assets !== void 0 && limits.assets < 0) {
|
|
3347
|
+
throw new Error("Asset limit cannot be negative");
|
|
3348
|
+
}
|
|
3349
|
+
if (limits.storage_mb !== void 0 && limits.storage_mb < 0) {
|
|
3350
|
+
throw new Error("Storage limit cannot be negative");
|
|
3351
|
+
}
|
|
3352
|
+
throw new Error("updateUsageQuota requires API integration");
|
|
3353
|
+
}
|
|
3354
|
+
function generateId() {
|
|
3355
|
+
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
3356
|
+
}
|
|
3357
|
+
function isValidEmail(email) {
|
|
3358
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
3359
|
+
}
|
|
3360
|
+
function getRolePermissions(role) {
|
|
3361
|
+
const permissionMap = {
|
|
3362
|
+
owner: [
|
|
3363
|
+
"assets:read",
|
|
3364
|
+
"assets:write",
|
|
3365
|
+
"assets:delete",
|
|
3366
|
+
"assets:admin",
|
|
3367
|
+
"members:manage",
|
|
3368
|
+
"policies:manage",
|
|
3369
|
+
"quotas:manage",
|
|
3370
|
+
"audit:read",
|
|
3371
|
+
"org:manage"
|
|
3372
|
+
],
|
|
3373
|
+
admin: [
|
|
3374
|
+
"assets:read",
|
|
3375
|
+
"assets:write",
|
|
3376
|
+
"assets:delete",
|
|
3377
|
+
"members:manage",
|
|
3378
|
+
"policies:manage",
|
|
3379
|
+
"audit:read"
|
|
3380
|
+
],
|
|
3381
|
+
member: [
|
|
3382
|
+
"assets:read",
|
|
3383
|
+
"assets:write",
|
|
3384
|
+
"audit:read"
|
|
3385
|
+
],
|
|
3386
|
+
viewer: [
|
|
3387
|
+
"assets:read"
|
|
3388
|
+
],
|
|
3389
|
+
auditor: [
|
|
3390
|
+
"assets:read",
|
|
3391
|
+
"audit:read"
|
|
3392
|
+
]
|
|
3393
|
+
};
|
|
3394
|
+
return permissionMap[role] || [];
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
// src/marketplace/partner.ts
|
|
3398
|
+
async function registerPartner(registration, requestFn) {
|
|
3399
|
+
const result = await requestFn("POST", "/marketplace/partners", registration);
|
|
3400
|
+
return result;
|
|
3401
|
+
}
|
|
3402
|
+
async function getPartner(partnerId, requestFn) {
|
|
3403
|
+
const result = await requestFn("GET", `/marketplace/partners/${partnerId}`);
|
|
3404
|
+
return result;
|
|
3405
|
+
}
|
|
3406
|
+
async function updatePartner(partnerId, params, requestFn) {
|
|
3407
|
+
const result = await requestFn("PATCH", `/marketplace/partners/${partnerId}`, params);
|
|
3408
|
+
return result;
|
|
3409
|
+
}
|
|
3410
|
+
async function getPartnerAnalytics(partnerId, period, requestFn) {
|
|
3411
|
+
const result = await requestFn(
|
|
3412
|
+
"GET",
|
|
3413
|
+
`/marketplace/partners/${partnerId}/analytics?period=${encodeURIComponent(period)}`
|
|
3414
|
+
);
|
|
3415
|
+
return result;
|
|
3416
|
+
}
|
|
3417
|
+
async function listPartners(params, requestFn) {
|
|
3418
|
+
const queryParams = new URLSearchParams();
|
|
3419
|
+
if (params?.tier) queryParams.append("tier", params.tier);
|
|
3420
|
+
if (params?.status) queryParams.append("status", params.status);
|
|
3421
|
+
if (params?.type) queryParams.append("type", params.type);
|
|
3422
|
+
if (params?.limit) queryParams.append("limit", params.limit.toString());
|
|
3423
|
+
if (params?.offset) queryParams.append("offset", params.offset.toString());
|
|
3424
|
+
const query = queryParams.toString();
|
|
3425
|
+
const path = query ? `/marketplace/partners?${query}` : "/marketplace/partners";
|
|
3426
|
+
const result = await requestFn("GET", path);
|
|
3427
|
+
return result;
|
|
3428
|
+
}
|
|
3429
|
+
async function generatePartnerApiKey(partnerId, scopes, expiresAt, requestFn) {
|
|
3430
|
+
const body = { scopes, expiresAt };
|
|
3431
|
+
const result = await requestFn("POST", `/marketplace/partners/${partnerId}/keys`, body);
|
|
3432
|
+
return result;
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
// src/marketplace/templates.ts
|
|
3436
|
+
async function createTemplate(partnerId, params, requestFn) {
|
|
3437
|
+
const body = { ...params, partnerId };
|
|
3438
|
+
const result = await requestFn("POST", "/marketplace/templates", body);
|
|
3439
|
+
return result;
|
|
3440
|
+
}
|
|
3441
|
+
async function getTemplate(templateId, requestFn) {
|
|
3442
|
+
const result = await requestFn("GET", `/marketplace/templates/${templateId}`);
|
|
3443
|
+
return result;
|
|
3444
|
+
}
|
|
3445
|
+
async function updateTemplate(templateId, params, requestFn) {
|
|
3446
|
+
const result = await requestFn("PATCH", `/marketplace/templates/${templateId}`, params);
|
|
3447
|
+
return result;
|
|
3448
|
+
}
|
|
3449
|
+
async function publishTemplate(templateId, params, requestFn) {
|
|
3450
|
+
const result = await requestFn("POST", `/marketplace/templates/${templateId}/publish`, params);
|
|
3451
|
+
return result;
|
|
3452
|
+
}
|
|
3453
|
+
async function deprecateTemplate(templateId, params, requestFn) {
|
|
3454
|
+
const result = await requestFn("POST", `/marketplace/templates/${templateId}/deprecate`, params);
|
|
3455
|
+
return result;
|
|
3456
|
+
}
|
|
3457
|
+
async function listTemplates(params, requestFn) {
|
|
3458
|
+
const queryParams = new URLSearchParams();
|
|
3459
|
+
if (params?.category) queryParams.append("category", params.category);
|
|
3460
|
+
if (params?.status) queryParams.append("status", params.status);
|
|
3461
|
+
if (params?.author) queryParams.append("author", params.author);
|
|
3462
|
+
if (params?.limit) queryParams.append("limit", params.limit.toString());
|
|
3463
|
+
if (params?.offset) queryParams.append("offset", params.offset.toString());
|
|
3464
|
+
const query = queryParams.toString();
|
|
3465
|
+
const path = query ? `/marketplace/templates?${query}` : "/marketplace/templates";
|
|
3466
|
+
const result = await requestFn("GET", path);
|
|
3467
|
+
return result;
|
|
3468
|
+
}
|
|
3469
|
+
async function searchTemplates(query, params, requestFn) {
|
|
3470
|
+
const queryParams = new URLSearchParams({ q: query });
|
|
3471
|
+
if (params?.category) queryParams.append("category", params.category);
|
|
3472
|
+
if (params?.limit) queryParams.append("limit", params.limit.toString());
|
|
3473
|
+
const result = await requestFn(
|
|
3474
|
+
"GET",
|
|
3475
|
+
`/marketplace/templates/search?${queryParams.toString()}`
|
|
3476
|
+
);
|
|
3477
|
+
return result;
|
|
3478
|
+
}
|
|
3479
|
+
async function installTemplate(templateId, requestFn) {
|
|
3480
|
+
const result = await requestFn("POST", `/marketplace/templates/${templateId}/install`);
|
|
3481
|
+
return result;
|
|
3482
|
+
}
|
|
3483
|
+
async function uninstallTemplate(templateId, requestFn) {
|
|
3484
|
+
const result = await requestFn("POST", `/marketplace/templates/${templateId}/uninstall`);
|
|
3485
|
+
return result;
|
|
3486
|
+
}
|
|
3487
|
+
async function rateTemplate(templateId, params, requestFn) {
|
|
3488
|
+
const result = await requestFn("POST", `/marketplace/templates/${templateId}/rate`, params);
|
|
3489
|
+
return result;
|
|
3490
|
+
}
|
|
3491
|
+
async function validateAgainstTemplate(templateId, data, requestFn) {
|
|
3492
|
+
const result = await requestFn("POST", `/marketplace/templates/${templateId}/validate`, { data });
|
|
3493
|
+
return result;
|
|
3494
|
+
}
|
|
3495
|
+
|
|
1471
3496
|
// src/index.ts
|
|
1472
|
-
var SDK_VERSION2 = "
|
|
3497
|
+
var SDK_VERSION2 = "3.0.0";
|
|
1473
3498
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1474
3499
|
0 && (module.exports = {
|
|
1475
3500
|
AssetsResource,
|
|
1476
3501
|
AuditResource,
|
|
1477
3502
|
AuthenticationError,
|
|
1478
3503
|
BatchNotFoundError,
|
|
3504
|
+
BatchesResource,
|
|
1479
3505
|
CodeNotFoundError,
|
|
1480
3506
|
ComplianceResource,
|
|
3507
|
+
DPPAccessLevel,
|
|
1481
3508
|
DocumentsResource,
|
|
1482
3509
|
InvalidCodeError,
|
|
1483
3510
|
InvalidGTINError,
|
|
1484
3511
|
InvalidSerialError,
|
|
1485
3512
|
KeysResource,
|
|
1486
3513
|
KeysetsResource,
|
|
3514
|
+
M2MResource,
|
|
1487
3515
|
NetworkError,
|
|
1488
3516
|
OptropicClient,
|
|
1489
3517
|
OptropicError,
|
|
3518
|
+
Permission,
|
|
1490
3519
|
ProvenanceResource,
|
|
1491
3520
|
QuotaExceededError,
|
|
1492
3521
|
RateLimitedError,
|
|
@@ -1495,13 +3524,87 @@ var SDK_VERSION2 = "2.3.0";
|
|
|
1495
3524
|
SchemasResource,
|
|
1496
3525
|
ServiceUnavailableError,
|
|
1497
3526
|
StaleFilterError,
|
|
3527
|
+
TenantsResource,
|
|
1498
3528
|
TimeoutError,
|
|
3529
|
+
appendRecord,
|
|
3530
|
+
buildBatteryPassportQR,
|
|
1499
3531
|
buildDPPConfig,
|
|
3532
|
+
buildDefenceMetadata,
|
|
3533
|
+
buildEPCISDocument,
|
|
3534
|
+
buildGS1DigitalLink,
|
|
3535
|
+
calculateReadiness,
|
|
3536
|
+
calibrateThreshold,
|
|
3537
|
+
computeDistance,
|
|
3538
|
+
computeSimilarity,
|
|
3539
|
+
createAuditChain,
|
|
1500
3540
|
createClient,
|
|
3541
|
+
createConsensusResultMessage,
|
|
3542
|
+
createDescriptorExchange,
|
|
1501
3543
|
createErrorFromResponse,
|
|
3544
|
+
createHandshakeAccept,
|
|
3545
|
+
createHandshakeInit,
|
|
3546
|
+
createOrganization,
|
|
3547
|
+
createRLSPolicy,
|
|
3548
|
+
createTemplate,
|
|
3549
|
+
decodeNATOUID,
|
|
3550
|
+
deleteRLSPolicy,
|
|
3551
|
+
deprecateTemplate,
|
|
3552
|
+
deserializeMessage,
|
|
3553
|
+
encodeNATOUID,
|
|
3554
|
+
evaluateConsensus,
|
|
3555
|
+
exportChain,
|
|
3556
|
+
filterDPPByAccess,
|
|
3557
|
+
generatePartnerApiKey,
|
|
3558
|
+
generateQRCodePayload,
|
|
3559
|
+
getAILabel,
|
|
3560
|
+
getDPPAccessPolicy,
|
|
3561
|
+
getEPCISMappingTable,
|
|
3562
|
+
getOrganization,
|
|
3563
|
+
getPartner,
|
|
3564
|
+
getPartnerAnalytics,
|
|
3565
|
+
getRolePermissions,
|
|
3566
|
+
getSupportedAIs,
|
|
3567
|
+
getTemplate,
|
|
3568
|
+
getUsageQuota,
|
|
3569
|
+
importChain,
|
|
3570
|
+
installTemplate,
|
|
3571
|
+
inviteMember,
|
|
3572
|
+
isValidAI,
|
|
3573
|
+
listMembers,
|
|
3574
|
+
listPartners,
|
|
3575
|
+
listRLSPolicies,
|
|
3576
|
+
listTemplates,
|
|
3577
|
+
mapMovementToProvenance,
|
|
3578
|
+
mapNATOUIDToOptropic,
|
|
3579
|
+
mapOptropicToNATOUID,
|
|
3580
|
+
mapToEPCIS,
|
|
3581
|
+
mapToOptropicAsset,
|
|
1502
3582
|
parseFilterHeader,
|
|
3583
|
+
parseGS1DigitalLink,
|
|
1503
3584
|
parseSaltsHeader,
|
|
3585
|
+
provenanceChainToEPCIS,
|
|
3586
|
+
publishTemplate,
|
|
3587
|
+
rateTemplate,
|
|
3588
|
+
registerPartner,
|
|
3589
|
+
removeMember,
|
|
3590
|
+
searchTemplates,
|
|
3591
|
+
serializeMessage,
|
|
3592
|
+
toEPCISEvent,
|
|
3593
|
+
uninstallTemplate,
|
|
3594
|
+
updateMemberRole,
|
|
3595
|
+
updateOrganization,
|
|
3596
|
+
updatePartner,
|
|
3597
|
+
updateTemplate,
|
|
3598
|
+
updateUsageQuota,
|
|
3599
|
+
validateAgainstTemplate,
|
|
3600
|
+
validateBatteryPassport,
|
|
1504
3601
|
validateDPPMetadata,
|
|
3602
|
+
validateDefenceMetadata,
|
|
3603
|
+
validateGTIN,
|
|
3604
|
+
validateLogisticsMovement,
|
|
3605
|
+
validateMessage,
|
|
3606
|
+
validateNATOUID,
|
|
3607
|
+
verifyChain,
|
|
1505
3608
|
verifyOffline,
|
|
1506
3609
|
verifyWebhookSignature
|
|
1507
3610
|
});
|