@tracepass/dpp-epcis 0.1.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/LICENSE +202 -0
- package/README.md +65 -0
- package/dist/cbv.d.ts +131 -0
- package/dist/cbv.d.ts.map +1 -0
- package/dist/cbv.js +201 -0
- package/dist/cbv.js.map +1 -0
- package/dist/context.d.ts +55 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +58 -0
- package/dist/context.js.map +1 -0
- package/dist/event-builders.d.ts +126 -0
- package/dist/event-builders.d.ts.map +1 -0
- package/dist/event-builders.js +230 -0
- package/dist/event-builders.js.map +1 -0
- package/dist/identifiers.d.ts +61 -0
- package/dist/identifiers.d.ts.map +1 -0
- package/dist/identifiers.js +86 -0
- package/dist/identifiers.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/validate.d.ts +38 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +216 -0
- package/dist/validate.js.map +1 -0
- package/package.json +22 -0
package/dist/context.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EPCIS 2.0 JSON-LD @context for TracePass-emitted EPCIS documents.
|
|
3
|
+
*
|
|
4
|
+
* An EPCIS 2.0 document is JSON-LD — the same linked-data family as the
|
|
5
|
+
* schema.org JSON-LD the public passport viewer already emits. The
|
|
6
|
+
* @context tells a consumer which vocabularies the document's terms
|
|
7
|
+
* come from.
|
|
8
|
+
*
|
|
9
|
+
* Four vocabularies compose here:
|
|
10
|
+
* 1. The GS1-published EPCIS 2.0 context — the canonical source of
|
|
11
|
+
* `EPCISDocument`, `ObjectEvent`, `eventTime`, `bizStep`, etc.
|
|
12
|
+
* GS1 hosts this at a stable URL; consumers dereference it.
|
|
13
|
+
* 2. The GS1 CBV terms — Core Business Vocabulary (bizSteps,
|
|
14
|
+
* dispositions). Carried inside the EPCIS context for 2.0.
|
|
15
|
+
* 3. `cbv:` alias — so CBV-standard bizStep/disposition values that
|
|
16
|
+
* DO exist in the standard vocabulary are written with the
|
|
17
|
+
* official `cbv:` prefix.
|
|
18
|
+
* 4. `tracepass:` alias — for the terms that are genuinely ours:
|
|
19
|
+
* the custom bizStep URIs we coin for production steps absent
|
|
20
|
+
* from CBV (smelting, rolling, …), and any TracePass-specific
|
|
21
|
+
* annotation we attach to an event.
|
|
22
|
+
*
|
|
23
|
+
* Design call (per the 2026-05-22 EPCIS review, decision [Q2]):
|
|
24
|
+
* production steps that CBV's fixed bizStep list doesn't cover get
|
|
25
|
+
* TracePass-owned vocabulary URIs under `https://tracepass.eu/cbv/`.
|
|
26
|
+
* This is the GS1-sanctioned industry-extension pattern — a valid
|
|
27
|
+
* EPCIS document MAY use bizStep values outside the standard CBV as
|
|
28
|
+
* long as they are URIs. The `tracepass:` alias keeps those URIs
|
|
29
|
+
* compact in the serialized JSON.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* The GS1-hosted EPCIS 2.0 JSON-LD context URL. Stable, published by
|
|
33
|
+
* GS1. Consumers dereference it to resolve EPCIS + CBV core terms.
|
|
34
|
+
*/
|
|
35
|
+
export const EPCIS_CONTEXT_URL = "https://ref.gs1.org/standards/epcis/2.0.0/epcis-context.jsonld";
|
|
36
|
+
/**
|
|
37
|
+
* Base URI for the TracePass-owned vocabulary. The CBV bizStep
|
|
38
|
+
* extension URIs (`.../cbv/bizstep/smelting`) and any other
|
|
39
|
+
* TracePass-namespaced EPCIS term hang off this. Published as a
|
|
40
|
+
* resolvable JSON-LD vocabulary document — see `cbv.ts` for the
|
|
41
|
+
* term list and `docs`/the vocabulary route for the published doc.
|
|
42
|
+
*/
|
|
43
|
+
export const TRACEPASS_VOC_BASE = "https://tracepass.eu/voc/";
|
|
44
|
+
/**
|
|
45
|
+
* The `@context` value placed on every EPCIS document TracePass
|
|
46
|
+
* emits. Array form: the GS1 EPCIS 2.0 context URL first (so its
|
|
47
|
+
* term definitions take precedence), then a local object defining
|
|
48
|
+
* the `tracepass:` alias.
|
|
49
|
+
*
|
|
50
|
+
* The `cbv:` prefix is already defined inside the GS1 EPCIS context,
|
|
51
|
+
* so it is NOT redeclared here — redeclaring it would risk shadowing
|
|
52
|
+
* the official definition.
|
|
53
|
+
*/
|
|
54
|
+
export const EPCIS_JSONLD_CONTEXT = [
|
|
55
|
+
EPCIS_CONTEXT_URL,
|
|
56
|
+
{ tracepass: TRACEPASS_VOC_BASE },
|
|
57
|
+
];
|
|
58
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAC5B,gEAAgE,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,2BAA2B,CAAC;AAE9D;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAmD;IAClF,iBAAiB;IACjB,EAAE,SAAS,EAAE,kBAAkB,EAAE;CACzB,CAAC"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure builders for individual EPCIS 2.0 events.
|
|
3
|
+
*
|
|
4
|
+
* Each function takes a TracePass domain object and returns a plain
|
|
5
|
+
* JSON-LD-shaped EPCIS event object. No IO, no template lookups, no
|
|
6
|
+
* @context (the document builder in `document.ts` adds the single
|
|
7
|
+
* shared @context once for the whole document).
|
|
8
|
+
*
|
|
9
|
+
* Three event sources, per the [Q1] decision (all three in Layer A,
|
|
10
|
+
* `supplyChainEvents` first):
|
|
11
|
+
* - `supplyChainEvents` (steel template field 77) → TransformationEvent
|
|
12
|
+
* — a production step consumes inputs and yields the product.
|
|
13
|
+
* - `ServiceEvent` → ObjectEvent — repair/inspection observed
|
|
14
|
+
* on the product after it exists.
|
|
15
|
+
* - `OwnershipTransfer` → ObjectEvent — a custody/ownership change.
|
|
16
|
+
*
|
|
17
|
+
* EPCIS time fields: every event needs `eventTime` (an instant) and
|
|
18
|
+
* `eventTimeZoneOffset` (the offset that instant was recorded in).
|
|
19
|
+
* TracePass stores timestamps as UTC `Date`s with no original offset
|
|
20
|
+
* retained, so we emit `eventTime` in UTC and `eventTimeZoneOffset`
|
|
21
|
+
* as `+00:00`. This is honest — it says "recorded in UTC" — rather
|
|
22
|
+
* than inventing a local offset we never captured.
|
|
23
|
+
*
|
|
24
|
+
* Unit-tested in tests/epcis/event-builders.test.ts.
|
|
25
|
+
*/
|
|
26
|
+
import type { ServiceEvent, OwnershipTransfer } from "@tracepass/dpp-types";
|
|
27
|
+
/**
|
|
28
|
+
* One entry of the steel template's `supplyChainEvents` array (field
|
|
29
|
+
* 77). The template's `aiHints.expectedFormat` documents the shape:
|
|
30
|
+
* `{eventType, timestamp, location, country, operator, epcisEventId}`.
|
|
31
|
+
* Every field is optional in practice — supplier-provided data is
|
|
32
|
+
* uneven — so the builder must tolerate partial entries.
|
|
33
|
+
*/
|
|
34
|
+
export interface SupplyChainEventInput {
|
|
35
|
+
eventType?: string;
|
|
36
|
+
timestamp?: string;
|
|
37
|
+
location?: string;
|
|
38
|
+
country?: string;
|
|
39
|
+
operator?: string;
|
|
40
|
+
/** A pre-existing EPCIS event id, if the source system already had
|
|
41
|
+
* one. When present we reuse it instead of minting our own. */
|
|
42
|
+
epcisEventId?: string;
|
|
43
|
+
/** GLN of the operating site, when the source provided one. */
|
|
44
|
+
gln?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Convert an ISO-ish timestamp string (or Date) to an EPCIS
|
|
48
|
+
* `eventTime` string. Returns null when the value can't be parsed —
|
|
49
|
+
* the caller drops the event rather than emit an invalid time.
|
|
50
|
+
*/
|
|
51
|
+
export declare function toEventTime(value: string | Date | undefined | null): string | null;
|
|
52
|
+
/**
|
|
53
|
+
* Build a TransformationEvent from one `supplyChainEvents` entry.
|
|
54
|
+
*
|
|
55
|
+
* A production step is a transformation: inputs (ore, billet, scrap)
|
|
56
|
+
* become the output product. We rarely hold the input EPCs from
|
|
57
|
+
* supplier data, so `inputEPCList` is usually empty and the event
|
|
58
|
+
* carries only the `outputEPCList` (the product) — still a valid
|
|
59
|
+
* TransformationEvent under EPCIS 2.0.
|
|
60
|
+
*
|
|
61
|
+
* Returns null when the entry has no usable timestamp (an EPCIS event
|
|
62
|
+
* without `eventTime` is invalid).
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildTransformationEvent(entry: SupplyChainEventInput, productEpc: string, passportId: string, index: number): Record<string, unknown> | null;
|
|
65
|
+
/**
|
|
66
|
+
* Build an ObjectEvent from a `ServiceEvent` (repair / warranty /
|
|
67
|
+
* maintenance / inspection / replacement / recall).
|
|
68
|
+
*
|
|
69
|
+
* The event is `action: "OBSERVE"` — a service event observes an
|
|
70
|
+
* existing object, it doesn't create or destroy it. Uses `performedAt`
|
|
71
|
+
* for the time; falls back to `createdAt` when the service wasn't
|
|
72
|
+
* marked performed (a scheduled-but-not-done service still has a
|
|
73
|
+
* record time). Returns null when neither timestamp is usable.
|
|
74
|
+
*/
|
|
75
|
+
export declare function buildServiceObjectEvent(service: Pick<ServiceEvent, "_id" | "type" | "title" | "performedBy" | "performedAt" | "createdAt" | "warrantyRef">, productEpc: string, passportId: string): Record<string, unknown> | null;
|
|
76
|
+
/**
|
|
77
|
+
* Build an ObjectEvent from an `OwnershipTransfer`. A custody change
|
|
78
|
+
* is an `action: "OBSERVE"` ObjectEvent — the object persists, only
|
|
79
|
+
* its holder changes. We only export *accepted* transfers (a pending
|
|
80
|
+
* or rejected transfer is not a real custody change) — the caller is
|
|
81
|
+
* expected to filter, but the builder also guards.
|
|
82
|
+
*
|
|
83
|
+
* Returns null when the transfer isn't accepted or has no usable time.
|
|
84
|
+
*/
|
|
85
|
+
export declare function buildOwnershipObjectEvent(transfer: Pick<OwnershipTransfer, "_id" | "reason" | "transferredAt" | "createdAt" | "status" | "fromName" | "toName">, productEpc: string, passportId: string): Record<string, unknown> | null;
|
|
86
|
+
/**
|
|
87
|
+
* Build the commissioning ObjectEvent — the "this passport went live"
|
|
88
|
+
* event, emitted from `passport.publishedAt`. Every published passport
|
|
89
|
+
* gets exactly one; it anchors the event timeline even when the
|
|
90
|
+
* passport has no supply-chain or service events at all.
|
|
91
|
+
*
|
|
92
|
+
* Returns null when there is no publish timestamp (an unpublished
|
|
93
|
+
* passport shouldn't be EPCIS-exported in the first place).
|
|
94
|
+
*/
|
|
95
|
+
export declare function buildCommissioningEvent(publishedAt: Date | string | undefined | null, productEpc: string, passportId: string, manufacturerGln?: string | null): Record<string, unknown> | null;
|
|
96
|
+
/**
|
|
97
|
+
* One supply-chain event a supplier reports through the portal — the
|
|
98
|
+
* plain shape collected from the supplier-facing form (mirrors the
|
|
99
|
+
* Zod `supplierSupplyChainEventSchema`).
|
|
100
|
+
*/
|
|
101
|
+
export interface SupplierReportedEvent {
|
|
102
|
+
eventType: string;
|
|
103
|
+
timestamp: string;
|
|
104
|
+
location?: string;
|
|
105
|
+
country?: string;
|
|
106
|
+
gln?: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Build an EPCIS ObjectEvent from a supplier-reported supply-chain
|
|
110
|
+
* event. A supplier describing "shipping" / "receiving" / a
|
|
111
|
+
* production step is reporting an observation about the component
|
|
112
|
+
* they supply — an `action: "OBSERVE"` ObjectEvent.
|
|
113
|
+
*
|
|
114
|
+
* `bizStep` is mapped through `mapEventTypeToBizStep`, so both
|
|
115
|
+
* standard CBV steps (shipping, receiving) and production steps
|
|
116
|
+
* (smelting, …) resolve correctly — the same mapping the inline
|
|
117
|
+
* supply-chain events use.
|
|
118
|
+
*
|
|
119
|
+
* `index` makes the minted `eventID` deterministic per (passport,
|
|
120
|
+
* supplier-submission-index), so re-capturing the same submission
|
|
121
|
+
* dedupes rather than piling up duplicates.
|
|
122
|
+
*
|
|
123
|
+
* Returns null when the report has no usable timestamp.
|
|
124
|
+
*/
|
|
125
|
+
export declare function buildSupplierObjectEvent(report: SupplierReportedEvent, productEpc: string, passportId: string, index: number): Record<string, unknown> | null;
|
|
126
|
+
//# sourceMappingURL=event-builders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-builders.d.ts","sourceRoot":"","sources":["../src/event-builders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAY5E;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;oEACgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAKlF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,qBAAqB,EAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,GACZ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CA+BhC;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,IAAI,CACX,YAAY,EACZ,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,aAAa,GAAG,aAAa,GAAG,WAAW,GAAG,aAAa,CACvF,EACD,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAuBhC;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,IAAI,CACZ,iBAAiB,EACjB,KAAK,GAAG,QAAQ,GAAG,eAAe,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ,CACpF,EACD,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CA2BhC;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,EAC7C,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,GAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAkBhC;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,qBAAqB,EAC7B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,GACZ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CA4BhC"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure builders for individual EPCIS 2.0 events.
|
|
3
|
+
*
|
|
4
|
+
* Each function takes a TracePass domain object and returns a plain
|
|
5
|
+
* JSON-LD-shaped EPCIS event object. No IO, no template lookups, no
|
|
6
|
+
* @context (the document builder in `document.ts` adds the single
|
|
7
|
+
* shared @context once for the whole document).
|
|
8
|
+
*
|
|
9
|
+
* Three event sources, per the [Q1] decision (all three in Layer A,
|
|
10
|
+
* `supplyChainEvents` first):
|
|
11
|
+
* - `supplyChainEvents` (steel template field 77) → TransformationEvent
|
|
12
|
+
* — a production step consumes inputs and yields the product.
|
|
13
|
+
* - `ServiceEvent` → ObjectEvent — repair/inspection observed
|
|
14
|
+
* on the product after it exists.
|
|
15
|
+
* - `OwnershipTransfer` → ObjectEvent — a custody/ownership change.
|
|
16
|
+
*
|
|
17
|
+
* EPCIS time fields: every event needs `eventTime` (an instant) and
|
|
18
|
+
* `eventTimeZoneOffset` (the offset that instant was recorded in).
|
|
19
|
+
* TracePass stores timestamps as UTC `Date`s with no original offset
|
|
20
|
+
* retained, so we emit `eventTime` in UTC and `eventTimeZoneOffset`
|
|
21
|
+
* as `+00:00`. This is honest — it says "recorded in UTC" — rather
|
|
22
|
+
* than inventing a local offset we never captured.
|
|
23
|
+
*
|
|
24
|
+
* Unit-tested in tests/epcis/event-builders.test.ts.
|
|
25
|
+
*/
|
|
26
|
+
import { mapEventTypeToBizStep, mapServiceEventType, mapOwnershipReasonDisposition, CBV_BIZSTEP_URI, } from "./cbv.js";
|
|
27
|
+
import { locationRef, buildEventId } from "./identifiers.js";
|
|
28
|
+
/** EPCIS `eventTimeZoneOffset` for our UTC-stored timestamps. */
|
|
29
|
+
const UTC_OFFSET = "+00:00";
|
|
30
|
+
/**
|
|
31
|
+
* Convert an ISO-ish timestamp string (or Date) to an EPCIS
|
|
32
|
+
* `eventTime` string. Returns null when the value can't be parsed —
|
|
33
|
+
* the caller drops the event rather than emit an invalid time.
|
|
34
|
+
*/
|
|
35
|
+
export function toEventTime(value) {
|
|
36
|
+
if (!value)
|
|
37
|
+
return null;
|
|
38
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
39
|
+
if (Number.isNaN(d.getTime()))
|
|
40
|
+
return null;
|
|
41
|
+
return d.toISOString();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build a TransformationEvent from one `supplyChainEvents` entry.
|
|
45
|
+
*
|
|
46
|
+
* A production step is a transformation: inputs (ore, billet, scrap)
|
|
47
|
+
* become the output product. We rarely hold the input EPCs from
|
|
48
|
+
* supplier data, so `inputEPCList` is usually empty and the event
|
|
49
|
+
* carries only the `outputEPCList` (the product) — still a valid
|
|
50
|
+
* TransformationEvent under EPCIS 2.0.
|
|
51
|
+
*
|
|
52
|
+
* Returns null when the entry has no usable timestamp (an EPCIS event
|
|
53
|
+
* without `eventTime` is invalid).
|
|
54
|
+
*/
|
|
55
|
+
export function buildTransformationEvent(entry, productEpc, passportId, index) {
|
|
56
|
+
const eventTime = toEventTime(entry.timestamp);
|
|
57
|
+
if (!eventTime)
|
|
58
|
+
return null;
|
|
59
|
+
const { bizStep } = mapEventTypeToBizStep(entry.eventType ?? "");
|
|
60
|
+
const event = {
|
|
61
|
+
type: "TransformationEvent",
|
|
62
|
+
eventID: entry.epcisEventId || buildEventId(passportId, `sce-${index}`),
|
|
63
|
+
eventTime,
|
|
64
|
+
eventTimeZoneOffset: UTC_OFFSET,
|
|
65
|
+
outputEPCList: [productEpc],
|
|
66
|
+
bizStep,
|
|
67
|
+
};
|
|
68
|
+
const where = locationRef(entry.gln);
|
|
69
|
+
if (where) {
|
|
70
|
+
event.bizLocation = where;
|
|
71
|
+
}
|
|
72
|
+
// Preserve human-readable provenance that has no native EPCIS slot
|
|
73
|
+
// under the tracepass: namespace — a strict consumer ignores it,
|
|
74
|
+
// a TracePass-aware one keeps the operator/country/site label.
|
|
75
|
+
const annotations = {};
|
|
76
|
+
if (entry.eventType)
|
|
77
|
+
annotations["tracepass:processStep"] = entry.eventType;
|
|
78
|
+
if (entry.operator)
|
|
79
|
+
annotations["tracepass:operator"] = entry.operator;
|
|
80
|
+
if (entry.country)
|
|
81
|
+
annotations["tracepass:country"] = entry.country;
|
|
82
|
+
if (entry.location)
|
|
83
|
+
annotations["tracepass:locationName"] = entry.location;
|
|
84
|
+
Object.assign(event, annotations);
|
|
85
|
+
return event;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Build an ObjectEvent from a `ServiceEvent` (repair / warranty /
|
|
89
|
+
* maintenance / inspection / replacement / recall).
|
|
90
|
+
*
|
|
91
|
+
* The event is `action: "OBSERVE"` — a service event observes an
|
|
92
|
+
* existing object, it doesn't create or destroy it. Uses `performedAt`
|
|
93
|
+
* for the time; falls back to `createdAt` when the service wasn't
|
|
94
|
+
* marked performed (a scheduled-but-not-done service still has a
|
|
95
|
+
* record time). Returns null when neither timestamp is usable.
|
|
96
|
+
*/
|
|
97
|
+
export function buildServiceObjectEvent(service, productEpc, passportId) {
|
|
98
|
+
const eventTime = toEventTime(service.performedAt ?? service.createdAt);
|
|
99
|
+
if (!eventTime)
|
|
100
|
+
return null;
|
|
101
|
+
const { bizStep, disposition } = mapServiceEventType(service.type);
|
|
102
|
+
const event = {
|
|
103
|
+
type: "ObjectEvent",
|
|
104
|
+
eventID: buildEventId(passportId, `service-${service._id.toString()}`),
|
|
105
|
+
eventTime,
|
|
106
|
+
eventTimeZoneOffset: UTC_OFFSET,
|
|
107
|
+
epcList: [productEpc],
|
|
108
|
+
action: "OBSERVE",
|
|
109
|
+
bizStep,
|
|
110
|
+
disposition,
|
|
111
|
+
"tracepass:serviceType": service.type,
|
|
112
|
+
};
|
|
113
|
+
if (service.title)
|
|
114
|
+
event["tracepass:serviceTitle"] = service.title;
|
|
115
|
+
if (service.performedBy)
|
|
116
|
+
event["tracepass:performedBy"] = service.performedBy;
|
|
117
|
+
if (service.warrantyRef)
|
|
118
|
+
event["tracepass:warrantyRef"] = service.warrantyRef;
|
|
119
|
+
return event;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Build an ObjectEvent from an `OwnershipTransfer`. A custody change
|
|
123
|
+
* is an `action: "OBSERVE"` ObjectEvent — the object persists, only
|
|
124
|
+
* its holder changes. We only export *accepted* transfers (a pending
|
|
125
|
+
* or rejected transfer is not a real custody change) — the caller is
|
|
126
|
+
* expected to filter, but the builder also guards.
|
|
127
|
+
*
|
|
128
|
+
* Returns null when the transfer isn't accepted or has no usable time.
|
|
129
|
+
*/
|
|
130
|
+
export function buildOwnershipObjectEvent(transfer, productEpc, passportId) {
|
|
131
|
+
if (transfer.status !== "accepted")
|
|
132
|
+
return null;
|
|
133
|
+
const eventTime = toEventTime(transfer.transferredAt ?? transfer.createdAt);
|
|
134
|
+
if (!eventTime)
|
|
135
|
+
return null;
|
|
136
|
+
const event = {
|
|
137
|
+
type: "ObjectEvent",
|
|
138
|
+
eventID: buildEventId(passportId, `ownership-${transfer._id.toString()}`),
|
|
139
|
+
eventTime,
|
|
140
|
+
eventTimeZoneOffset: UTC_OFFSET,
|
|
141
|
+
epcList: [productEpc],
|
|
142
|
+
action: "OBSERVE",
|
|
143
|
+
bizStep: transfer.reason === "recycling"
|
|
144
|
+
? CBV_BIZSTEP_URI.decommissioning
|
|
145
|
+
: CBV_BIZSTEP_URI.shipping,
|
|
146
|
+
disposition: mapOwnershipReasonDisposition(transfer.reason),
|
|
147
|
+
"tracepass:ownershipReason": transfer.reason,
|
|
148
|
+
};
|
|
149
|
+
// from/to names are custody parties; no GLN is held for them, so
|
|
150
|
+
// they ride as tracepass: annotations rather than EPCIS sourceList.
|
|
151
|
+
if (transfer.fromName)
|
|
152
|
+
event["tracepass:transferFrom"] = transfer.fromName;
|
|
153
|
+
if (transfer.toName)
|
|
154
|
+
event["tracepass:transferTo"] = transfer.toName;
|
|
155
|
+
return event;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Build the commissioning ObjectEvent — the "this passport went live"
|
|
159
|
+
* event, emitted from `passport.publishedAt`. Every published passport
|
|
160
|
+
* gets exactly one; it anchors the event timeline even when the
|
|
161
|
+
* passport has no supply-chain or service events at all.
|
|
162
|
+
*
|
|
163
|
+
* Returns null when there is no publish timestamp (an unpublished
|
|
164
|
+
* passport shouldn't be EPCIS-exported in the first place).
|
|
165
|
+
*/
|
|
166
|
+
export function buildCommissioningEvent(publishedAt, productEpc, passportId, manufacturerGln) {
|
|
167
|
+
const eventTime = toEventTime(publishedAt);
|
|
168
|
+
if (!eventTime)
|
|
169
|
+
return null;
|
|
170
|
+
const event = {
|
|
171
|
+
type: "ObjectEvent",
|
|
172
|
+
eventID: buildEventId(passportId, "commissioning"),
|
|
173
|
+
eventTime,
|
|
174
|
+
eventTimeZoneOffset: UTC_OFFSET,
|
|
175
|
+
epcList: [productEpc],
|
|
176
|
+
action: "ADD",
|
|
177
|
+
bizStep: CBV_BIZSTEP_URI.commissioning,
|
|
178
|
+
};
|
|
179
|
+
const where = locationRef(manufacturerGln);
|
|
180
|
+
if (where)
|
|
181
|
+
event.bizLocation = where;
|
|
182
|
+
return event;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Build an EPCIS ObjectEvent from a supplier-reported supply-chain
|
|
186
|
+
* event. A supplier describing "shipping" / "receiving" / a
|
|
187
|
+
* production step is reporting an observation about the component
|
|
188
|
+
* they supply — an `action: "OBSERVE"` ObjectEvent.
|
|
189
|
+
*
|
|
190
|
+
* `bizStep` is mapped through `mapEventTypeToBizStep`, so both
|
|
191
|
+
* standard CBV steps (shipping, receiving) and production steps
|
|
192
|
+
* (smelting, …) resolve correctly — the same mapping the inline
|
|
193
|
+
* supply-chain events use.
|
|
194
|
+
*
|
|
195
|
+
* `index` makes the minted `eventID` deterministic per (passport,
|
|
196
|
+
* supplier-submission-index), so re-capturing the same submission
|
|
197
|
+
* dedupes rather than piling up duplicates.
|
|
198
|
+
*
|
|
199
|
+
* Returns null when the report has no usable timestamp.
|
|
200
|
+
*/
|
|
201
|
+
export function buildSupplierObjectEvent(report, productEpc, passportId, index) {
|
|
202
|
+
const eventTime = toEventTime(report.timestamp);
|
|
203
|
+
if (!eventTime)
|
|
204
|
+
return null;
|
|
205
|
+
const { bizStep } = mapEventTypeToBizStep(report.eventType ?? "");
|
|
206
|
+
const event = {
|
|
207
|
+
type: "ObjectEvent",
|
|
208
|
+
eventID: buildEventId(passportId, `supplier-${index}`),
|
|
209
|
+
eventTime,
|
|
210
|
+
eventTimeZoneOffset: UTC_OFFSET,
|
|
211
|
+
epcList: [productEpc],
|
|
212
|
+
action: "OBSERVE",
|
|
213
|
+
bizStep,
|
|
214
|
+
};
|
|
215
|
+
const where = locationRef(report.gln);
|
|
216
|
+
if (where)
|
|
217
|
+
event.bizLocation = where;
|
|
218
|
+
// Human-readable provenance the supplier gave — no native EPCIS
|
|
219
|
+
// slot, kept under the tracepass: namespace (a strict consumer
|
|
220
|
+
// ignores it, a TracePass-aware one keeps the supplier's labels).
|
|
221
|
+
if (report.eventType)
|
|
222
|
+
event["tracepass:processStep"] = report.eventType;
|
|
223
|
+
if (report.country)
|
|
224
|
+
event["tracepass:country"] = report.country;
|
|
225
|
+
if (report.location)
|
|
226
|
+
event["tracepass:locationName"] = report.location;
|
|
227
|
+
event["tracepass:reportedBySupplier"] = true;
|
|
228
|
+
return event;
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=event-builders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-builders.js","sourceRoot":"","sources":["../src/event-builders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,6BAA6B,EAC7B,eAAe,GAChB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAE7D,iEAAiE;AACjE,MAAM,UAAU,GAAG,QAAQ,CAAC;AAsB5B;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,KAAuC;IACjE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,CAAC,GAAG,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1D,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;AACzB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,wBAAwB,CACtC,KAA4B,EAC5B,UAAkB,EAClB,UAAkB,EAClB,KAAa;IAEb,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAE5B,MAAM,EAAE,OAAO,EAAE,GAAG,qBAAqB,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IAEjE,MAAM,KAAK,GAA4B;QACrC,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,KAAK,CAAC,YAAY,IAAI,YAAY,CAAC,UAAU,EAAE,OAAO,KAAK,EAAE,CAAC;QACvE,SAAS;QACT,mBAAmB,EAAE,UAAU;QAC/B,aAAa,EAAE,CAAC,UAAU,CAAC;QAC3B,OAAO;KACR,CAAC;IAEF,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,KAAK,EAAE,CAAC;QACV,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC;IAC5B,CAAC;IAED,mEAAmE;IACnE,iEAAiE;IACjE,+DAA+D;IAC/D,MAAM,WAAW,GAA4B,EAAE,CAAC;IAChD,IAAI,KAAK,CAAC,SAAS;QAAE,WAAW,CAAC,uBAAuB,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC;IAC5E,IAAI,KAAK,CAAC,QAAQ;QAAE,WAAW,CAAC,oBAAoB,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC;IACvE,IAAI,KAAK,CAAC,OAAO;QAAE,WAAW,CAAC,mBAAmB,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC;IACpE,IAAI,KAAK,CAAC,QAAQ;QAAE,WAAW,CAAC,wBAAwB,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC;IAC3E,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;IAElC,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAGC,EACD,UAAkB,EAClB,UAAkB;IAElB,MAAM,SAAS,GAAG,WAAW,CAAC,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC;IACxE,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAE5B,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,mBAAmB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnE,MAAM,KAAK,GAA4B;QACrC,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,YAAY,CAAC,UAAU,EAAE,WAAW,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC;QACtE,SAAS;QACT,mBAAmB,EAAE,UAAU;QAC/B,OAAO,EAAE,CAAC,UAAU,CAAC;QACrB,MAAM,EAAE,SAAS;QACjB,OAAO;QACP,WAAW;QACX,uBAAuB,EAAE,OAAO,CAAC,IAAI;KACtC,CAAC;IAEF,IAAI,OAAO,CAAC,KAAK;QAAE,KAAK,CAAC,wBAAwB,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC;IACnE,IAAI,OAAO,CAAC,WAAW;QAAE,KAAK,CAAC,uBAAuB,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC;IAC9E,IAAI,OAAO,CAAC,WAAW;QAAE,KAAK,CAAC,uBAAuB,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC;IAE9E,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB,CACvC,QAGC,EACD,UAAkB,EAClB,UAAkB;IAElB,IAAI,QAAQ,CAAC,MAAM,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IAEhD,MAAM,SAAS,GAAG,WAAW,CAAC,QAAQ,CAAC,aAAa,IAAI,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC5E,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAE5B,MAAM,KAAK,GAA4B;QACrC,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,YAAY,CAAC,UAAU,EAAE,aAAa,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC;QACzE,SAAS;QACT,mBAAmB,EAAE,UAAU;QAC/B,OAAO,EAAE,CAAC,UAAU,CAAC;QACrB,MAAM,EAAE,SAAS;QACjB,OAAO,EACL,QAAQ,CAAC,MAAM,KAAK,WAAW;YAC7B,CAAC,CAAC,eAAe,CAAC,eAAe;YACjC,CAAC,CAAC,eAAe,CAAC,QAAQ;QAC9B,WAAW,EAAE,6BAA6B,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3D,2BAA2B,EAAE,QAAQ,CAAC,MAAM;KAC7C,CAAC;IAEF,iEAAiE;IACjE,oEAAoE;IACpE,IAAI,QAAQ,CAAC,QAAQ;QAAE,KAAK,CAAC,wBAAwB,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC;IAC3E,IAAI,QAAQ,CAAC,MAAM;QAAE,KAAK,CAAC,sBAAsB,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC;IAErE,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,uBAAuB,CACrC,WAA6C,EAC7C,UAAkB,EAClB,UAAkB,EAClB,eAA+B;IAE/B,MAAM,SAAS,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;IAC3C,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAE5B,MAAM,KAAK,GAA4B;QACrC,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,YAAY,CAAC,UAAU,EAAE,eAAe,CAAC;QAClD,SAAS;QACT,mBAAmB,EAAE,UAAU;QAC/B,OAAO,EAAE,CAAC,UAAU,CAAC;QACrB,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,eAAe,CAAC,aAAa;KACvC,CAAC;IAEF,MAAM,KAAK,GAAG,WAAW,CAAC,eAAe,CAAC,CAAC;IAC3C,IAAI,KAAK;QAAE,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC;IAErC,OAAO,KAAK,CAAC;AACf,CAAC;AAeD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAA6B,EAC7B,UAAkB,EAClB,UAAkB,EAClB,KAAa;IAEb,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAChD,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAE5B,MAAM,EAAE,OAAO,EAAE,GAAG,qBAAqB,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IAElE,MAAM,KAAK,GAA4B;QACrC,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,YAAY,CAAC,UAAU,EAAE,YAAY,KAAK,EAAE,CAAC;QACtD,SAAS;QACT,mBAAmB,EAAE,UAAU;QAC/B,OAAO,EAAE,CAAC,UAAU,CAAC;QACrB,MAAM,EAAE,SAAS;QACjB,OAAO;KACR,CAAC;IAEF,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,KAAK;QAAE,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC;IAErC,gEAAgE;IAChE,+DAA+D;IAC/D,kEAAkE;IAClE,IAAI,MAAM,CAAC,SAAS;QAAE,KAAK,CAAC,uBAAuB,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC;IACxE,IAAI,MAAM,CAAC,OAAO;QAAE,KAAK,CAAC,mBAAmB,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC;IAChE,IAAI,MAAM,CAAC,QAAQ;QAAE,KAAK,CAAC,wBAAwB,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC;IACvE,KAAK,CAAC,8BAA8B,CAAC,GAAG,IAAI,CAAC;IAE7C,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EPC and location identifier construction for EPCIS events.
|
|
3
|
+
*
|
|
4
|
+
* EPCIS events identify objects ("what") and places ("where") with
|
|
5
|
+
* URIs. TracePass already mints GS1 Digital Link URIs for products
|
|
6
|
+
* and validates GLNs for parties — this module turns those into the
|
|
7
|
+
* identifier forms EPCIS events expect.
|
|
8
|
+
*
|
|
9
|
+
* Identifier choices (per EPCIS 2.0, which accepts both URN and
|
|
10
|
+
* Digital Link / constrained-URI forms):
|
|
11
|
+
* - Object identity ("what") — we emit the GS1 Digital Link URI
|
|
12
|
+
* for the product. EPCIS 2.0 explicitly supports Digital Link
|
|
13
|
+
* URIs as EPCs, and it keeps the EPCIS document consistent with
|
|
14
|
+
* the identifier already printed on the passport's QR.
|
|
15
|
+
* - Location identity ("where") — we emit the GS1 SGLN-style
|
|
16
|
+
* Digital Link URI `https://id.gs1.org/414/<gln>` for a party's
|
|
17
|
+
* GLN. `414` is the GS1 Application Identifier for a physical
|
|
18
|
+
* location / party.
|
|
19
|
+
*
|
|
20
|
+
* Pure — no IO. Unit-tested in tests/epcis/identifiers.test.ts.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Build the EPC URI ("what") for a passport. We use the passport's
|
|
24
|
+
* already-minted Digital Link URI verbatim — it is the stable object
|
|
25
|
+
* identifier the rest of the platform (schema.org JSON-LD, tenant
|
|
26
|
+
* export, QR) also uses, so the EPCIS document agrees with them.
|
|
27
|
+
*
|
|
28
|
+
* Returns null when the passport has no Digital Link URI (should not
|
|
29
|
+
* happen for a published passport, but the builder must not throw).
|
|
30
|
+
*/
|
|
31
|
+
export declare function passportEpc(digitalLinkUri: string | undefined | null): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Build a location URI ("where") from a 13-digit GLN. Returns the
|
|
34
|
+
* GS1-canonical `id.gs1.org/414/<gln>` Digital Link form.
|
|
35
|
+
*
|
|
36
|
+
* Returns null when the GLN is absent or fails the mod-10 check —
|
|
37
|
+
* an invalid GLN must not produce a malformed location URI. The
|
|
38
|
+
* caller omits the readPoint/bizLocation rather than emitting a
|
|
39
|
+
* broken one.
|
|
40
|
+
*/
|
|
41
|
+
export declare function locationUri(gln: string | undefined | null): string | null;
|
|
42
|
+
/**
|
|
43
|
+
* Build the `id` object EPCIS uses for a readPoint / bizLocation:
|
|
44
|
+
* `{ id: "<uri>" }`. Returns null when the GLN is missing or invalid
|
|
45
|
+
* so the event builder can cleanly omit the key.
|
|
46
|
+
*/
|
|
47
|
+
export declare function locationRef(gln: string | undefined | null): {
|
|
48
|
+
id: string;
|
|
49
|
+
} | null;
|
|
50
|
+
/**
|
|
51
|
+
* Build an EPCIS `eventID` URI for one of our exported events. EPCIS
|
|
52
|
+
* 2.0 events carry an optional stable `eventID`; making it stable and
|
|
53
|
+
* deterministic means re-exporting the same passport produces the
|
|
54
|
+
* same event IDs (idempotent consumers can dedupe).
|
|
55
|
+
*
|
|
56
|
+
* Shape: a TracePass URN namespacing the passport id + an event-local
|
|
57
|
+
* discriminator (the source collection + its row id, or a synthetic
|
|
58
|
+
* index for inline supplyChainEvents which have no own id).
|
|
59
|
+
*/
|
|
60
|
+
export declare function buildEventId(passportId: string, discriminator: string): string;
|
|
61
|
+
//# sourceMappingURL=identifiers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identifiers.d.ts","sourceRoot":"","sources":["../src/identifiers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAeH;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAKpF;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAIzE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAC7B;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAGvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,GACpB,MAAM,CAGR"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EPC and location identifier construction for EPCIS events.
|
|
3
|
+
*
|
|
4
|
+
* EPCIS events identify objects ("what") and places ("where") with
|
|
5
|
+
* URIs. TracePass already mints GS1 Digital Link URIs for products
|
|
6
|
+
* and validates GLNs for parties — this module turns those into the
|
|
7
|
+
* identifier forms EPCIS events expect.
|
|
8
|
+
*
|
|
9
|
+
* Identifier choices (per EPCIS 2.0, which accepts both URN and
|
|
10
|
+
* Digital Link / constrained-URI forms):
|
|
11
|
+
* - Object identity ("what") — we emit the GS1 Digital Link URI
|
|
12
|
+
* for the product. EPCIS 2.0 explicitly supports Digital Link
|
|
13
|
+
* URIs as EPCs, and it keeps the EPCIS document consistent with
|
|
14
|
+
* the identifier already printed on the passport's QR.
|
|
15
|
+
* - Location identity ("where") — we emit the GS1 SGLN-style
|
|
16
|
+
* Digital Link URI `https://id.gs1.org/414/<gln>` for a party's
|
|
17
|
+
* GLN. `414` is the GS1 Application Identifier for a physical
|
|
18
|
+
* location / party.
|
|
19
|
+
*
|
|
20
|
+
* Pure — no IO. Unit-tested in tests/epcis/identifiers.test.ts.
|
|
21
|
+
*/
|
|
22
|
+
import { validateGln } from "@tracepass/gs1-utils";
|
|
23
|
+
/**
|
|
24
|
+
* GS1 canonical resolver host. Location URIs are minted against
|
|
25
|
+
* `id.gs1.org` (not our resolver) because a GLN is globally owned by
|
|
26
|
+
* the party, not by TracePass — an EPCIS consumer resolving the
|
|
27
|
+
* location should reach GS1's canonical resolver, not ours.
|
|
28
|
+
*/
|
|
29
|
+
const GS1_CANONICAL_HOST = "https://id.gs1.org";
|
|
30
|
+
/** GS1 Application Identifier for a location / party (GLN). */
|
|
31
|
+
const AI_LOCATION = "414";
|
|
32
|
+
/**
|
|
33
|
+
* Build the EPC URI ("what") for a passport. We use the passport's
|
|
34
|
+
* already-minted Digital Link URI verbatim — it is the stable object
|
|
35
|
+
* identifier the rest of the platform (schema.org JSON-LD, tenant
|
|
36
|
+
* export, QR) also uses, so the EPCIS document agrees with them.
|
|
37
|
+
*
|
|
38
|
+
* Returns null when the passport has no Digital Link URI (should not
|
|
39
|
+
* happen for a published passport, but the builder must not throw).
|
|
40
|
+
*/
|
|
41
|
+
export function passportEpc(digitalLinkUri) {
|
|
42
|
+
if (typeof digitalLinkUri !== "string" || digitalLinkUri.length === 0) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return digitalLinkUri;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build a location URI ("where") from a 13-digit GLN. Returns the
|
|
49
|
+
* GS1-canonical `id.gs1.org/414/<gln>` Digital Link form.
|
|
50
|
+
*
|
|
51
|
+
* Returns null when the GLN is absent or fails the mod-10 check —
|
|
52
|
+
* an invalid GLN must not produce a malformed location URI. The
|
|
53
|
+
* caller omits the readPoint/bizLocation rather than emitting a
|
|
54
|
+
* broken one.
|
|
55
|
+
*/
|
|
56
|
+
export function locationUri(gln) {
|
|
57
|
+
if (typeof gln !== "string")
|
|
58
|
+
return null;
|
|
59
|
+
if (!validateGln(gln))
|
|
60
|
+
return null;
|
|
61
|
+
return `${GS1_CANONICAL_HOST}/${AI_LOCATION}/${gln}`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Build the `id` object EPCIS uses for a readPoint / bizLocation:
|
|
65
|
+
* `{ id: "<uri>" }`. Returns null when the GLN is missing or invalid
|
|
66
|
+
* so the event builder can cleanly omit the key.
|
|
67
|
+
*/
|
|
68
|
+
export function locationRef(gln) {
|
|
69
|
+
const uri = locationUri(gln);
|
|
70
|
+
return uri ? { id: uri } : null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Build an EPCIS `eventID` URI for one of our exported events. EPCIS
|
|
74
|
+
* 2.0 events carry an optional stable `eventID`; making it stable and
|
|
75
|
+
* deterministic means re-exporting the same passport produces the
|
|
76
|
+
* same event IDs (idempotent consumers can dedupe).
|
|
77
|
+
*
|
|
78
|
+
* Shape: a TracePass URN namespacing the passport id + an event-local
|
|
79
|
+
* discriminator (the source collection + its row id, or a synthetic
|
|
80
|
+
* index for inline supplyChainEvents which have no own id).
|
|
81
|
+
*/
|
|
82
|
+
export function buildEventId(passportId, discriminator) {
|
|
83
|
+
const safe = discriminator.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
84
|
+
return `urn:tracepass:epcis:${passportId}:${safe}`;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=identifiers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identifiers.js","sourceRoot":"","sources":["../src/identifiers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,kBAAkB,GAAG,oBAAoB,CAAC;AAEhD,+DAA+D;AAC/D,MAAM,WAAW,GAAG,KAAK,CAAC;AAE1B;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,cAAyC;IACnE,IAAI,OAAO,cAAc,KAAK,QAAQ,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,GAA8B;IACxD,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,GAAG,kBAAkB,IAAI,WAAW,IAAI,GAAG,EAAE,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CACzB,GAA8B;IAE9B,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAC7B,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAClC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAC1B,UAAkB,EAClB,aAAqB;IAErB,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;IAC5D,OAAO,uBAAuB,UAAU,IAAI,IAAI,EAAE,CAAC;AACrD,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map Digital Product Passport data to GS1 EPCIS 2.0 events.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions over plain objects — no network, no database. Includes a CBV
|
|
5
|
+
* 2.0 (Core Business Vocabulary) mapping, extended with vocabulary URIs for
|
|
6
|
+
* steel production steps that standard CBV does not cover, following the
|
|
7
|
+
* GS1-sanctioned extension pattern.
|
|
8
|
+
*/
|
|
9
|
+
export { toEventTime, buildTransformationEvent, buildServiceObjectEvent, buildOwnershipObjectEvent, buildCommissioningEvent, buildSupplierObjectEvent, } from "./event-builders.js";
|
|
10
|
+
export type { SupplyChainEventInput, SupplierReportedEvent, } from "./event-builders.js";
|
|
11
|
+
export { CBV_BIZSTEP_URI, CBV_DISPOSITION_URI, TRACEPASS_BIZSTEP_URI, normalizeStepToken, mapEventTypeToBizStep, mapServiceEventType, mapOwnershipReasonDisposition, } from "./cbv.js";
|
|
12
|
+
export type { BizStepMapping } from "./cbv.js";
|
|
13
|
+
export { validateEpcisDocument } from "./validate.js";
|
|
14
|
+
export type { EpcisValidationIssue, EpcisValidationResult, } from "./validate.js";
|
|
15
|
+
export { passportEpc, locationUri, locationRef, buildEventId } from "./identifiers.js";
|
|
16
|
+
export { EPCIS_CONTEXT_URL, TRACEPASS_VOC_BASE, EPCIS_JSONLD_CONTEXT, } from "./context.js";
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,WAAW,EACX,wBAAwB,EACxB,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,6BAA6B,GAC9B,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EACV,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEvF,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map Digital Product Passport data to GS1 EPCIS 2.0 events.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions over plain objects — no network, no database. Includes a CBV
|
|
5
|
+
* 2.0 (Core Business Vocabulary) mapping, extended with vocabulary URIs for
|
|
6
|
+
* steel production steps that standard CBV does not cover, following the
|
|
7
|
+
* GS1-sanctioned extension pattern.
|
|
8
|
+
*/
|
|
9
|
+
export { toEventTime, buildTransformationEvent, buildServiceObjectEvent, buildOwnershipObjectEvent, buildCommissioningEvent, buildSupplierObjectEvent, } from "./event-builders.js";
|
|
10
|
+
export { CBV_BIZSTEP_URI, CBV_DISPOSITION_URI, TRACEPASS_BIZSTEP_URI, normalizeStepToken, mapEventTypeToBizStep, mapServiceEventType, mapOwnershipReasonDisposition, } from "./cbv.js";
|
|
11
|
+
export { validateEpcisDocument } from "./validate.js";
|
|
12
|
+
export { passportEpc, locationUri, locationRef, buildEventId } from "./identifiers.js";
|
|
13
|
+
export { EPCIS_CONTEXT_URL, TRACEPASS_VOC_BASE, EPCIS_JSONLD_CONTEXT, } from "./context.js";
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,WAAW,EACX,wBAAwB,EACxB,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAM7B,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,6BAA6B,GAC9B,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAMtD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEvF,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,cAAc,CAAC"}
|