diff --git a/package-lock.json b/package-lock.json
index 6691fe0..f36ecf6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2935,6 +2935,10 @@
"resolved": "packages/plugin-dkg-publisher",
"link": true
},
+ "node_modules/@dkg/plugin-epcis": {
+ "resolved": "packages/plugin-epcis",
+ "link": true
+ },
"node_modules/@dkg/plugin-example": {
"resolved": "packages/plugin-example",
"link": true
@@ -9195,6 +9199,45 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ajv-formats": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+ "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ajv-formats/node_modules/ajv": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
+ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
"node_modules/an-array": {
"version": "1.0.0",
"license": "MIT",
@@ -14718,6 +14761,22 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
"node_modules/fast-xml-parser": {
"version": "4.5.3",
"funding": [
@@ -28248,6 +28307,43 @@
"uuid": "dist/bin/uuid"
}
},
+ "packages/plugin-epcis": {
+ "name": "@dkg/plugin-epcis",
+ "version": "0.0.1",
+ "dependencies": {
+ "@dkg/plugin-swagger": "^0.0.2",
+ "@dkg/plugins": "^0.0.2",
+ "ajv": "^8.17.0",
+ "ajv-formats": "^3.0.0"
+ },
+ "devDependencies": {
+ "@dkg/eslint-config": "*",
+ "@dkg/typescript-config": "*",
+ "tsup": "^8.5.0"
+ }
+ },
+ "packages/plugin-epcis/node_modules/ajv": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
+ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "packages/plugin-epcis/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
"packages/plugin-example": {
"name": "@dkg/plugin-example",
"version": "0.0.3",
diff --git a/packages/plugin-epcis/docs/EPCIS-Integration-Guide.md b/packages/plugin-epcis/docs/EPCIS-Integration-Guide.md
new file mode 100644
index 0000000..63aef1c
--- /dev/null
+++ b/packages/plugin-epcis/docs/EPCIS-Integration-Guide.md
@@ -0,0 +1,612 @@
+# π EPCIS-DKG Integration Guide
+
+## Table of Contents
+
+1. [Overview & Architecture](#1-overview--architecture)
+2. [Quick Start](#2-quick-start)
+3. [EPCIS Event Types Explained](#3-epcis-event-types-explained)
+4. [API Reference](#4-api-reference)
+5. [Data Flow & DKG Publishing](#5-data-flow--dkg-publishing)
+6. [Query Examples](#6-query-examples)
+7. [Troubleshooting](#7-troubleshooting)
+
+---
+
+## 1. Overview & Architecture
+
+### What This System Does
+
+This integration bridges **GS1 EPCIS 2.0** (Electronic Product Code Information Services) with the **OriginTrail Decentralized Knowledge Graph (DKG)**. It allows you to:
+
+- **Capture** supply chain events in standard EPCIS format
+- **Publish** them as tamper-proof Knowledge Assets on the DKG
+- **Query** events using semantic filters across the distributed network
+
+### Why Use DKG for EPCIS?
+
+| Traditional EPCIS | EPCIS + DKG |
+|-------------------|-------------|
+| Centralized database | Decentralized, permissionless network |
+| Single point of failure | Replicated across multiple nodes |
+| Trust the provider | Cryptographically verifiable |
+| Siloed data | Interlinked Knowledge Graph |
+| Company-controlled | Owned via blockchain (UAL) |
+
+### Architecture Overview
+
+```
+βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β Your Application β
+βββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββ
+ β HTTP POST /epcis/capture
+ βΌ
+βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β EPCIS Plugin β
+β βββββββββββββββββββ ββββββββββββββββββββ β
+β β Validation βββββΆβ JSON-LD Transform β β
+β β (GS1 Schema) β β (EPCIS Context) β β
+β βββββββββββββββββββ ββββββββββ¬ββββββββββ β
+ββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ
+ β
+ βΌ
+βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β DKG Publisher Plugin β
+β βββββββββββββββ βββββββββββββββ ββββββββββββββββββββ β
+β β Asset Queue βββββΆβ BullMQ βββββΆβ DKG Network β β
+β β (MySQL) β β Workers β β (via dkg.js) β β
+β βββββββββββββββ βββββββββββββββ ββββββββββ¬ββββββββββ β
+βββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββ
+ β
+ βΌ
+βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β OriginTrail Decentralized Knowledge Graph β
+β β
+β Knowledge Asset (UAL: did:dkg:otp/0x.../123456) β
+β βββ EPCIS Event Data (RDF/JSON-LD) β
+β βββ Cryptographic Proof (Blockchain anchored) β
+β βββ Ownership (NFT) β
+βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+```
+
+---
+
+## 2. Quick Start
+
+### Prerequisites
+
+- DKG Node running (with EPCIS and Publisher plugins enabled)
+- Access to the API endpoint (default: `http://localhost:9200`)
+
+### Step 1: Send Your First EPCIS Event
+
+```bash
+curl -X POST http://localhost:9200/epcis/capture \
+ -H "Content-Type: application/json" \
+ -d '{
+ "@context": {
+ "@vocab": "https://gs1.github.io/EPCIS/",
+ "epcis": "https://gs1.github.io/EPCIS/",
+ "cbv": "https://ref.gs1.org/cbv/",
+ "type": "@type",
+ "id": "@id",
+ "epcisBody": "epcis:epcisBody",
+ "eventList": "epcis:eventList"
+ },
+ "type": "EPCISDocument",
+ "schemaVersion": "2.0",
+ "creationDate": "2024-01-01T00:00:00Z",
+ "epcisBody": {
+ "eventList": [{
+ "type": "ObjectEvent",
+ "eventTime": "2024-01-01T00:00:00.000Z",
+ "eventTimeZoneOffset": "+00:00",
+ "epcList": ["urn:epc:id:sgtin:0614141.107346.2017"],
+ "action": "OBSERVE",
+ "bizStep": "https://ref.gs1.org/cbv/BizStep-receiving",
+ "disposition": "https://ref.gs1.org/cbv/Disp-in_progress",
+ "readPoint": {"id": "urn:epc:id:sgln:0614141.00001.0"},
+ "bizLocation": {"id": "urn:epc:id:sgln:0614141.00001.0"}
+ }]
+ }
+ }'
+```
+
+### Step 2: Check Status
+
+The response includes a `captureID`. Use it to check publishing status:
+
+```bash
+curl http://localhost:9200/epcis/capture/123
+```
+
+Possible statuses:
+
+- `queued` - Waiting to be published
+- `processing` - Currently being published to DKG
+- `published` - Successfully published (includes UAL)
+- `failed` - Publishing failed (includes error message)
+
+### Step 3: Query Events
+
+Once published, query events from the DKG:
+
+```bash
+# By EPC
+curl "http://localhost:9200/epcis/events?epc=urn:epc:id:sgtin:0614141.107346.2017"
+
+# By time range
+curl "http://localhost:9200/epcis/events?from=2024-01-01T00:00:00Z&to=2024-12-31T23:59:59Z"
+
+# By business step
+curl "http://localhost:9200/epcis/events?bizStep=inspecting"
+```
+
+> π‘ **Interactive Documentation**: For detailed request/response schemas and to test the API live, visit the Swagger UI at `/swagger`
+
+---
+
+## 3. EPCIS Event Types Explained
+
+### What is EPCIS?
+
+EPCIS (Electronic Product Code Information Services) is a GS1 standard for capturing and sharing supply chain events. It answers the "what, where, when, and why" of products moving through a supply chain.
+
+### The Five Event Types
+
+| Event Type | Purpose | Example Use Case |
+|------------|---------|------------------|
+| **ObjectEvent** | Track individual items | Product inspection, quality check |
+| **AggregationEvent** | Items grouped/ungrouped | Packing items into a case |
+| **TransactionEvent** | Business transactions | Purchase order, invoice |
+| **TransformationEvent** | InputβOutput conversion | Manufacturing, assembly |
+| **AssociationEvent** | Link assets together | Sensor attached to container |
+
+### Action Types
+
+- **ADD** - New item introduced (e.g., manufactured, received)
+- **OBSERVE** - Item observed without state change (e.g., scanned at checkpoint)
+- **DELETE** - Item removed from tracking (e.g., sold, destroyed)
+
+### Business Steps (bizStep)
+
+Common GS1 CBV (Core Business Vocabulary) business steps:
+
+| bizStep | Description |
+|---------|-------------|
+| `receiving` | Goods received at a location |
+| `shipping` | Goods shipped from a location |
+| `inspecting` | Quality inspection performed |
+| `assembling` | Components assembled into product |
+| `packing` | Items packed for shipment |
+| `commissioning` | New serial assigned (e.g., manufacturing) |
+| `decommissioning` | Serial number retired |
+
+> **Shorthand supported**: You can use just `"assembling"` instead of the full URI `"https://ref.gs1.org/cbv/BizStep-assembling"`
+
+---
+
+## 4. API Reference
+
+### Understanding the JSON-LD Context
+
+EPCIS documents use JSON-LD (Linked Data) format. The `@context` object maps terms to URIs for proper semantic interpretation:
+
+```json
+{
+ "@context": {
+ "@vocab": "https://gs1.github.io/EPCIS/",
+ "epcis": "https://gs1.github.io/EPCIS/",
+ "cbv": "https://ref.gs1.org/cbv/",
+ "type": "@type",
+ "id": "@id",
+ "epcisBody": "epcis:epcisBody",
+ "eventList": "epcis:eventList"
+ }
+}
+```
+
+| Key | Purpose |
+|-----|---------|
+| `@vocab` | Default namespace for unmapped terms |
+| `epcis` | EPCIS vocabulary namespace |
+| `cbv` | GS1 Core Business Vocabulary |
+| `type` / `id` | Maps to JSON-LD keywords |
+| `epcisBody`, `eventList` | Explicit term mappings |
+
+> **Note**: You can also use the shorthand `["https://ref.gs1.org/standards/epcis/2.0.0/epcis-context.jsonld"]` but the explicit context above gives you more control and is properly tested.
+
+---
+
+### POST `/epcis/capture`
+
+Accept an EPCIS Document and queue it for publishing to DKG.
+
+**Request Body**: EPCISDocument (JSON-LD)
+
+```json
+{
+ "@context": {
+ "@vocab": "https://gs1.github.io/EPCIS/",
+ "epcis": "https://gs1.github.io/EPCIS/",
+ "cbv": "https://ref.gs1.org/cbv/",
+ "type": "@type",
+ "id": "@id",
+ "epcisBody": "epcis:epcisBody",
+ "eventList": "epcis:eventList"
+ },
+ "type": "EPCISDocument",
+ "schemaVersion": "2.0",
+ "creationDate": "2024-01-01T00:00:00Z",
+ "epcisBody": {
+ "eventList": [/* array of events */]
+ }
+}
+```
+
+**Response** (HTTP 202 Accepted):
+
+```json
+{
+ "status": "202",
+ "receivedAt": "2024-01-01T00:00:01.123Z",
+ "captureID": "456",
+ "eventCount": 1
+}
+```
+
+---
+
+### GET `/epcis/capture/:captureID`
+
+Check the status of a previously submitted capture.
+
+**Response**:
+
+```json
+{
+ "status": "published",
+ "captureID": "456",
+ "UAL": "did:dkg:otp/0x1234.../789",
+ "publishedAt": "2024-01-01T00:01:23.456Z"
+}
+```
+
+| Field | Description |
+|-------|-------------|
+| `status` | `queued` / `processing` / `published` / `failed` |
+| `UAL` | Uniform Asset Locator (only when published) |
+| `error` | Error message (only when failed) |
+
+---
+
+### GET `/epcis/events`
+
+Query EPCIS events from the DKG.
+
+**Query Parameters**:
+
+| Parameter | Type | Description | Example |
+|-----------|------|-------------|---------|
+| `epc` | string | Filter by EPC identifier | `urn:epc:id:sgtin:0614141.107346.2017` |
+| `from` | string (ISO 8601) | Start of time range | `2024-01-01T00:00:00Z` |
+| `to` | string (ISO 8601) | End of time range | `2024-12-31T23:59:59Z` |
+| `bizStep` | string | Filter by business step | `assembling` or full URI |
+| `bizLocation` | string | Filter by location | `urn:epc:id:sgln:0614141.00001.0` |
+| `ual` | string | Get specific event by UAL | `did:dkg:otp/...` |
+
+**Response**:
+
+```json
+{
+ "success": true,
+ "query": "SELECT ...",
+ "results": [/* array of matching events */],
+ "count": 5
+}
+```
+
+---
+
+## 5. Data Flow & DKG Publishing
+
+### Publishing Pipeline
+
+```
+1. CAPTURE REQUEST
+ βββΆ Validate against GS1 EPCIS 2.0 JSON Schema
+
+2. QUEUE (Tier 1 - MySQL)
+ βββΆ Asset registered with status "queued"
+ βββΆ Assigned priority and metadata
+
+3. POLLING (every 2 seconds)
+ βββΆ QueuePoller checks for available wallets
+ βββΆ Moves jobs to BullMQ (Tier 2 - Redis)
+
+4. PROCESSING (BullMQ Workers)
+ βββΆ Worker acquires wallet lock
+ βββΆ Wraps content as JSON-LD Knowledge Asset
+ βββΆ Calls dkg.js asset.create()
+
+5. DKG NETWORK
+ βββΆ Content replicated to DKG nodes
+ βββΆ Cryptographic proof anchored to blockchain
+ βββΆ UAL (NFT) minted for ownership
+
+6. COMPLETION
+ βββΆ Asset status updated to "published"
+ βββΆ UAL stored for future queries
+```
+
+### What is a UAL?
+
+A **Uniform Asset Locator** is a globally unique identifier for your Knowledge Asset:
+
+```
+did:dkg:otp/0x1234567890abcdef/123456
+ββββ¬βββ ββ¬β ββββββββββ¬ββββββββ ββββ¬βββ
+ β β β β
+ β β β βββ Asset ID
+ β β βββ Contract address
+ β βββ Blockchain (otp = OriginTrail Parachain)
+ βββ DID method
+```
+
+With a UAL, you can:
+
+- **Verify** the content hasn't been tampered with
+- **Prove** ownership on the blockchain
+- **Query** the event data from any DKG node
+- **Link** to other Knowledge Assets
+
+---
+
+## 6. Query Examples
+
+### Find All Events for a Product
+
+```bash
+curl "http://localhost:9200/epcis/events?epc=urn:epc:id:sgtin:0614141.107346.2017"
+```
+
+### Find Assembly Events at a Specific Location
+
+```bash
+curl "http://localhost:9200/epcis/events?bizStep=assembling&bizLocation=urn:epc:id:sgln:0614141.00001.0"
+```
+
+### Get Full Event Details by UAL
+
+```bash
+curl "http://localhost:9200/epcis/events?ual=did:dkg:otp/0x1234.../789"
+```
+
+### Time Range Query
+
+```bash
+curl "http://localhost:9200/epcis/events?from=2024-01-01T00:00:00Z&to=2024-01-31T23:59:59Z"
+```
+
+### SPARQL Direct Query
+
+Under the hood, queries are translated to SPARQL. Example generated query:
+
+```sparql
+PREFIX epcis:
+PREFIX schema:
+
+SELECT ?ual ?eventType ?eventTime ?epc ?bizStep ?disposition ?readPoint ?bizLocation
+WHERE {
+ GRAPH ?ual {
+ ?event a ?eventType .
+ ?event epcis:epcList "urn:epc:id:sgtin:0614141.107346.2017" .
+ OPTIONAL { ?event epcis:bizStep ?bizStep . }
+ OPTIONAL { ?event epcis:eventTime ?eventTime . }
+ }
+ FILTER(STRSTARTS(STR(?eventType), "https://gs1.github.io/EPCIS/"))
+}
+ORDER BY DESC(?eventTime)
+LIMIT 100
+```
+
+---
+
+## 7. Troubleshooting
+
+### Common Errors
+
+| Error | Cause | Solution |
+|-------|-------|----------|
+| `Invalid EPCISDocument` | Schema validation failed | Check your JSON matches EPCIS 2.0 spec |
+| `Invalid captureID format` | Non-numeric captureID | Use the numeric ID from capture response |
+| `Capture not found` | Unknown captureID | Verify the ID; it may have been deleted |
+| `Publishing failed` | DKG network error | Check wallet balance, node connectivity |
+| `No available wallets` | All wallets are busy | Wait or add more wallets to the pool |
+
+### Checking System Health
+
+**Publisher Dashboard**: Visit `/admin/queues` to see:
+
+- Active jobs
+- Waiting queue
+- Failed jobs with error details
+- Worker status
+
+**API Health**: The Swagger UI at `/swagger` shows all available endpoints and their status.
+
+### Validation Errors
+
+The system validates against the official GS1 EPCIS 2.0 JSON Schema. Common issues:
+
+1. **Missing `@context`** - Must include EPCIS context
+2. **Invalid `eventTime`** - Must be ISO 8601 format
+3. **Wrong `type`** - Must be exactly `"EPCISDocument"` (case-sensitive)
+4. **Invalid `bizStep`** - Must be valid CBV URI or shorthand
+
+### Getting Help
+
+- **Swagger UI**: `http://your-server/swagger` - Interactive API docs
+- **OpenAPI Spec**: `http://your-server/openapi` - Raw JSON spec
+- **Logs**: Check server logs for detailed error messages
+
+---
+
+## Appendix: Sample EPCIS Documents
+
+### Object Event (Receiving Goods)
+
+```json
+{
+ "@context": {
+ "@vocab": "https://gs1.github.io/EPCIS/",
+ "epcis": "https://gs1.github.io/EPCIS/",
+ "cbv": "https://ref.gs1.org/cbv/",
+ "type": "@type",
+ "id": "@id",
+ "epcisBody": "epcis:epcisBody",
+ "eventList": "epcis:eventList"
+ },
+ "type": "EPCISDocument",
+ "schemaVersion": "2.0",
+ "creationDate": "2024-01-01T00:00:00Z",
+ "epcisBody": {
+ "eventList": [{
+ "type": "ObjectEvent",
+ "eventTime": "2024-01-01T00:00:00.000Z",
+ "eventTimeZoneOffset": "+00:00",
+ "epcList": ["urn:epc:id:sgtin:0614141.107346.2017"],
+ "action": "OBSERVE",
+ "bizStep": "https://ref.gs1.org/cbv/BizStep-receiving",
+ "disposition": "https://ref.gs1.org/cbv/Disp-in_progress",
+ "readPoint": {"id": "urn:epc:id:sgln:0614141.00001.0"},
+ "bizLocation": {"id": "urn:epc:id:sgln:0614141.00001.0"},
+ "bizTransactionList": [
+ {
+ "type": "urn:epcglobal:cbv:btt:po",
+ "bizTransaction": "urn:epc:id:gdti:0614141.00001.1234"
+ }
+ ]
+ }]
+ }
+}
+```
+
+### Transformation Event (Assembly)
+
+```json
+{
+ "@context": {
+ "@vocab": "https://gs1.github.io/EPCIS/",
+ "epcis": "https://gs1.github.io/EPCIS/",
+ "cbv": "https://ref.gs1.org/cbv/",
+ "type": "@type",
+ "id": "@id",
+ "epcisBody": "epcis:epcisBody",
+ "eventList": "epcis:eventList"
+ },
+ "type": "EPCISDocument",
+ "schemaVersion": "2.0",
+ "creationDate": "2024-01-01T00:00:00Z",
+ "epcisBody": {
+ "eventList": [{
+ "type": "TransformationEvent",
+ "eventTime": "2024-01-01T12:00:00.000Z",
+ "eventTimeZoneOffset": "+00:00",
+ "inputEPCList": [
+ "urn:epc:id:sgtin:0614141.107346.001",
+ "urn:epc:id:sgtin:0614141.107346.002"
+ ],
+ "outputEPCList": [
+ "urn:epc:id:sgtin:0614141.107347.001"
+ ],
+ "bizStep": "https://ref.gs1.org/cbv/BizStep-assembling",
+ "bizLocation": {"id": "urn:epc:id:sgln:0614141.00002.0"}
+ }]
+ }
+}
+```
+
+### Aggregation Event (Packing)
+
+```json
+{
+ "@context": {
+ "@vocab": "https://gs1.github.io/EPCIS/",
+ "epcis": "https://gs1.github.io/EPCIS/",
+ "cbv": "https://ref.gs1.org/cbv/",
+ "type": "@type",
+ "id": "@id",
+ "epcisBody": "epcis:epcisBody",
+ "eventList": "epcis:eventList"
+ },
+ "type": "EPCISDocument",
+ "schemaVersion": "2.0",
+ "creationDate": "2024-01-01T00:00:00Z",
+ "epcisBody": {
+ "eventList": [{
+ "type": "AggregationEvent",
+ "eventTime": "2024-01-01T14:00:00.000Z",
+ "eventTimeZoneOffset": "+00:00",
+ "parentID": "urn:epc:id:sscc:0614141.0000000001",
+ "childEPCs": [
+ "urn:epc:id:sgtin:0614141.107346.001",
+ "urn:epc:id:sgtin:0614141.107346.002",
+ "urn:epc:id:sgtin:0614141.107346.003"
+ ],
+ "action": "ADD",
+ "bizStep": "https://ref.gs1.org/cbv/BizStep-packing",
+ "bizLocation": {"id": "urn:epc:id:sgln:0614141.00001.0"}
+ }]
+ }
+}
+```
+
+### Object Event with Sensor Data
+
+```json
+{
+ "@context": {
+ "@vocab": "https://gs1.github.io/EPCIS/",
+ "epcis": "https://gs1.github.io/EPCIS/",
+ "cbv": "https://ref.gs1.org/cbv/",
+ "type": "@type",
+ "id": "@id",
+ "epcisBody": "epcis:epcisBody",
+ "eventList": "epcis:eventList"
+ },
+ "type": "EPCISDocument",
+ "schemaVersion": "2.0",
+ "creationDate": "2024-01-01T00:00:00Z",
+ "epcisBody": {
+ "eventList": [{
+ "type": "ObjectEvent",
+ "eventTime": "2024-01-01T08:00:00.000Z",
+ "eventTimeZoneOffset": "+00:00",
+ "epcList": ["urn:epc:id:sgtin:0614141.107346.2017"],
+ "action": "OBSERVE",
+ "bizStep": "https://ref.gs1.org/cbv/BizStep-inspecting",
+ "disposition": "https://ref.gs1.org/cbv/Disp-conformant",
+ "readPoint": {"id": "urn:epc:id:sgln:0614141.00001.0"},
+ "bizLocation": {"id": "urn:epc:id:sgln:0614141.00001.0"},
+ "sensorElementList": [
+ {
+ "sensorReport": [
+ {
+ "type": "https://gs1.org/voc/MeasurementType-Temperature",
+ "time": "2024-01-01T08:00:00.000Z",
+ "value": 23.5,
+ "uom": "CEL"
+ }
+ ]
+ }
+ ]
+ }]
+ }
+}
+```
+
+---
+
+*Last updated: January 2026*
+*For API details, see the interactive [Swagger documentation](/swagger)*
+
diff --git a/packages/plugin-epcis/eslint.config.mjs b/packages/plugin-epcis/eslint.config.mjs
new file mode 100644
index 0000000..59c87c1
--- /dev/null
+++ b/packages/plugin-epcis/eslint.config.mjs
@@ -0,0 +1,4 @@
+import { config } from "@dkg/eslint-config/base";
+
+/** @type {import("eslint").Linter.Config} */
+export default config;
diff --git a/packages/plugin-epcis/package.json b/packages/plugin-epcis/package.json
new file mode 100644
index 0000000..8b9abc8
--- /dev/null
+++ b/packages/plugin-epcis/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "@dkg/plugin-epcis",
+ "version": "0.0.1",
+ "description": "",
+ "main": "./dist/index.js",
+ "module": "./dist/index.mjs",
+ "types": "./dist/index.d.ts",
+ "scripts": {
+ "dev": "tsup src/*.ts --format cjs,esm --dts --watch",
+ "build": "tsup src/*.ts --format cjs,esm --dts",
+ "check-types": "tsc --noEmit",
+ "lint": "eslint . --max-warnings 0",
+ "test": "mocha --loader ../../node_modules/tsx/dist/loader.mjs 'tests/**/*.spec.ts'"
+ },
+ "dependencies": {
+ "@dkg/plugin-swagger": "^0.0.2",
+ "@dkg/plugins": "^0.0.2",
+ "ajv": "^8.17.0",
+ "ajv-formats": "^3.0.0"
+ },
+ "devDependencies": {
+ "@dkg/eslint-config": "*",
+ "@dkg/typescript-config": "*",
+ "tsup": "^8.5.0"
+ }
+}
diff --git a/packages/plugin-epcis/src/index.ts b/packages/plugin-epcis/src/index.ts
new file mode 100644
index 0000000..8c41c20
--- /dev/null
+++ b/packages/plugin-epcis/src/index.ts
@@ -0,0 +1,436 @@
+import { defineDkgPlugin } from "@dkg/plugins";
+import { openAPIRoute, z } from "@dkg/plugin-swagger";
+import { EpcisValidationService } from "./services/EPCISValidationService";
+import { EpcisQueryService } from "./services/EPCISQueryService";
+import type { CaptureResponse } from "./model/types";
+
+// Timeout for internal publisher requests (30s for POST, 5s for GET)
+const PUBLISHER_POST_TIMEOUT_MS = 10000;
+const PUBLISHER_GET_TIMEOUT_MS = 5000;
+
+// Helper function to send JSON-LD to publisher
+async function sendToPublisher(
+ jsonLd: any,
+ metadata?: { source?: string; sourceId?: string },
+ publishOptions?: {
+ privacy?: "private" | "public";
+ epochs?: number;
+ }
+): Promise<{ id: number; status: string; attemptCount: number }> {
+ const publisherUrl = process.env.PUBLISHER_URL || "http://localhost:9200";
+
+ try {
+ const response = await fetch(`${publisherUrl}/api/dkg/assets`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ content: jsonLd,
+ metadata: metadata || { source: "EPCIS" },
+ publishOptions: {
+ privacy: publishOptions?.privacy ?? "private",
+ epochs: publishOptions?.epochs ?? 12,
+ },
+ }),
+ signal: AbortSignal.timeout(PUBLISHER_POST_TIMEOUT_MS),
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.error || "Publisher request failed");
+ }
+
+ return response.json();
+ } catch (error: any) {
+ if (error.name === "TimeoutError") {
+ throw new Error("Publisher request timed out");
+ }
+ throw error;
+ }
+}
+
+export default defineDkgPlugin((ctx, mcp, api) => {
+
+ const validationService = new EpcisValidationService();
+ const queryService = new EpcisQueryService();
+
+ console.log("π EPCIS Plugin loaded");
+
+ // MCP Tool: Query EPCIS events from DKG
+ mcp.registerTool(
+ "epcis-query",
+ {
+ title: "Query EPCIS Events",
+ description:
+ "Query EPCIS supply chain events from the OriginTrail DKG. " +
+ "Can filter by EPC (product identifier), from date to date, business step, or location. " +
+ "Use fullTrace=true to search across all event types (transformations, aggregations) for complete supply chain traceability.",
+ inputSchema: {
+ epc: z.string().optional().describe("EPC identifier (e.g., urn:epc:id:sgtin:0614141.107346.2017)"),
+ from: z.string().optional().describe("Query events from this date onwards, requires it to follow ISO 8601 format (e.g., 2024-01-01T00:00:00Z)"),
+ to: z.string().optional().describe("Query events up to this date, requires it to follow ISO 8601 format (e.g., 2024-01-01T00:00:00Z)"),
+ bizStep: z.string().optional().describe("Business step (e.g., 'receiving', 'shipping', 'assembling')"),
+ bizLocation: z.string().optional().describe("Business location URI"),
+ fullTrace: z.boolean().optional().describe("If true, search all EPC fields for full traceability"),
+ },
+ },
+ async (input) => {
+ try {
+ const sparqlQuery = queryService.buildQuery({
+ epc: input.epc,
+ from: input.from,
+ to: input.to,
+ bizStep: input.bizStep,
+ bizLocation: input.bizLocation,
+ fullTrace: input.fullTrace,
+ });
+
+ const results = await ctx.dkg.graph.query(sparqlQuery, "SELECT");
+
+ const summary = results?.length
+ ? `Found ${results.length} EPCIS event(s)`
+ : "No events found matching the criteria";
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify({
+ summary,
+ count: results?.data.length || 0,
+ events: results || [],
+ //query: sparqlQuery,
+ }, null, 2)
+ }
+ ],
+ };
+ } catch (error: any) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify({
+ error: "Query failed",
+ message: error.message,
+ }, null, 2)
+ }
+ ],
+ isError: true,
+ };
+ }
+ }
+ );
+
+ // MCP Tool: Track item journey (full traceability)
+ mcp.registerTool(
+ "epcis-track-item",
+ {
+ title: "Track Item Journey",
+ description:
+ "Track a single item's complete journey through the supply chain. " +
+ "Finds all events where this EPC appears - as observed item, transformation input/output, or in aggregations. " +
+ "Returns events in chronological order showing the item's full lifecycle.",
+ inputSchema: {
+ epc: z.string().describe("The EPC to track (e.g., urn:epc:id:sgtin:0614141.107346.2017)"),
+ },
+ },
+ async (input) => {
+ try {
+ const sparqlQuery = queryService.buildQuery({
+ epc: input.epc,
+ fullTrace: true, // Always use full traceability for item tracking
+ });
+
+ const results = await ctx.dkg.graph.query(sparqlQuery, "SELECT");
+
+ const eventCount = results?.length || 0;
+ let summary = `Tracking: ${input.epc}\n`;
+ summary += `Found ${eventCount} event(s) in the supply chain.\n\n`;
+
+ if (eventCount > 0) {
+ summary += "Journey Timeline:\n";
+ results.forEach((event: any, idx: number) => {
+ const time = event.eventTime || "Unknown time";
+ const step = event.bizStep?.split("-").pop() || event.eventType?.split("/").pop() || "Unknown";
+ const location = event.bizLocation || event.readPoint || "Unknown location";
+ summary += `${idx + 1}. [${time}] ${step} @ ${location}\n`;
+ });
+ }
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify({
+ summary,
+ epc: input.epc,
+ eventCount,
+ events: results || [],
+ }, null, 2)
+ }
+ ],
+ };
+ } catch (error: any) {
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify({
+ error: "Tracking failed",
+ message: error.message,
+ }, null, 2)
+ }
+ ],
+ isError: true,
+ };
+ }
+ }
+ );
+
+ // POST /epcis/capture - Accept EPCISDocument and queue for publishing
+ api.post(
+ "/epcis/capture",
+ openAPIRoute(
+ {
+ tag: "EPCIS",
+ summary: "Capture EPCIS Document",
+ description: "Accept an EPCISDocument and queue it for publishing to DKG",
+ body: z.object({
+ epcisDocument: z.object({}).passthrough().openapi({
+ description: "The EPCISDocument (JSON-LD)",
+ }),
+ publishOptions: z.object({
+ privacy: z.enum(["private", "public"]).optional().openapi({
+ description: "Asset visibility (default: private)",
+ }),
+ epochs: z.number().min(1).optional().openapi({
+ description: "Number of epochs to publish for (default: 12)",
+ }),
+ }).optional().openapi({
+ description: "Publishing options (all optional with sensible defaults)",
+ }),
+ }),
+ response: {
+ description: "Capture accepted",
+ schema: z.object({
+ status: z.string(),
+ receivedAt: z.string(),
+ captureID: z.string(),
+ eventCount: z.number(),
+ }),
+ },
+ },
+ async (req, res) => {
+ try {
+ const { epcisDocument, publishOptions } = req.body;
+
+ // Validate the EPCIS document
+ const validation = validationService.validate(epcisDocument);
+
+ if (!validation.valid) {
+ return res.status(400).json({
+ error: "Invalid EPCISDocument",
+ details: validation.errors,
+ } as any);
+ }
+
+ // Send to publisher with user-provided options (or defaults)
+ const result = await sendToPublisher(
+ epcisDocument,
+ {
+ source: "EPCIS",
+ sourceId: `epcis-${Date.now()}`,
+ },
+ publishOptions
+ );
+
+ // Return capture response
+ const response: CaptureResponse = {
+ status: "202",
+ receivedAt: new Date().toISOString(),
+ captureID: String(result.id),
+ eventCount: validation.eventCount || 0,
+ };
+
+ res.status(202).json(response);
+ } catch (error: any) {
+ console.error("[EPCIS Capture] Error:", error);
+ res.status(500).json({
+ error: "Failed to process capture",
+ //message: error.message,
+ } as any);
+ }
+ }
+ )
+ );
+
+ // GET /epcis/capture/:captureID - Check capture status
+ api.get(
+ "/epcis/capture/:captureID",
+ openAPIRoute(
+ {
+ tag: "EPCIS",
+ summary: "Get Capture Status",
+ description: "Check the status of an EPCIS capture by captureID",
+ params: z.object({
+ captureID: z.string().openapi({
+ description: "The capture ID returned from POST /epcis/capture",
+ example: "123",
+ }),
+ }),
+ response: {
+ description: "Capture status",
+ schema: z.object({
+ status: z.string(),
+ captureID: z.string(),
+ UAL: z.string().optional(),
+ publishedAt: z.string().optional(),
+ error: z.string().optional(),
+ }),
+ },
+ },
+ async (req, res) => {
+ try {
+ const { captureID } = req.params;
+ const publisherUrl = process.env.PUBLISHER_URL || "http://localhost:9200";
+
+ const captureIdPattern = /^[0-9]{1,20}$/;
+ if (!captureIdPattern.test(captureID)) {
+ return res.status(400).json({
+ error: "Invalid captureID format",
+ captureID,
+ } as any);
+ }
+ // Query publisher for asset status
+ let response: Response;
+ try {
+ response = await fetch(
+ `${publisherUrl}/api/dkg/assets/status/${encodeURIComponent(captureID)}`,
+ { signal: AbortSignal.timeout(PUBLISHER_GET_TIMEOUT_MS) }
+ );
+ } catch (error: any) {
+ if (error.name === "TimeoutError") {
+ return res.status(504).json({
+ error: "Publisher timeout",
+ captureID,
+ } as any);
+ }
+ throw error;
+ }
+
+ if (!response.ok) {
+ if (response.status === 404) {
+ return res.status(404).json({ error: "Capture not found", captureID } as any);
+ }
+ throw new Error("Failed to fetch capture status");
+ }
+
+ const asset = await response.json();
+
+ // Map publisher status to EPCIS response
+ const result: any = {
+ status: asset.status,
+ captureID,
+ };
+
+ if (asset.ual) result.UAL = asset.ual;
+ if (asset.publishedAt) result.publishedAt = asset.publishedAt;
+ if (asset.lastError) result.error = asset.lastError;
+
+ res.json(result);
+ } catch (error: any) {
+ console.error("[EPCIS Status] Error:", error);
+ res.status(500).json({
+ error: "Failed to get capture status",
+ //message: error.message,
+ } as any);
+ }
+ }
+ )
+ );
+
+ // GET /epcis/events - Query EPCIS events from DKG
+ api.get(
+ "/epcis/events",
+ openAPIRoute(
+ {
+ tag: "EPCIS",
+ summary: "Query EPCIS Events",
+ description: "Query EPCIS events from DKG using various filters",
+ query: z.object({
+ epc: z.string().optional().openapi({
+ description: "Filter by EPC (product identifier)",
+ example: "urn:epc:id:sgtin:0614141.107346.2017",
+ }),
+ from: z.string().datetime({ message: "Must be ISO 8601 format (e.g., 2024-01-01T00:00:00Z)" }).optional().openapi({
+ description: "Start of time range (ISO 8601)",
+ example: "2024-01-01T00:00:00Z",
+ }),
+ to: z.string().datetime({ message: "Must be ISO 8601 format (e.g., 2024-12-31T23:59:59Z)" }).optional().openapi({
+ description: "End of time range (ISO 8601)",
+ example: "2024-12-31T23:59:59Z",
+ }),
+ bizStep: z.string().optional().openapi({
+ description: "Filter by business step URI",
+ example: "https://ref.gs1.org/cbv/BizStep-assembling",
+ }),
+ bizLocation: z.string().optional().openapi({
+ description: "Filter by business location",
+ example: "urn:epc:id:sgln:0614141.00001.0",
+ }),
+ /*ual: z.string().optional().openapi({
+ description: "Get event by specific UAL",
+ }),*/
+ fullTrace: z.string().optional().openapi({
+ description: "If 'true', search all EPC fields (epcList, inputEPCList, outputEPCList, childEPCs, parentID) for full supply chain traceability",
+ example: "true",
+ }),
+ }),
+ response: {
+ description: "Query results",
+ schema: z.object({
+ success: z.boolean(),
+ query: z.string().optional(),
+ results: z.array(z.any()),
+ count: z.number(),
+ }),
+ },
+ },
+ async (req, res) => {
+ try {
+ const { epc, from, to, bizStep, bizLocation, /*ual,*/ fullTrace } = req.query;
+
+ // Build the SPARQL query based on parameters
+ const sparqlQuery = queryService.buildQuery({
+ epc: epc as string,
+ from: from as string,
+ to: to as string,
+ bizStep: bizStep as string,
+ bizLocation: bizLocation as string,
+ //ual: ual as string,
+ fullTrace: fullTrace === 'true',
+ });
+
+ console.log("[EPCIS Events] Executing SPARQL query:", sparqlQuery);
+
+ // Execute query against DKG
+ const results = await ctx.dkg.graph.query(sparqlQuery, "SELECT");
+
+ res.json({
+ success: true,
+ //query: sparqlQuery,
+ results: results || [],
+ count: results?.length || 0,
+ });
+ } catch (error: any) {
+ console.error("[EPCIS Events] Query error:", error);
+ res.status(500).json({
+ success: false,
+ error: "Failed to query events",
+ message: error.message,
+ } as any);
+ }
+ }
+ )
+ );
+
+});
\ No newline at end of file
diff --git a/packages/plugin-epcis/src/model/types.ts b/packages/plugin-epcis/src/model/types.ts
new file mode 100644
index 0000000..ccf4ab8
--- /dev/null
+++ b/packages/plugin-epcis/src/model/types.ts
@@ -0,0 +1,50 @@
+// EPCIS Document types based on GS1 EPCIS 2.0
+export interface EPCISDocument {
+ "@context": string | string[] | Record;
+ type: "EPCISDocument";
+ schemaVersion: string;
+ creationDate: string;
+ epcisBody?: {
+ eventList: EPCISEvent[];
+ };
+ eventList?: EPCISEvent[];
+ [key: string]: any;
+ }
+
+ export interface EPCISEvent {
+ type: string;
+ eventTime: string;
+ eventTimeZoneOffset?: string;
+ epcList?: string[];
+ action?: string;
+ bizStep?: string;
+ disposition?: string;
+ readPoint?: { id: string };
+ bizLocation?: { id: string };
+ bizTransactionList?: Array<{ type: string; bizTransaction: string }>;
+ sensorElementList?: any[];
+ [key: string]: any;
+ }
+
+ // API Response types
+ export interface CaptureResponse {
+ status: string;
+ receivedAt: string;
+ captureID: string;
+ eventCount: number;
+ }
+
+ export interface CaptureStatusResponse {
+ status: "pending" | "queued" | "assigned" | "publishing" | "published" | "failed";
+ UAL?: string;
+ eventCount?: number;
+ error?: string;
+ publishedAt?: string | null;
+ }
+
+ // Validation result type
+ export interface ValidationResult {
+ valid: boolean;
+ errors?: string[];
+ eventCount?: number;
+ }
\ No newline at end of file
diff --git a/packages/plugin-epcis/src/schemas/epcis-json-schema.json b/packages/plugin-epcis/src/schemas/epcis-json-schema.json
new file mode 100644
index 0000000..c1b5086
--- /dev/null
+++ b/packages/plugin-epcis/src/schemas/epcis-json-schema.json
@@ -0,0 +1,2352 @@
+{
+ "$id": "https://ref.gs1.org/standards/epcis/2.0.0/epcis-json-schema.json",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "required": [
+ "type"
+ ],
+ "properties": {
+ "type": {
+ "type": "string"
+ }
+ },
+ "allOf": [
+ {
+ "if": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": [
+ "EPCISDocument"
+ ]
+ }
+ }
+ },
+ "then": {
+ "$ref": "#/definitions/epcisDocument"
+ }
+ },
+ {
+ "if": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": [
+ "EPCISQueryDocument"
+ ]
+ }
+ }
+ },
+ "then": {
+ "$ref": "#/definitions/epcisQueryDocument"
+ }
+ },
+ {
+ "if": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": [
+ "ObjectEvent"
+ ]
+ }
+ }
+ },
+ "then": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/ObjectEvent"
+ },
+ {
+ "$ref": "#/definitions/required-ld-context"
+ }
+ ]
+ }
+ },
+ {
+ "if": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": [
+ "AggregationEvent"
+ ]
+ }
+ }
+ },
+ "then": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/AggregationEvent"
+ },
+ {
+ "$ref": "#/definitions/required-ld-context"
+ }
+ ]
+ }
+ },
+ {
+ "if": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": [
+ "AssociationEvent"
+ ]
+ }
+ }
+ },
+ "then": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/AssociationEvent"
+ },
+ {
+ "$ref": "#/definitions/required-ld-context"
+ }
+ ]
+ }
+ },
+ {
+ "if": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": [
+ "TransformationEvent"
+ ]
+ }
+ }
+ },
+ "then": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/TransformationEvent"
+ },
+ {
+ "$ref": "#/definitions/required-ld-context"
+ }
+ ]
+ }
+ },
+ {
+ "if": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": [
+ "TransactionEvent"
+ ]
+ }
+ }
+ },
+ "then": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/TransactionEvent"
+ },
+ {
+ "$ref": "#/definitions/required-ld-context"
+ }
+ ]
+ }
+ },
+ {
+ "if": {
+ "not": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": [
+ "AssociationEvent",
+ "ObjectEvent",
+ "AggregationEvent",
+ "TransactionEvent",
+ "TransformationEvent",
+ "EPCISQueryDocument",
+ "EPCISDocument"
+ ]
+ }
+ }
+ }
+ },
+ "then": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/Extended-Event"
+ },
+ {
+ "$ref": "#/definitions/required-ld-context"
+ }
+ ]
+ }
+ }
+ ],
+ "definitions": {
+ "vocabulary": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "$ref": "#/definitions/uri"
+ },
+ "vocabularyElementList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/vocabularyElement"
+ }
+ }
+ },
+ "required": [
+ "type"
+ ]
+ },
+ "vocabularyElement": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/definitions/uri"
+ },
+ "attributes": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/attribute"
+ }
+ },
+ "children": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/uri"
+ }
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "attribute": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/definitions/uri"
+ },
+ "attribute": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string"
+ },
+ {
+ "type": "object"
+ }
+ ]
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "eventList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/EPCIS-Document-Event"
+ }
+ },
+ "vocabularyList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/vocabulary"
+ }
+ },
+ "resultsBody": {
+ "type": "object",
+ "properties": {
+ "eventList": {
+ "$ref": "#/definitions/eventList"
+ },
+ "vocabularyList": {
+ "$ref": "#/definitions/vocabularyList"
+ }
+ },
+ "required": [
+ "eventList"
+ ],
+ "propertyNames": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "eventList",
+ "vocabularyList"
+ ]
+ },
+ {
+ "$ref": "#/definitions/vocab-uri"
+ }
+ ]
+ }
+ },
+ "queryResults": {
+ "type": "object",
+ "properties": {
+ "queryName": {
+ "type": "string"
+ },
+ "subscriptionID": {
+ "type": "string"
+ },
+ "resultsBody": {
+ "$ref": "#/definitions/resultsBody"
+ }
+ },
+ "required": [
+ "queryName",
+ "resultsBody"
+ ],
+ "propertyNames": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "queryName",
+ "subscriptionID",
+ "resultsBody"
+ ]
+ },
+ {
+ "$ref": "#/definitions/vocab-uri"
+ }
+ ]
+ }
+ },
+ "epcisQueryDocumentBody": {
+ "type": "object",
+ "properties": {
+ "queryResults": {
+ "$ref": "#/definitions/queryResults"
+ }
+ },
+ "required": [
+ "queryResults"
+ ],
+ "propertyNames": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "queryResults"
+ ]
+ },
+ {
+ "$ref": "#/definitions/vocab-uri"
+ }
+ ]
+ }
+ },
+ "epcisHeader": {
+ "type": "object",
+ "properties": {
+ "epcisMasterData": {
+ "type": "object",
+ "properties": {
+ "vocabularyList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/vocabulary"
+ }
+ }
+ }
+ }
+ },
+ "propertyNames": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "epcisMasterData"
+ ]
+ },
+ {
+ "$ref": "#/definitions/vocab-uri"
+ }
+ ]
+ }
+ },
+ "epcisDocument": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/definitions/id"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "EPCISDocument"
+ ]
+ },
+ "@context": {
+ "$ref": "#/definitions/@context"
+ },
+ "schemaVersion": {
+ "$ref": "#/definitions/version"
+ },
+ "creationDate": {
+ "$ref": "#/definitions/time"
+ },
+ "instanceIdentifier": {
+ "type": "string"
+ },
+ "sender": {
+ "type": "string"
+ },
+ "receiver": {
+ "type": "string"
+ },
+ "epcisHeader": {
+ "$ref": "#/definitions/epcisHeader"
+ },
+ "epcisBody": {
+ "type": "object",
+ "properties": {
+ "eventList": {
+ "$ref": "#/definitions/eventList"
+ }
+ },
+ "required": [
+ "eventList"
+ ]
+ }
+ },
+ "required": [
+ "@context",
+ "type",
+ "schemaVersion",
+ "creationDate",
+ "epcisBody"
+ ],
+ "propertyNames": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "@context",
+ "id",
+ "type",
+ "schemaVersion",
+ "creationDate",
+ "instanceIdentifier",
+ "sender",
+ "receiver",
+ "epcisHeader",
+ "epcisBody"
+ ]
+ },
+ {
+ "$ref": "#/definitions/vocab-uri"
+ }
+ ]
+ }
+ },
+ "epcisQueryDocument": {
+ "type": "object",
+ "properties": {
+ "@context": {
+ "$ref": "#/definitions/@context"
+ },
+ "id": {
+ "$ref": "#/definitions/id"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "EPCISQueryDocument"
+ ]
+ },
+ "schemaVersion": {
+ "$ref": "#/definitions/version"
+ },
+ "creationDate": {
+ "$ref": "#/definitions/time"
+ },
+ "epcisBody": {
+ "$ref": "#/definitions/epcisQueryDocumentBody"
+ }
+ },
+ "required": [
+ "@context",
+ "type",
+ "epcisBody"
+ ],
+ "propertyNames": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "@context",
+ "id",
+ "type",
+ "schemaVersion",
+ "creationDate",
+ "epcisBody"
+ ]
+ },
+ {
+ "$ref": "#/definitions/vocab-uri"
+ }
+ ]
+ }
+ },
+ "EPCIS-Document": {
+ "oneOf": [
+ {
+ "$ref": "#/definitions/epcisDocument"
+ },
+ {
+ "$ref": "#/definitions/epcisQueryDocument"
+ }
+ ]
+ },
+ "uri": {
+ "type": "string",
+ "format": "uri"
+ },
+ "time": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "decimal": {
+ "type": "number"
+ },
+ "boolean": {
+ "type": "boolean"
+ },
+ "hexBinary": {
+ "type": "string",
+ "pattern": "^[A-Fa-f0-9]+$"
+ },
+ "string": {
+ "type": "string"
+ },
+ "action": {
+ "type": "string",
+ "enum": [
+ "OBSERVE",
+ "ADD",
+ "DELETE"
+ ]
+ },
+ "eventType": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "ObjectEvent",
+ "AggregationEvent",
+ "AssociationEvent",
+ "TransformationEvent",
+ "TransactionEvent"
+ ]
+ },
+ {
+ "type": "string",
+ "format": "uri"
+ }
+ ]
+ },
+ "persistentDisposition": {
+ "allOf": [
+ {
+ "type": "object",
+ "properties": {
+ "set": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/disposition"
+ },
+ "minItems": 1,
+ "uniqueItems": true
+ },
+ "unset": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/disposition"
+ },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ },
+ "additionalProperties": false
+ },
+ {
+ "anyOf": [
+ {
+ "type": "object",
+ "required": [
+ "set"
+ ]
+ },
+ {
+ "type": "object",
+ "required": [
+ "unset"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "epcList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/uri"
+ },
+ "uniqueItems": true
+ },
+ "quantityList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/quantityElement"
+ }
+ },
+ "uom": {
+ "type": "string",
+ "pattern": "^[A-Z0-9]{2,3}$"
+ },
+ "eventID": {
+ "$ref": "#/definitions/uri"
+ },
+ "certificationInfo": {
+ "anyOf": [
+ {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/uri"
+ }
+ },
+ {
+ "$ref": "#/definitions/uri"
+ }
+ ]
+ },
+ "errorDeclaration": {
+ "type": "object",
+ "properties": {
+ "declarationTime": {
+ "$ref": "#/definitions/time"
+ },
+ "reason": {
+ "$ref": "#/definitions/error-reason"
+ },
+ "correctiveEventIDs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/eventID"
+ }
+ }
+ },
+ "required": [
+ "declarationTime"
+ ],
+ "propertyNames": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/vocab-uri"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "declarationTime",
+ "reason",
+ "correctiveEventIDs"
+ ]
+ }
+ ]
+ }
+ },
+ "quantityElement": {
+ "type": "object",
+ "properties": {
+ "epcClass": {
+ "$ref": "#/definitions/uri"
+ },
+ "quantity": {
+ "$ref": "#/definitions/decimal"
+ },
+ "uom": {
+ "$ref": "#/definitions/uom"
+ }
+ },
+ "required": [
+ "epcClass"
+ ],
+ "additionalProperties": false
+ },
+ "bizTransaction": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "$ref": "#/definitions/bizTransaction-type"
+ },
+ "bizTransaction": {
+ "$ref": "#/definitions/uri"
+ }
+ },
+ "required": [
+ "bizTransaction"
+ ],
+ "additionalProperties": false
+ },
+ "readPoint": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/definitions/uri"
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "bizLocation": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/definitions/uri"
+ }
+ },
+ "required": [
+ "id"
+ ]
+ },
+ "source": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "$ref": "#/definitions/source-dest-type"
+ },
+ "source": {
+ "$ref": "#/definitions/uri"
+ }
+ },
+ "required": [
+ "type",
+ "source"
+ ],
+ "additionalProperties": false
+ },
+ "destination": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "$ref": "#/definitions/source-dest-type"
+ },
+ "destination": {
+ "$ref": "#/definitions/uri"
+ }
+ },
+ "required": [
+ "type",
+ "destination"
+ ],
+ "additionalProperties": false
+ },
+ "sensorElement": {
+ "type": "object",
+ "properties": {
+ "sensorMetadata": {
+ "$ref": "#/definitions/sensorMetadata"
+ },
+ "sensorReport": {
+ "$ref": "#/definitions/sensorReportList"
+ }
+ },
+ "required": [
+ "sensorReport"
+ ],
+ "propertyNames": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/vocab-uri"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "sensorMetadata",
+ "sensorReport"
+ ]
+ }
+ ]
+ }
+ },
+ "sensorReportList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/sensorReport"
+ },
+ "minItems": 1
+ },
+ "sensorReport": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "$ref": "#/definitions/measurementType"
+ },
+ "exception": {
+ "$ref": "#/definitions/sensorAlertType"
+ },
+ "deviceID": {
+ "$ref": "#/definitions/uri"
+ },
+ "deviceMetadata": {
+ "$ref": "#/definitions/uri"
+ },
+ "rawData": {
+ "$ref": "#/definitions/uri"
+ },
+ "dataProcessingMethod": {
+ "$ref": "#/definitions/uri"
+ },
+ "bizRules": {
+ "$ref": "#/definitions/uri"
+ },
+ "time": {
+ "$ref": "#/definitions/time"
+ },
+ "microorganism": {
+ "$ref": "#/definitions/uri"
+ },
+ "chemicalSubstance": {
+ "$ref": "#/definitions/uri"
+ },
+ "coordinateReferenceSystem": {
+ "$ref": "#/definitions/uri"
+ },
+ "value": {
+ "$ref": "#/definitions/decimal"
+ },
+ "component": {
+ "$ref": "#/definitions/component"
+ },
+ "stringValue": {
+ "$ref": "#/definitions/string"
+ },
+ "booleanValue": {
+ "$ref": "#/definitions/boolean"
+ },
+ "hexBinaryValue": {
+ "$ref": "#/definitions/hexBinary"
+ },
+ "uriValue": {
+ "$ref": "#/definitions/uri"
+ },
+ "minValue": {
+ "$ref": "#/definitions/decimal"
+ },
+ "maxValue": {
+ "$ref": "#/definitions/decimal"
+ },
+ "meanValue": {
+ "$ref": "#/definitions/decimal"
+ },
+ "sDev": {
+ "$ref": "#/definitions/decimal"
+ },
+ "percRank": {
+ "$ref": "#/definitions/decimal"
+ },
+ "percValue": {
+ "$ref": "#/definitions/decimal"
+ },
+ "uom": {
+ "$ref": "#/definitions/string"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "propertyNames": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/vocab-uri"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "type",
+ "exception",
+ "deviceID",
+ "deviceMetadata",
+ "rawData",
+ "dataProcessingMethod",
+ "bizRules",
+ "time",
+ "microorganism",
+ "chemicalSubstance",
+ "coordinateReferenceSystem",
+ "value",
+ "component",
+ "stringValue",
+ "booleanValue",
+ "hexBinaryValue",
+ "uriValue",
+ "minValue",
+ "maxValue",
+ "meanValue",
+ "sDev",
+ "percRank",
+ "percValue",
+ "uom"
+ ]
+ }
+ ]
+ }
+ },
+ "sensorMetadata": {
+ "type": "object",
+ "properties": {
+ "time": {
+ "$ref": "#/definitions/time"
+ },
+ "deviceID": {
+ "$ref": "#/definitions/uri"
+ },
+ "deviceMetadata": {
+ "$ref": "#/definitions/uri"
+ },
+ "rawData": {
+ "$ref": "#/definitions/uri"
+ },
+ "startTime": {
+ "$ref": "#/definitions/time"
+ },
+ "endTime": {
+ "$ref": "#/definitions/time"
+ },
+ "dataProcessingMethod": {
+ "$ref": "#/definitions/uri"
+ },
+ "bizRules": {
+ "$ref": "#/definitions/uri"
+ }
+ },
+ "propertyNames": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/vocab-uri"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "time",
+ "deviceID",
+ "deviceMetadata",
+ "rawData",
+ "startTime",
+ "endTime",
+ "dataProcessingMethod",
+ "bizRules"
+ ]
+ }
+ ]
+ }
+ },
+ "ilmd": {
+ "type": "object",
+ "propertyNames": {
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "Event": {
+ "type": "object",
+ "properties": {
+ "@context": {
+ "$ref": "#/definitions/@context"
+ },
+ "eventTime": {
+ "$ref": "#/definitions/time"
+ },
+ "recordTime": {
+ "$ref": "#/definitions/time"
+ },
+ "eventTimeZoneOffset": {
+ "type": "string",
+ "pattern": "^([+]|[-])((0[0-9]|1[0-3]):([0-5][0-9])|14:00)$"
+ },
+ "eventID": {
+ "$ref": "#/definitions/eventID"
+ },
+ "certificationInfo": {
+ "$ref": "#/definitions/certificationInfo"
+ },
+ "errorDeclaration": {
+ "$ref": "#/definitions/errorDeclaration"
+ }
+ },
+ "required": [
+ "eventTime",
+ "eventTimeZoneOffset"
+ ]
+ },
+ "common-event-properties": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "@context",
+ "type",
+ "eventTime",
+ "recordTime",
+ "eventTimeZoneOffset",
+ "eventID",
+ "certificationInfo",
+ "errorDeclaration"
+ ]
+ },
+ {
+ "$ref": "#/definitions/vocab-uri"
+ }
+ ]
+ },
+ "Extended-Event": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/Event"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "$ref": "#/definitions/vocab-uri"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ ]
+ },
+ "disposition": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/vocab-other-uri"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "active",
+ "container_closed",
+ "damaged",
+ "destroyed",
+ "dispensed",
+ "disposed",
+ "encoded",
+ "expired",
+ "in_progress",
+ "in_transit",
+ "inactive",
+ "no_pedigree_match",
+ "non_sellable_other",
+ "partially_dispensed",
+ "recalled",
+ "reserved",
+ "retail_sold",
+ "returned",
+ "sellable_accessible",
+ "sellable_not_accessible",
+ "stolen",
+ "unknown",
+ "available",
+ "completeness_verified",
+ "completeness_inferred",
+ "conformant",
+ "container_open",
+ "mismatch_instance",
+ "mismatch_class",
+ "mismatch_quantity",
+ "needs_replacement",
+ "non_conformant",
+ "unavailable"
+ ]
+ }
+ ]
+ },
+ "@context": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uri"
+ },
+ {
+ "type": "object"
+ },
+ {
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "anyOf": [
+ {
+ "type": "string",
+ "format": "uri"
+ },
+ {
+ "type": "object"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "vocab-uri": {
+ "type": "string",
+ "format": "uri"
+ },
+ "vocab-other-uri": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^(?!(urn:epcglobal:cbv|https?:\\/\\/ns\\.gs1\\.org/cbv\\/))"
+ },
+ "vocab-nonGS1WebVoc-uri": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^(?!(https?:\\/\\/gs1\\.org\\/voc\\/|https?:\\/\\/www\\.gs1\\.org\\/voc\\/))"
+ },
+ "required-ld-context": {
+ "type": "object",
+ "required": [
+ "@context"
+ ]
+ },
+ "version": {
+ "type": "string",
+ "pattern": "^\\d+(\\.\\d+)*$"
+ },
+ "id": {
+ "type": "string",
+ "format": "uri"
+ },
+ "error-reason": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/vocab-other-uri"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "did_not_occur",
+ "incorrect_data"
+ ]
+ }
+ ]
+ },
+ "bizTransaction-type": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/vocab-other-uri"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "bol",
+ "cert",
+ "desadv",
+ "inv",
+ "pedigree",
+ "po",
+ "poc",
+ "prodorder",
+ "recadv",
+ "rma",
+ "testprd",
+ "testres",
+ "upevt"
+ ]
+ }
+ ]
+ },
+ "source-dest-type": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/vocab-other-uri"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "owning_party",
+ "possessing_party",
+ "location"
+ ]
+ }
+ ]
+ },
+ "measurementType": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/vocab-nonGS1WebVoc-uri"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "AbsoluteHumidity",
+ "AbsorbedDose",
+ "AbsorbedDoseRate",
+ "Acceleration",
+ "Radioactivity",
+ "Altitude",
+ "AmountOfSubstance",
+ "AmountOfSubstancePerUnitVolume",
+ "Angle",
+ "AngularAcceleration",
+ "AngularMomentum",
+ "AngularVelocity",
+ "Area",
+ "Capacitance",
+ "Conductance",
+ "Conductivity",
+ "Count",
+ "Density",
+ "Dimensionless",
+ "DoseEquivalent",
+ "DoseEquivalentRate",
+ "DynamicViscosity",
+ "ElectricCharge",
+ "ElectricCurrent",
+ "ElectricCurrentDensity",
+ "ElectricFieldStrength",
+ "Energy",
+ "Exposure",
+ "Force",
+ "Frequency",
+ "Illuminance",
+ "Inductance",
+ "Irradiance",
+ "KinematicViscosity",
+ "Length",
+ "LinearMomentum",
+ "Luminance",
+ "LuminousFlux",
+ "LuminousIntensity",
+ "MagneticFlux",
+ "MagneticFluxDensity",
+ "MagneticVectorPotential",
+ "Mass",
+ "MassConcentration",
+ "MassFlowRate",
+ "MassPerAreaTime",
+ "MemoryCapacity",
+ "MolalityOfSolute",
+ "MolarEnergy",
+ "MolarMass",
+ "MolarVolume",
+ "Power",
+ "Pressure",
+ "RadiantFlux",
+ "RadiantIntensity",
+ "RelativeHumidity",
+ "Resistance",
+ "Resistivity",
+ "SolidAngle",
+ "SpecificVolume",
+ "Speed",
+ "SurfaceDensity",
+ "SurfaceTension",
+ "Temperature",
+ "Time",
+ "Torque",
+ "Voltage",
+ "Volume",
+ "VolumeFlowRate",
+ "VolumeFraction",
+ "VolumetricFlux",
+ "Wavenumber"
+ ]
+ }
+ ]
+ },
+ "sensorAlertType": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/vocab-nonGS1WebVoc-uri"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "ALARM_CONDITION",
+ "ERROR_CONDITION"
+ ]
+ }
+ ]
+ },
+ "component": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/vocab-other-uri"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "x",
+ "y",
+ "z",
+ "axial_distance",
+ "azimuth",
+ "height",
+ "spherical_radius",
+ "polar_angle",
+ "elevation_angle",
+ "easting",
+ "northing",
+ "latitude",
+ "longitude",
+ "altitude"
+ ]
+ }
+ ]
+ },
+ "EPCIS-Document-Event": {
+ "type": "object",
+ "required": [
+ "type"
+ ],
+ "allOf": [
+ {
+ "if": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": [
+ "ObjectEvent"
+ ]
+ }
+ }
+ },
+ "then": {
+ "$ref": "#/definitions/ObjectEvent"
+ }
+ },
+ {
+ "if": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": [
+ "AggregationEvent"
+ ]
+ }
+ }
+ },
+ "then": {
+ "$ref": "#/definitions/AggregationEvent"
+ }
+ },
+ {
+ "if": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": [
+ "TransactionEvent"
+ ]
+ }
+ }
+ },
+ "then": {
+ "$ref": "#/definitions/TransactionEvent"
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "type": {
+ "enum": [
+ "TransformationEvent"
+ ]
+ }
+ }
+ },
+ "then": {
+ "$ref": "#/definitions/TransformationEvent"
+ }
+ },
+ {
+ "if": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": [
+ "AssociationEvent"
+ ]
+ }
+ }
+ },
+ "then": {
+ "$ref": "#/definitions/AssociationEvent"
+ }
+ },
+ {
+ "if": {
+ "not": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": [
+ "AssociationEvent",
+ "ObjectEvent",
+ "AggregationEvent",
+ "TransactionEvent",
+ "TransformationEvent"
+ ]
+ }
+ }
+ }
+ },
+ "then": {
+ "$ref": "#/definitions/Extended-Event"
+ }
+ }
+ ],
+ "properties": {
+ "type": {
+ "type": "string"
+ }
+ }
+ },
+ "ObjectEvent": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/Event"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "ObjectEvent"
+ ]
+ },
+ "epcList": {
+ "$ref": "#/definitions/epcList"
+ },
+ "quantityList": {
+ "$ref": "#/definitions/quantityList"
+ },
+ "action": {
+ "$ref": "#/definitions/action"
+ },
+ "bizStep": {
+ "$ref": "#/definitions/bizStep"
+ },
+ "disposition": {
+ "$ref": "#/definitions/disposition"
+ },
+ "persistentDisposition": {
+ "$ref": "#/definitions/persistentDisposition"
+ },
+ "readPoint": {
+ "$ref": "#/definitions/readPoint"
+ },
+ "bizLocation": {
+ "$ref": "#/definitions/bizLocation"
+ },
+ "bizTransactionList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/bizTransaction"
+ }
+ },
+ "sourceList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/source"
+ }
+ },
+ "destinationList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/destination"
+ }
+ },
+ "sensorElementList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/sensorElement"
+ }
+ },
+ "ilmd": {
+ "$ref": "#/definitions/ilmd"
+ }
+ },
+ "required": [
+ "type",
+ "action"
+ ],
+ "propertyNames": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/common-event-properties"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "action",
+ "epcList",
+ "quantityList",
+ "bizStep",
+ "disposition",
+ "persistentDisposition",
+ "readPoint",
+ "bizLocation",
+ "bizTransactionList",
+ "sourceList",
+ "destinationList",
+ "sensorElementList",
+ "ilmd"
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "epcList": {
+ "type": "array",
+ "minItems": 0,
+ "items": {
+ "$ref": "#/definitions/id"
+ }
+ }
+ },
+ "required": [
+ "epcList"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "quantityList": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/definitions/quantityElement"
+ }
+ }
+ },
+ "required": [
+ "quantityList"
+ ]
+ },
+ {
+ "allOf": [
+ {
+ "type": "object",
+ "properties": {
+ "sensorElementList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/sensorElement"
+ },
+ "minItems": 1
+ }
+ },
+ "required": [
+ "sensorElementList"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "readPoint": {
+ "$ref": "#/definitions/readPoint"
+ }
+ },
+ "required": [
+ "readPoint"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "ilmd": {
+ "not": {}
+ },
+ "action": {
+ "type": "string",
+ "pattern": "^OBSERVE$"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "properties": {
+ "ilmd": {
+ "not": {}
+ },
+ "action": {
+ "type": "string",
+ "pattern": "^DELETE$"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "properties": {
+ "action": {
+ "type": "string",
+ "pattern": "^ADD$"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "bizStep": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/vocab-other-uri"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "accepting",
+ "arriving",
+ "assembling",
+ "collecting",
+ "commissioning",
+ "consigning",
+ "creating_class_instance",
+ "cycle_counting",
+ "decommissioning",
+ "departing",
+ "destroying",
+ "disassembling",
+ "dispensing",
+ "encoding",
+ "entering_exiting",
+ "holding",
+ "inspecting",
+ "installing",
+ "killing",
+ "loading",
+ "other",
+ "packing",
+ "picking",
+ "receiving",
+ "removing",
+ "repackaging",
+ "repairing",
+ "replacing",
+ "reserving",
+ "retail_selling",
+ "shipping",
+ "staging_outbound",
+ "stock_taking",
+ "stocking",
+ "storing",
+ "transporting",
+ "unloading",
+ "unpacking",
+ "void_shipping",
+ "sensor_reporting",
+ "sampling"
+ ]
+ }
+ ]
+ },
+ "AggregationEvent": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/Event"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "AggregationEvent"
+ ]
+ },
+ "parentID": {
+ "$ref": "#/definitions/uri"
+ },
+ "childEPCs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/uri"
+ }
+ },
+ "childQuantityList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/quantityElement"
+ }
+ },
+ "action": {
+ "$ref": "#/definitions/action"
+ },
+ "bizStep": {
+ "$ref": "#/definitions/bizStep"
+ },
+ "disposition": {
+ "$ref": "#/definitions/disposition"
+ },
+ "readPoint": {
+ "$ref": "#/definitions/readPoint"
+ },
+ "bizLocation": {
+ "$ref": "#/definitions/bizLocation"
+ },
+ "bizTransactionList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/bizTransaction"
+ }
+ },
+ "sourceList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/source"
+ }
+ },
+ "destinationList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/destination"
+ }
+ },
+ "sensorElementList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/sensorElement"
+ }
+ }
+ },
+ "required": [
+ "type",
+ "action"
+ ],
+ "propertyNames": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/common-event-properties"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "parentID",
+ "childEPCs",
+ "childQuantityList",
+ "action",
+ "bizStep",
+ "disposition",
+ "persistentDisposition",
+ "readPoint",
+ "bizLocation",
+ "bizTransactionList",
+ "sourceList",
+ "destinationList",
+ "sensorElementList"
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "childEPCs": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/definitions/id"
+ }
+ }
+ },
+ "required": [
+ "childEPCs"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "childQuantityList": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/definitions/quantityElement"
+ }
+ }
+ },
+ "required": [
+ "childQuantityList"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "action": {
+ "type": "string",
+ "pattern": "^DELETE$"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "TransactionEvent": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/Event"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "TransactionEvent"
+ ]
+ },
+ "bizTransactionList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/bizTransaction"
+ },
+ "minItems": 1
+ },
+ "parentID": {
+ "$ref": "#/definitions/uri"
+ },
+ "epcList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/uri"
+ }
+ },
+ "quantityList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/quantityElement"
+ }
+ },
+ "action": {
+ "$ref": "#/definitions/action"
+ },
+ "bizStep": {
+ "$ref": "#/definitions/bizStep"
+ },
+ "disposition": {
+ "$ref": "#/definitions/disposition"
+ },
+ "readPoint": {
+ "$ref": "#/definitions/readPoint"
+ },
+ "bizLocation": {
+ "$ref": "#/definitions/bizLocation"
+ },
+ "sourceList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/source"
+ }
+ },
+ "destinationList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/destination"
+ }
+ },
+ "sensorElementList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/sensorElement"
+ }
+ }
+ },
+ "required": [
+ "type",
+ "bizTransactionList",
+ "action"
+ ],
+ "propertyNames": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/common-event-properties"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "bizTransactionList",
+ "parentID",
+ "epcList",
+ "quantityList",
+ "action",
+ "bizStep",
+ "disposition",
+ "persistentDisposition",
+ "readPoint",
+ "bizLocation",
+ "sourceList",
+ "destinationList",
+ "sensorElementList"
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "epcList": {
+ "type": "array",
+ "minItems": 0,
+ "items": {
+ "$ref": "#/definitions/id"
+ }
+ }
+ },
+ "required": [
+ "epcList"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "quantityList": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/definitions/quantityElement"
+ }
+ }
+ },
+ "required": [
+ "quantityList"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "action": {
+ "type": "string",
+ "pattern": "^DELETE$"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "TransformationEvent": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/Event"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "TransformationEvent"
+ ]
+ },
+ "inputEPCList": {
+ "$ref": "#/definitions/epcList"
+ },
+ "inputQuantityList": {
+ "$ref": "#/definitions/quantityList"
+ },
+ "outputEPCList": {
+ "$ref": "#/definitions/epcList"
+ },
+ "outputQuantityList": {
+ "$ref": "#/definitions/quantityList"
+ },
+ "transformationID": {
+ "$ref": "#/definitions/uri"
+ },
+ "bizStep": {
+ "$ref": "#/definitions/bizStep"
+ },
+ "disposition": {
+ "$ref": "#/definitions/disposition"
+ },
+ "persistentDisposition": {
+ "$ref": "#/definitions/persistentDisposition"
+ },
+ "readPoint": {
+ "$ref": "#/definitions/readPoint"
+ },
+ "bizLocation": {
+ "$ref": "#/definitions/bizLocation"
+ },
+ "bizTransactionList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/bizTransaction"
+ }
+ },
+ "sourceList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/source"
+ }
+ },
+ "destinationList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/destination"
+ }
+ },
+ "sensorElementList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/sensorElement"
+ }
+ },
+ "ilmd": {
+ "$ref": "#/definitions/ilmd"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "propertyNames": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/common-event-properties"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "inputEPCList",
+ "inputQuantityList",
+ "outputEPCList",
+ "outputQuantityList",
+ "transformationID",
+ "bizStep",
+ "disposition",
+ "persistentDisposition",
+ "readPoint",
+ "bizLocation",
+ "bizTransactionList",
+ "sourceList",
+ "destinationList",
+ "sensorElementList",
+ "ilmd"
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "anyOf": [
+ {
+ "allOf": [
+ {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "inputEPCList": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "inputEPCList"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "inputQuantityList": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "object"
+ }
+ }
+ },
+ "required": [
+ "inputQuantityList"
+ ]
+ }
+ ]
+ },
+ {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "outputEPCList": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "outputEPCList"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "outputQuantityList": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "object"
+ }
+ }
+ },
+ "required": [
+ "outputQuantityList"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "inputEPCList": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "inputEPCList"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "inputQuantityList": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "object"
+ }
+ }
+ },
+ "required": [
+ "inputQuantityList"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "outputEPCList": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "outputEPCList"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "outputQuantityList": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "object"
+ }
+ }
+ },
+ "required": [
+ "outputQuantityList"
+ ]
+ }
+ ],
+ "type": "object",
+ "required": [
+ "transformationID"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "AssociationEvent": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/Event"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "AssociationEvent"
+ ]
+ },
+ "parentID": {
+ "$ref": "#/definitions/uri"
+ },
+ "childEPCs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/uri"
+ }
+ },
+ "childQuantityList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/quantityElement"
+ }
+ },
+ "action": {
+ "$ref": "#/definitions/action"
+ },
+ "bizStep": {
+ "$ref": "#/definitions/bizStep"
+ },
+ "disposition": {
+ "$ref": "#/definitions/disposition"
+ },
+ "readPoint": {
+ "$ref": "#/definitions/readPoint"
+ },
+ "bizLocation": {
+ "$ref": "#/definitions/bizLocation"
+ },
+ "bizTransactionList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/bizTransaction"
+ }
+ },
+ "sourceList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/source"
+ }
+ },
+ "destinationList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/destination"
+ }
+ },
+ "sensorElementList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/sensorElement"
+ }
+ }
+ },
+ "required": [
+ "type",
+ "action",
+ "parentID"
+ ],
+ "propertyNames": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/common-event-properties"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "parentID",
+ "childEPCs",
+ "childQuantityList",
+ "action",
+ "bizStep",
+ "disposition",
+ "persistentDisposition",
+ "readPoint",
+ "bizLocation",
+ "bizTransactionList",
+ "sourceList",
+ "destinationList",
+ "sensorElementList"
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "childEPCs": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/definitions/id"
+ }
+ }
+ },
+ "required": [
+ "childEPCs"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "childQuantityList": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/definitions/quantityElement"
+ }
+ }
+ },
+ "required": [
+ "childQuantityList"
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "action": {
+ "type": "string",
+ "pattern": "^DELETE$"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/packages/plugin-epcis/src/services/EPCISQueryService.ts b/packages/plugin-epcis/src/services/EPCISQueryService.ts
new file mode 100644
index 0000000..a867014
--- /dev/null
+++ b/packages/plugin-epcis/src/services/EPCISQueryService.ts
@@ -0,0 +1,154 @@
+/**
+ * EPCIS Query Service
+ * Supports composite filtering - combine multiple filters in one query
+ */
+
+// Namespace prefixes for EPCIS queries
+const PREFIXES = `
+PREFIX epcis:
+PREFIX schema:
+PREFIX xsd:
+`;
+
+export interface EpcisQueryParams {
+ epc?: string;
+ from?: string;
+ to?: string;
+ bizStep?: string;
+ bizLocation?: string;
+ // ual?: string; // TODO: Re-enable when UAL query is implemented
+ /** If true, searches all EPC fields (epcList, inputEPCList, outputEPCList, childEPCs, parentID) */
+ fullTrace?: boolean;
+}
+
+/**
+ * Escape special characters in SPARQL string literals
+ */
+function escapeSparql(value: string): string {
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
+}
+
+/**
+ * Normalize bizStep to full GS1 CBV URI
+ * Accepts: "assembling" or "https://ref.gs1.org/cbv/BizStep-assembling"
+ */
+function normalizeBizStep(value: string): string {
+
+ if (typeof value !== "string" || value.length === 0) {
+ throw new Error("Invalid bizStep value");
+ }
+
+ if (!value.includes('://')) {
+ return `https://ref.gs1.org/cbv/BizStep-${value}`;
+ }
+ return value;
+}
+
+export class EpcisQueryService {
+ /**
+ * Build a composite SPARQL query supporting multiple filters
+ * All provided filters are combined with AND logic
+ */
+ buildQuery(params: EpcisQueryParams): string {
+ // Special case: UAL lookup returns all triples for that graph
+ /*if (params.ual) {
+ return this.getEventByUal(params.ual);
+ }*/
+
+ const wherePatterns: string[] = [];
+ const filterClauses: string[] = [];
+ const optionalClauses: string[] = [];
+
+ // Base pattern - always present
+ wherePatterns.push('?event a ?eventType .');
+
+ // Filter by event type (must be EPCIS event)
+ filterClauses.push('FILTER(STRSTARTS(STR(?eventType), "https://gs1.github.io/EPCIS/"))');
+
+ // EPC filter - with optional full traceability across all EPC fields
+ if (params.epc) {
+ const epcValue = escapeSparql(params.epc);
+ if (params.fullTrace) {
+ // Search across ALL EPC fields for full supply chain traceability
+ wherePatterns.push(`{
+ { ?event epcis:epcList "${epcValue}" }
+ UNION { ?event epcis:inputEPCList "${epcValue}" }
+ UNION { ?event epcis:outputEPCList "${epcValue}" }
+ UNION { ?event epcis:childEPCs "${epcValue}" }
+ UNION { ?event epcis:parentID "${epcValue}" }
+ }`);
+ } else {
+ // Default: only search epcList
+ wherePatterns.push(`?event epcis:epcList "${epcValue}" .`);
+ }
+ } else {
+ optionalClauses.push('OPTIONAL { ?event epcis:epcList ?epc . }');
+ }
+
+ // BizStep filter (accepts shorthand like "assembling" or full URI)
+ if (params.bizStep) {
+ const bizStepUri = normalizeBizStep(params.bizStep);
+ wherePatterns.push('?event epcis:bizStep ?bizStep .');
+ filterClauses.push(`FILTER(STR(?bizStep) = "${escapeSparql(bizStepUri)}")`);
+ } else {
+ optionalClauses.push('OPTIONAL { ?event epcis:bizStep ?bizStep . }');
+ }
+
+ // BizLocation filter
+ if (params.bizLocation) {
+ wherePatterns.push(`?event epcis:bizLocation "${escapeSparql(params.bizLocation)}" .`);
+ } else {
+ optionalClauses.push('OPTIONAL { ?event epcis:bizLocation ?bizLocation . }');
+ }
+
+ // Time range filter - use xsd:dateTime for proper date comparison
+ if (params.from || params.to) {
+ wherePatterns.push('?event epcis:eventTime ?eventTime .');
+ if (params.from && params.to) {
+ filterClauses.push(
+ `FILTER(xsd:dateTime(?eventTime) >= xsd:dateTime("${escapeSparql(params.from)}") && xsd:dateTime(?eventTime) <= xsd:dateTime("${escapeSparql(params.to)}"))`
+ );
+ } else if (params.from) {
+ filterClauses.push(`FILTER(xsd:dateTime(?eventTime) >= xsd:dateTime("${escapeSparql(params.from)}"))`);
+ } else if (params.to) {
+ filterClauses.push(`FILTER(xsd:dateTime(?eventTime) <= xsd:dateTime("${escapeSparql(params.to)}"))`);
+ }
+ } else {
+ optionalClauses.push('OPTIONAL { ?event epcis:eventTime ?eventTime . }');
+ }
+
+ // Always optional fields
+ optionalClauses.push('OPTIONAL { ?event epcis:disposition ?disposition . }');
+ optionalClauses.push('OPTIONAL { ?event epcis:readPoint ?readPoint . }');
+
+ // Assemble the query
+ return `${PREFIXES}
+SELECT ?ual ?eventType ?eventTime ?epc ?bizStep ?disposition ?readPoint ?bizLocation
+WHERE {
+ GRAPH ?ual {
+ ${wherePatterns.join('\n ')}
+ ${optionalClauses.join('\n ')}
+ }
+ ${filterClauses.join('\n ')}
+}
+ORDER BY DESC(?eventTime)
+LIMIT 100`;
+ }
+
+ /**
+ * Query event by UAL (get full event details)
+ */
+ /*private getEventByUal(ual: string): string {
+ // Basic UAL format validation
+ if (!ual.startsWith('did:')) {
+ throw new Error('Invalid UAL format');
+ }
+ return `${PREFIXES}
+SELECT ?predicate ?object
+WHERE {
+ GRAPH <${escapeSparql(ual)}> {
+ ?subject ?predicate ?object .
+ }
+}`;
+ }*/
+}
diff --git a/packages/plugin-epcis/src/services/EPCISValidationService.ts b/packages/plugin-epcis/src/services/EPCISValidationService.ts
new file mode 100644
index 0000000..db77cdd
--- /dev/null
+++ b/packages/plugin-epcis/src/services/EPCISValidationService.ts
@@ -0,0 +1,55 @@
+import Ajv from "ajv";
+import addFormats from "ajv-formats";
+import epcisSchema from "../schemas/epcis-json-schema.json";
+import type { EPCISDocument, ValidationResult } from "../model/types";
+
+export class EpcisValidationService {
+ private ajv: Ajv;
+ private validateSchema: ReturnType;
+
+ constructor() {
+ this.ajv = new Ajv({
+ allErrors: true,
+ strict: false,
+ validateFormats: true,
+ });
+ addFormats(this.ajv);
+
+ // Compile the EPCIS schema
+ this.validateSchema = this.ajv.compile(epcisSchema);
+ }
+
+ /**
+ * Validate an EPCISDocument against the GS1 JSON Schema
+ */
+ validate(document: unknown): ValidationResult {
+ const isValid = this.validateSchema(document);
+
+ if (!isValid) {
+ const errors = this.validateSchema.errors?.map((err) => {
+ return `${err.instancePath || "/"}: ${err.message}`;
+ }) || ["Unknown validation error"];
+
+ return {
+ valid: false,
+ errors,
+ };
+ }
+
+ // Count events for response
+ const doc = document as EPCISDocument;
+ const eventList = doc.epcisBody?.eventList || [];
+
+ return {
+ valid: true,
+ eventCount: eventList.length,
+ };
+ }
+
+ /**
+ * Extract events from an EPCISDocument
+ */
+ extractEvents(document: EPCISDocument): EPCISDocument["eventList"] {
+ return document.eventList || document.epcisBody?.eventList || [];
+ }
+}
\ No newline at end of file
diff --git a/packages/plugin-epcis/test-data/bicycle-manufacturing-story.json b/packages/plugin-epcis/test-data/bicycle-manufacturing-story.json
new file mode 100644
index 0000000..dcfe3ca
--- /dev/null
+++ b/packages/plugin-epcis/test-data/bicycle-manufacturing-story.json
@@ -0,0 +1,399 @@
+{
+ "story": "Alpine Cycles Bicycle Manufacturing - Full Traceability Demo",
+ "description": "This dataset tells the story of manufacturing premium bicycles, from receiving raw components to shipping finished products. Use these events to test EPCIS querying and item tracking.",
+
+ "characters": {
+ "components": {
+ "frame": "urn:epc:id:sgtin:4012345.011111.1001",
+ "frontWheel": "urn:epc:id:sgtin:4012345.022222.2001",
+ "rearWheel": "urn:epc:id:sgtin:4012345.022222.2002",
+ "handlebar": "urn:epc:id:sgtin:4012345.033333.3001"
+ },
+ "finishedProduct": {
+ "bicycle": "urn:epc:id:sgtin:4012345.099999.9001"
+ },
+ "container": {
+ "pallet": "urn:epc:id:sscc:4012345.0000000001"
+ }
+ },
+
+ "locations": {
+ "receivingDock": "urn:epc:id:sgln:4012345.00001.0",
+ "qualityLab": "urn:epc:id:sgln:4012345.00002.0",
+ "assemblyLine": "urn:epc:id:sgln:4012345.00003.0",
+ "packingArea": "urn:epc:id:sgln:4012345.00004.0",
+ "shippingDock": "urn:epc:id:sgln:4012345.00005.0"
+ },
+
+ "events": [
+ {
+ "name": "Event 1: Receive Frame",
+ "description": "Carbon fiber frame arrives from supplier",
+ "request": {
+ "epcisDocument": {
+ "@context": {
+ "@vocab": "https://gs1.github.io/EPCIS/",
+ "epcis": "https://gs1.github.io/EPCIS/",
+ "cbv": "https://ref.gs1.org/cbv/",
+ "type": "@type",
+ "id": "@id"
+ },
+ "type": "EPCISDocument",
+ "schemaVersion": "2.0",
+ "creationDate": "2024-03-01T08:00:00Z",
+ "epcisBody": {
+ "eventList": [{
+ "type": "ObjectEvent",
+ "eventTime": "2024-03-01T08:00:00.000Z",
+ "eventTimeZoneOffset": "+00:00",
+ "epcList": ["urn:epc:id:sgtin:4012345.011111.1001"],
+ "action": "ADD",
+ "bizStep": "https://ref.gs1.org/cbv/BizStep-receiving",
+ "disposition": "https://ref.gs1.org/cbv/Disp-in_progress",
+ "readPoint": {"id": "urn:epc:id:sgln:4012345.00001.0"},
+ "bizLocation": {"id": "urn:epc:id:sgln:4012345.00001.0"},
+ "bizTransactionList": [
+ {"type": "https://ref.gs1.org/cbv/BTT-po", "bizTransaction": "urn:epc:id:gdti:4012345.00001.PO-2024-001"}
+ ]
+ }]
+ }
+ },
+ "publishOptions": {
+ "privacy": "private",
+ "epochs": 12
+ }
+ }
+ },
+ {
+ "name": "Event 2: Receive Wheels",
+ "description": "Front and rear wheels arrive from wheel supplier",
+ "request": {
+ "epcisDocument": {
+ "@context": {
+ "@vocab": "https://gs1.github.io/EPCIS/",
+ "epcis": "https://gs1.github.io/EPCIS/",
+ "cbv": "https://ref.gs1.org/cbv/",
+ "type": "@type",
+ "id": "@id"
+ },
+ "type": "EPCISDocument",
+ "schemaVersion": "2.0",
+ "creationDate": "2024-03-01T08:30:00Z",
+ "epcisBody": {
+ "eventList": [{
+ "type": "ObjectEvent",
+ "eventTime": "2024-03-01T08:30:00.000Z",
+ "eventTimeZoneOffset": "+00:00",
+ "epcList": [
+ "urn:epc:id:sgtin:4012345.022222.2001",
+ "urn:epc:id:sgtin:4012345.022222.2002"
+ ],
+ "action": "ADD",
+ "bizStep": "https://ref.gs1.org/cbv/BizStep-receiving",
+ "disposition": "https://ref.gs1.org/cbv/Disp-in_progress",
+ "readPoint": {"id": "urn:epc:id:sgln:4012345.00001.0"},
+ "bizLocation": {"id": "urn:epc:id:sgln:4012345.00001.0"},
+ "bizTransactionList": [
+ {"type": "https://ref.gs1.org/cbv/BTT-po", "bizTransaction": "urn:epc:id:gdti:4012345.00001.PO-2024-002"}
+ ]
+ }]
+ }
+ },
+ "publishOptions": {
+ "privacy": "private",
+ "epochs": 12
+ }
+ }
+ },
+ {
+ "name": "Event 3: Receive Handlebar",
+ "description": "Aluminum handlebar arrives",
+ "request": {
+ "epcisDocument": {
+ "@context": {
+ "@vocab": "https://gs1.github.io/EPCIS/",
+ "epcis": "https://gs1.github.io/EPCIS/",
+ "cbv": "https://ref.gs1.org/cbv/",
+ "type": "@type",
+ "id": "@id"
+ },
+ "type": "EPCISDocument",
+ "schemaVersion": "2.0",
+ "creationDate": "2024-03-01T09:00:00Z",
+ "epcisBody": {
+ "eventList": [{
+ "type": "ObjectEvent",
+ "eventTime": "2024-03-01T09:00:00.000Z",
+ "eventTimeZoneOffset": "+00:00",
+ "epcList": ["urn:epc:id:sgtin:4012345.033333.3001"],
+ "action": "ADD",
+ "bizStep": "https://ref.gs1.org/cbv/BizStep-receiving",
+ "disposition": "https://ref.gs1.org/cbv/Disp-in_progress",
+ "readPoint": {"id": "urn:epc:id:sgln:4012345.00001.0"},
+ "bizLocation": {"id": "urn:epc:id:sgln:4012345.00001.0"}
+ }]
+ }
+ },
+ "publishOptions": {
+ "privacy": "private",
+ "epochs": 12
+ }
+ }
+ },
+ {
+ "name": "Event 4: Inspect Frame",
+ "description": "Quality check on carbon fiber frame - PASSED",
+ "request": {
+ "epcisDocument": {
+ "@context": {
+ "@vocab": "https://gs1.github.io/EPCIS/",
+ "epcis": "https://gs1.github.io/EPCIS/",
+ "cbv": "https://ref.gs1.org/cbv/",
+ "type": "@type",
+ "id": "@id"
+ },
+ "type": "EPCISDocument",
+ "schemaVersion": "2.0",
+ "creationDate": "2024-03-01T10:00:00Z",
+ "epcisBody": {
+ "eventList": [{
+ "type": "ObjectEvent",
+ "eventTime": "2024-03-01T10:00:00.000Z",
+ "eventTimeZoneOffset": "+00:00",
+ "epcList": ["urn:epc:id:sgtin:4012345.011111.1001"],
+ "action": "OBSERVE",
+ "bizStep": "https://ref.gs1.org/cbv/BizStep-inspecting",
+ "disposition": "https://ref.gs1.org/cbv/Disp-conformant",
+ "readPoint": {"id": "urn:epc:id:sgln:4012345.00002.0"},
+ "bizLocation": {"id": "urn:epc:id:sgln:4012345.00002.0"}
+ }]
+ }
+ },
+ "publishOptions": {
+ "privacy": "private",
+ "epochs": 12
+ }
+ }
+ },
+ {
+ "name": "Event 5: Inspect Wheels",
+ "description": "Quality check on both wheels - PASSED",
+ "request": {
+ "epcisDocument": {
+ "@context": {
+ "@vocab": "https://gs1.github.io/EPCIS/",
+ "epcis": "https://gs1.github.io/EPCIS/",
+ "cbv": "https://ref.gs1.org/cbv/",
+ "type": "@type",
+ "id": "@id"
+ },
+ "type": "EPCISDocument",
+ "schemaVersion": "2.0",
+ "creationDate": "2024-03-01T10:30:00Z",
+ "epcisBody": {
+ "eventList": [{
+ "type": "ObjectEvent",
+ "eventTime": "2024-03-01T10:30:00.000Z",
+ "eventTimeZoneOffset": "+00:00",
+ "epcList": [
+ "urn:epc:id:sgtin:4012345.022222.2001",
+ "urn:epc:id:sgtin:4012345.022222.2002"
+ ],
+ "action": "OBSERVE",
+ "bizStep": "https://ref.gs1.org/cbv/BizStep-inspecting",
+ "disposition": "https://ref.gs1.org/cbv/Disp-conformant",
+ "readPoint": {"id": "urn:epc:id:sgln:4012345.00002.0"},
+ "bizLocation": {"id": "urn:epc:id:sgln:4012345.00002.0"}
+ }]
+ }
+ },
+ "publishOptions": {
+ "privacy": "private",
+ "epochs": 12
+ }
+ }
+ },
+ {
+ "name": "Event 6: Assemble Bicycle",
+ "description": "π§ TRANSFORMATION: Components assembled into finished bicycle",
+ "request": {
+ "epcisDocument": {
+ "@context": {
+ "@vocab": "https://gs1.github.io/EPCIS/",
+ "epcis": "https://gs1.github.io/EPCIS/",
+ "cbv": "https://ref.gs1.org/cbv/",
+ "type": "@type",
+ "id": "@id"
+ },
+ "type": "EPCISDocument",
+ "schemaVersion": "2.0",
+ "creationDate": "2024-03-01T14:00:00Z",
+ "epcisBody": {
+ "eventList": [{
+ "type": "TransformationEvent",
+ "eventTime": "2024-03-01T14:00:00.000Z",
+ "eventTimeZoneOffset": "+00:00",
+ "inputEPCList": [
+ "urn:epc:id:sgtin:4012345.011111.1001",
+ "urn:epc:id:sgtin:4012345.022222.2001",
+ "urn:epc:id:sgtin:4012345.022222.2002",
+ "urn:epc:id:sgtin:4012345.033333.3001"
+ ],
+ "outputEPCList": [
+ "urn:epc:id:sgtin:4012345.099999.9001"
+ ],
+ "bizStep": "https://ref.gs1.org/cbv/BizStep-assembling",
+ "disposition": "https://ref.gs1.org/cbv/Disp-active",
+ "readPoint": {"id": "urn:epc:id:sgln:4012345.00003.0"},
+ "bizLocation": {"id": "urn:epc:id:sgln:4012345.00003.0"}
+ }]
+ }
+ },
+ "publishOptions": {
+ "privacy": "private",
+ "epochs": 12
+ }
+ }
+ },
+ {
+ "name": "Event 7: Final Quality Check",
+ "description": "Finished bicycle passes final inspection",
+ "request": {
+ "epcisDocument": {
+ "@context": {
+ "@vocab": "https://gs1.github.io/EPCIS/",
+ "epcis": "https://gs1.github.io/EPCIS/",
+ "cbv": "https://ref.gs1.org/cbv/",
+ "type": "@type",
+ "id": "@id"
+ },
+ "type": "EPCISDocument",
+ "schemaVersion": "2.0",
+ "creationDate": "2024-03-01T15:00:00Z",
+ "epcisBody": {
+ "eventList": [{
+ "type": "ObjectEvent",
+ "eventTime": "2024-03-01T15:00:00.000Z",
+ "eventTimeZoneOffset": "+00:00",
+ "epcList": ["urn:epc:id:sgtin:4012345.099999.9001"],
+ "action": "OBSERVE",
+ "bizStep": "https://ref.gs1.org/cbv/BizStep-inspecting",
+ "disposition": "https://ref.gs1.org/cbv/Disp-conformant",
+ "readPoint": {"id": "urn:epc:id:sgln:4012345.00002.0"},
+ "bizLocation": {"id": "urn:epc:id:sgln:4012345.00002.0"}
+ }]
+ }
+ },
+ "publishOptions": {
+ "privacy": "private",
+ "epochs": 12
+ }
+ }
+ },
+ {
+ "name": "Event 8: Pack Bicycle",
+ "description": "π¦ AGGREGATION: Bicycle packed onto shipping pallet",
+ "request": {
+ "epcisDocument": {
+ "@context": {
+ "@vocab": "https://gs1.github.io/EPCIS/",
+ "epcis": "https://gs1.github.io/EPCIS/",
+ "cbv": "https://ref.gs1.org/cbv/",
+ "type": "@type",
+ "id": "@id"
+ },
+ "type": "EPCISDocument",
+ "schemaVersion": "2.0",
+ "creationDate": "2024-03-01T16:00:00Z",
+ "epcisBody": {
+ "eventList": [{
+ "type": "AggregationEvent",
+ "eventTime": "2024-03-01T16:00:00.000Z",
+ "eventTimeZoneOffset": "+00:00",
+ "parentID": "urn:epc:id:sscc:4012345.0000000001",
+ "childEPCs": [
+ "urn:epc:id:sgtin:4012345.099999.9001"
+ ],
+ "action": "ADD",
+ "bizStep": "https://ref.gs1.org/cbv/BizStep-packing",
+ "disposition": "https://ref.gs1.org/cbv/Disp-in_transit",
+ "readPoint": {"id": "urn:epc:id:sgln:4012345.00004.0"},
+ "bizLocation": {"id": "urn:epc:id:sgln:4012345.00004.0"}
+ }]
+ }
+ },
+ "publishOptions": {
+ "privacy": "private",
+ "epochs": 12
+ }
+ }
+ },
+ {
+ "name": "Event 9: Ship Pallet",
+ "description": "π Pallet shipped to customer",
+ "request": {
+ "epcisDocument": {
+ "@context": {
+ "@vocab": "https://gs1.github.io/EPCIS/",
+ "epcis": "https://gs1.github.io/EPCIS/",
+ "cbv": "https://ref.gs1.org/cbv/",
+ "type": "@type",
+ "id": "@id"
+ },
+ "type": "EPCISDocument",
+ "schemaVersion": "2.0",
+ "creationDate": "2024-03-02T08:00:00Z",
+ "epcisBody": {
+ "eventList": [{
+ "type": "ObjectEvent",
+ "eventTime": "2024-03-02T08:00:00.000Z",
+ "eventTimeZoneOffset": "+00:00",
+ "epcList": ["urn:epc:id:sscc:4012345.0000000001"],
+ "action": "OBSERVE",
+ "bizStep": "https://ref.gs1.org/cbv/BizStep-shipping",
+ "disposition": "https://ref.gs1.org/cbv/Disp-in_transit",
+ "readPoint": {"id": "urn:epc:id:sgln:4012345.00005.0"},
+ "bizLocation": {"id": "urn:epc:id:sgln:4012345.00005.0"},
+ "bizTransactionList": [
+ {"type": "https://ref.gs1.org/cbv/BTT-desadv", "bizTransaction": "urn:epc:id:gdti:4012345.00001.ASN-2024-001"}
+ ]
+ }]
+ }
+ },
+ "publishOptions": {
+ "privacy": "private",
+ "epochs": 12
+ }
+ }
+ }
+ ],
+
+ "testQueries": {
+ "trackFrame": {
+ "description": "Track the carbon frame from receiving through assembly",
+ "epc": "urn:epc:id:sgtin:4012345.011111.1001",
+ "fullTrace": true,
+ "expectedEvents": ["receiving", "inspecting", "assembling (as input)"]
+ },
+ "trackBicycle": {
+ "description": "Track the finished bicycle from creation to shipping",
+ "epc": "urn:epc:id:sgtin:4012345.099999.9001",
+ "fullTrace": true,
+ "expectedEvents": ["assembling (as output)", "inspecting", "packing (as child)", "shipping"]
+ },
+ "findAllReceiving": {
+ "description": "Find all items received on March 1st",
+ "bizStep": "receiving",
+ "from": "2024-03-01T00:00:00Z",
+ "to": "2024-03-01T23:59:59Z"
+ },
+ "findAssemblyEvents": {
+ "description": "Find all assembly operations",
+ "bizStep": "assembling"
+ },
+ "findAtQualityLab": {
+ "description": "Find all events at the quality lab",
+ "bizLocation": "urn:epc:id:sgln:4012345.00002.0"
+ }
+ }
+}
diff --git a/packages/plugin-epcis/test-data/load-test-events.sh b/packages/plugin-epcis/test-data/load-test-events.sh
new file mode 100755
index 0000000..8a70f53
--- /dev/null
+++ b/packages/plugin-epcis/test-data/load-test-events.sh
@@ -0,0 +1,73 @@
+#!/bin/bash
+# Load Bicycle Manufacturing Story - EPCIS Test Events
+# Usage: ./load-test-events.sh [BASE_URL]
+
+BASE_URL="${1:-http://localhost:9200}"
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+DATA_FILE="$SCRIPT_DIR/bicycle-manufacturing-story.json"
+
+echo "π΄ Alpine Cycles - Bicycle Manufacturing Story"
+echo "================================================"
+echo "Loading EPCIS events to: $BASE_URL"
+echo ""
+
+# Check if jq is available
+if ! command -v jq &> /dev/null; then
+ echo "β jq is required but not installed. Install with: apt install jq"
+ exit 1
+fi
+
+# Load each event
+EVENT_COUNT=$(jq '.events | length' "$DATA_FILE")
+echo "π¦ Found $EVENT_COUNT events to load"
+echo ""
+
+for i in $(seq 0 $((EVENT_COUNT - 1))); do
+ EVENT_NAME=$(jq -r ".events[$i].name" "$DATA_FILE")
+ EVENT_DESC=$(jq -r ".events[$i].description" "$DATA_FILE")
+
+ echo "[$((i + 1))/$EVENT_COUNT] $EVENT_NAME"
+ echo " $EVENT_DESC"
+
+ # Extract and send the request (contains epcisDocument + publishOptions)
+ REQUEST=$(jq ".events[$i].request" "$DATA_FILE")
+
+ RESPONSE=$(curl -s -X POST "$BASE_URL/epcis/capture" \
+ -H "Content-Type: application/json" \
+ -d "$REQUEST")
+
+ CAPTURE_ID=$(echo "$RESPONSE" | jq -r '.captureID // "error"')
+ STATUS=$(echo "$RESPONSE" | jq -r '.status // "error"')
+
+ if [ "$CAPTURE_ID" != "error" ] && [ "$CAPTURE_ID" != "null" ]; then
+ echo " β
Captured: ID=$CAPTURE_ID"
+ else
+ echo " β Failed: $RESPONSE"
+ fi
+ echo ""
+
+ # Small delay to avoid overwhelming the server
+ sleep 0.5
+done
+
+echo "================================================"
+echo "β
All events loaded!"
+echo ""
+echo "π Test Queries to Try:"
+echo ""
+echo "1. Track the carbon frame through assembly:"
+echo " curl '$BASE_URL/epcis/events?epc=urn:epc:id:sgtin:4012345.011111.1001&fullTrace=true'"
+echo ""
+echo "2. Track the finished bicycle:"
+echo " curl '$BASE_URL/epcis/events?epc=urn:epc:id:sgtin:4012345.099999.9001&fullTrace=true'"
+echo ""
+echo "3. Find all receiving events:"
+echo " curl '$BASE_URL/epcis/events?bizStep=receiving'"
+echo ""
+echo "4. Find assembly operations:"
+echo " curl '$BASE_URL/epcis/events?bizStep=assembling'"
+echo ""
+echo "5. Find events at quality lab:"
+echo " curl '$BASE_URL/epcis/events?bizLocation=urn:epc:id:sgln:4012345.00002.0'"
+echo ""
+
diff --git a/packages/plugin-epcis/tests/plugin-epcis.spec.ts b/packages/plugin-epcis/tests/plugin-epcis.spec.ts
new file mode 100644
index 0000000..f1e5e67
--- /dev/null
+++ b/packages/plugin-epcis/tests/plugin-epcis.spec.ts
@@ -0,0 +1,80 @@
+import { describe, it, beforeEach, afterEach } from "mocha";
+import { expect } from "chai";
+import sinon from "sinon";
+import pluginEpcisPlugin from "../dist/index.js";
+import {
+ createExpressApp,
+ createInMemoryBlobStorage,
+ createMcpServerClientPair,
+ createMockDkgClient,
+} from "@dkg/plugins/testing";
+import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
+import express from "express";
+// import request from "supertest";
+
+// Mock DKG context
+const mockDkgContext = {
+ dkg: createMockDkgClient(),
+ blob: createInMemoryBlobStorage(),
+};
+
+describe("@dkg/plugin-epcis checks", function () {
+ let mockMcpServer: McpServer;
+ let mockMcpClient: Client;
+ let apiRouter: express.Router;
+ let app: express.Application;
+
+ this.timeout(5000);
+
+ beforeEach(async () => {
+ const { server, client, connect } = await createMcpServerClientPair();
+ mockMcpServer = server;
+ mockMcpClient = client;
+ apiRouter = express.Router();
+ app = createExpressApp();
+
+ // Initialize plugin
+ pluginEpcisPlugin(mockDkgContext, mockMcpServer, apiRouter);
+ await connect();
+ app.use("/", apiRouter);
+ });
+
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ describe("Plugin Configuration", () => {
+ it("should create plugin without errors", () => {
+ expect(pluginEpcisPlugin).to.be.a("function");
+ });
+ });
+
+ describe("Core Functionality", () => {
+ it("should register tools or endpoints", async () => {
+ // TODO: Replace this placeholder with your actual tests!
+ // Example for MCP tools:
+ // const tools = await mockMcpClient.listTools().then((r) => r.tools);
+ // expect(tools.some((t) => t.name === "your-tool-name")).to.equal(true);
+
+ // Example for API endpoints:
+ // request(app).get("/your-endpoint").expect(200);
+
+ throw new Error(
+ "TODO: Replace placeholder test with your actual plugin functionality tests",
+ );
+ });
+ });
+
+ describe("Error Handling", () => {
+ it("should handle invalid parameters", async () => {
+ // TODO: Replace this placeholder with your actual error handling tests!
+ // Example:
+ // await request(app).get("/invalid-endpoint").expect(400);
+
+ throw new Error(
+ "TODO: Replace placeholder test with your actual error handling tests",
+ );
+ });
+ });
+});
diff --git a/packages/plugin-epcis/tsconfig.json b/packages/plugin-epcis/tsconfig.json
new file mode 100644
index 0000000..aff3847
--- /dev/null
+++ b/packages/plugin-epcis/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "@dkg/typescript-config/base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}