weshipyou-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +33 -0
- package/README.md +49 -0
- package/dist/chunk-KT3VLIZI.mjs +18 -0
- package/dist/chunk-KT3VLIZI.mjs.map +1 -0
- package/dist/chunk-RM3JFTCX.cjs +18 -0
- package/dist/chunk-RM3JFTCX.cjs.map +1 -0
- package/dist/cli.js +20 -0
- package/dist/cli.js.map +1 -0
- package/dist/enterprise.cjs +73 -0
- package/dist/enterprise.cjs.map +1 -0
- package/dist/enterprise.d.mts +398 -0
- package/dist/enterprise.d.ts +398 -0
- package/dist/enterprise.mjs +73 -0
- package/dist/enterprise.mjs.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +221 -0
- package/dist/index.d.ts +221 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/dist/update-shipment.use-case-BsHiVqQV.d.mts +504 -0
- package/dist/update-shipment.use-case-BsHiVqQV.d.ts +504 -0
- package/package.json +106 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Dazai Osamu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
1. The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
2. ATTRIBUTION REQUIREMENT: Any application, service, or derivative work that
|
|
16
|
+
incorporates or uses this Software, in whole or in part, MUST display
|
|
17
|
+
prominently within its user interface (e.g., in an "About", "Credits", or
|
|
18
|
+
"Settings" section) the following attribution:
|
|
19
|
+
|
|
20
|
+
"Powered by WeShipYou SDK — https://github.com/dazaiosamu/weshipyou-sdk
|
|
21
|
+
Contact: dazaiosamu.2b2t@gmail.com"
|
|
22
|
+
|
|
23
|
+
This attribution may be placed in a legal/notices page, settings menu, or
|
|
24
|
+
about dialog, but must be accessible to any end user without requiring
|
|
25
|
+
developer tools, source code access, or command-line inspection.
|
|
26
|
+
|
|
27
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
28
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
29
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
30
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
31
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
32
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
33
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# WeShipYou SDK
|
|
2
|
+
|
|
3
|
+
SDK oficial en TypeScript para la API de WeShipYou. Gestión de envíos, tarifas, recargas móviles, webhooks, multi-tenancy, CQRS, auditoría y más.
|
|
4
|
+
|
|
5
|
+
## Instalación
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install weshipyou-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Uso rápido
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { bootstrap } from 'weshipyou-sdk'
|
|
15
|
+
|
|
16
|
+
const { authService, ratesService, shipmentService } = bootstrap({
|
|
17
|
+
baseURL: 'https://weshipyou.com/api/v1',
|
|
18
|
+
email: 'tu@email.com',
|
|
19
|
+
password: 'tu-password',
|
|
20
|
+
accountId: 'tu-account-id',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// Obtener tarifas
|
|
24
|
+
const rates = await ratesService.getRates({
|
|
25
|
+
accountId: 'CU4MTDLV',
|
|
26
|
+
totalValue: 100,
|
|
27
|
+
incoterms: 'DAP',
|
|
28
|
+
sender: { /* ... */ },
|
|
29
|
+
recipient: { /* ... */ },
|
|
30
|
+
parcels: [ /* ... */ ],
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Ver [documentación completa](./docs/).
|
|
35
|
+
|
|
36
|
+
## Licencia
|
|
37
|
+
|
|
38
|
+
**WeShipYou SDK** — Copyright (c) 2024 Dazai Osamu
|
|
39
|
+
|
|
40
|
+
Este proyecto está licenciado bajo los términos de la **MIT License** con una cláusula adicional de atribución.
|
|
41
|
+
|
|
42
|
+
**Tú debes incluir en tu aplicación** (en una sección de créditos, "Acerca de", ajustes o legal) el siguiente texto:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Powered by WeShipYou SDK — https://github.com/dazaiosamu/weshipyou-sdk
|
|
46
|
+
Contact: dazaiosamu.2b2t@gmail.com
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Ver el archivo [LICENSE](./LICENSE) para los términos completos.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
var c=class{constructor(e){this.httpClient=e;this.categoryCache=new Map}async getRates(e,t="en-US"){return(await this.httpClient.request({method:"POST",url:"/api/v1/shipments/rates",body:e,headers:{"Accept-Language":t}})).data}async createShipment(e,t="en-US"){return(await this.httpClient.request({method:"POST",url:"/api/v1/shipments",body:e,headers:{"Accept-Language":t}})).data}async updateShipment(e,t,n="en-US"){return(await this.httpClient.request({method:"PUT",url:`/api/v1/shipments/${encodeURIComponent(e)}`,body:t,headers:{"Accept-Language":n}})).data}async getCountryCategories(e,t="en-US"){let n=`${e}:${t}`,a=this.categoryCache.get(n);if(a&&a.expires>Date.now())return a.data;let r=await this.httpClient.request({method:"GET",url:`/api/v1/shipments/country-categories?countryCode=${encodeURIComponent(e)}`,headers:{"Accept-Language":t}});return this.categoryCache.set(n,{data:r.data,expires:Date.now()+3e5}),r.data}};var m=class{constructor(e,t){this.authService=e;this.shipmentService=t}async execute(e,t,n,a,r="en-US"){return await this.authService.authenticate(e,t,r),this.shipmentService.updateShipment(n,a,r)}};import{randomUUID as E}from"crypto";import I from"better-sqlite3";var d=class{constructor(e=":memory:"){this.db=new I(e),this.db.exec(`
|
|
2
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
3
|
+
id TEXT PRIMARY KEY,
|
|
4
|
+
aggregate_id TEXT NOT NULL,
|
|
5
|
+
type TEXT NOT NULL,
|
|
6
|
+
payload TEXT NOT NULL,
|
|
7
|
+
timestamp TEXT NOT NULL,
|
|
8
|
+
version INTEGER NOT NULL
|
|
9
|
+
)
|
|
10
|
+
`),this.db.exec("CREATE INDEX IF NOT EXISTS idx_aggregate ON events(aggregate_id)"),this.db.exec("CREATE INDEX IF NOT EXISTS idx_timestamp ON events(timestamp)")}async append(e,t){let n=this.db.prepare(`
|
|
11
|
+
INSERT OR IGNORE INTO events (id, aggregate_id, type, payload, timestamp, version)
|
|
12
|
+
VALUES (@id, @aggregateId, @type, @payload, @timestamp, @version)
|
|
13
|
+
`);this.db.transaction(r=>{for(let i of r)n.run({id:i.eventId||E(),aggregateId:i.aggregateId,type:i.type,payload:JSON.stringify(i.payload),timestamp:i.timestamp,version:i.version})})(t)}async getByAggregate(e,t=0){return this.db.prepare(`
|
|
14
|
+
SELECT * FROM events
|
|
15
|
+
WHERE aggregate_id = ? AND version >= ?
|
|
16
|
+
ORDER BY version ASC
|
|
17
|
+
`).all(e,t).map(a=>({...a,payload:JSON.parse(a.payload)}))}async replay(e,t){let n=e?"SELECT * FROM events WHERE timestamp >= ? ORDER BY timestamp ASC":"SELECT * FROM events ORDER BY timestamp ASC",a=this.db.prepare(n),r=e?a.all(e):a.all();if(!t)return;let i=100;for(let o=0;o<r.length;o+=i){let y=r.slice(o,o+i).map(p=>{let h={...p,payload:JSON.parse(p.payload)};return t(h).catch(v=>{console.error(`Replay error for event ${p.id}:`,v)})});await Promise.allSettled(y)}}close(){this.db.close()}};var g=class{constructor(e,t,n,a=new Date().toISOString(),r=1){this.eventId=e;this.aggregateId=t;this.payload=n;this.timestamp=a;this.version=r;this.type="shipment.created"}},u=class{constructor(e,t,n,a=new Date().toISOString(),r=1){this.eventId=e;this.aggregateId=t;this.payload=n;this.timestamp=a;this.version=r;this.type="shipment.status_changed"}},l=class{constructor(e,t,n,a=new Date().toISOString(),r=1){this.eventId=e;this.aggregateId=t;this.payload=n;this.timestamp=a;this.version=r;this.type="shipment.updated"}};export{c as a,m as b,d as c,g as d,u as e,l as f};
|
|
18
|
+
//# sourceMappingURL=chunk-KT3VLIZI.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/application/services/shipment.service.ts","../src/application/use-cases/update-shipment.use-case.ts","../src/infrastructure/event-store/sqlite-event-store.impl.ts","../src/domain/events/shipment.events.ts"],"sourcesContent":["import { IHttpClient } from '../../domain/interfaces/http-client.interface';\nimport { AcceptLanguage } from '../../domain/interfaces/language.enum';\nimport { IRateRequest, IShipmentRequest } from '../../domain/interfaces/shipment.interface';\n\nexport interface IShipmentService {\n getRates(payload: IRateRequest, lang?: AcceptLanguage): Promise<unknown>;\n createShipment(payload: IShipmentRequest, lang?: AcceptLanguage): Promise<unknown>;\n updateShipment(id: string, payload: Partial<IShipmentRequest>, lang?: AcceptLanguage): Promise<unknown>;\n getCountryCategories(countryCode: string, lang?: AcceptLanguage): Promise<unknown>;\n}\n\nexport class ShipmentService implements IShipmentService {\n private categoryCache = new Map<string, { data: unknown; expires: number }>();\n\n constructor(private readonly httpClient: IHttpClient) {}\n\n async getRates(payload: IRateRequest, lang = AcceptLanguage.EN_US): Promise<unknown> {\n const res = await this.httpClient.request({\n method: 'POST',\n url: '/api/v1/shipments/rates',\n body: payload as unknown as Record<string, unknown>,\n headers: { 'Accept-Language': lang }\n });\n return res.data;\n }\n\n async createShipment(payload: IShipmentRequest, lang = AcceptLanguage.EN_US): Promise<unknown> {\n const res = await this.httpClient.request({\n method: 'POST',\n url: '/api/v1/shipments',\n body: payload as unknown as Record<string, unknown>,\n headers: { 'Accept-Language': lang }\n });\n return res.data;\n }\n\n async updateShipment(id: string, payload: Partial<IShipmentRequest>, lang = AcceptLanguage.EN_US): Promise<unknown> {\n const res = await this.httpClient.request({\n method: 'PUT',\n url: `/api/v1/shipments/${encodeURIComponent(id)}`,\n body: payload as unknown as Record<string, unknown>,\n headers: { 'Accept-Language': lang }\n });\n return res.data;\n }\n\n async getCountryCategories(countryCode: string, lang = AcceptLanguage.EN_US): Promise<unknown> {\n const cacheKey = `${countryCode}:${lang}`;\n const cached = this.categoryCache.get(cacheKey);\n if (cached && cached.expires > Date.now()) return cached.data;\n const res = await this.httpClient.request({\n method: 'GET',\n url: `/api/v1/shipments/country-categories?countryCode=${encodeURIComponent(countryCode)}`,\n headers: { 'Accept-Language': lang }\n });\n this.categoryCache.set(cacheKey, { data: res.data, expires: Date.now() + 300_000 });\n return res.data;\n }\n}\n","import { IAuthService } from '../services/auth.service';\nimport { IShipmentService } from '../services/shipment.service';\nimport { IShipmentRequest } from '../../domain/interfaces/shipment.interface';\nimport { AcceptLanguage } from '../../domain/interfaces/language.enum';\n\nexport class UpdateShipmentUseCase {\n constructor(\n private readonly authService: IAuthService,\n private readonly shipmentService: IShipmentService\n ) {}\n\n async execute(\n username: string,\n password: string,\n shipmentId: string,\n payload: Partial<IShipmentRequest>,\n lang = AcceptLanguage.EN_US\n ): Promise<unknown> {\n await this.authService.authenticate(username, password, lang);\n return this.shipmentService.updateShipment(shipmentId, payload, lang);\n }\n}\n","import { randomUUID } from 'crypto';\nimport { IEventStore, DomainEvent } from '../../domain/interfaces/event-store.interface';\nimport Database from 'better-sqlite3';\n\nexport class SqliteEventStore implements IEventStore {\n private db: Database.Database;\n\n constructor(dbPath: string = ':memory:') {\n this.db = new Database(dbPath);\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n aggregate_id TEXT NOT NULL,\n type TEXT NOT NULL,\n payload TEXT NOT NULL,\n timestamp TEXT NOT NULL,\n version INTEGER NOT NULL\n )\n `);\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_aggregate ON events(aggregate_id)`);\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_timestamp ON events(timestamp)`);\n }\n\n async append(aggregateId: string, events: DomainEvent[]): Promise<void> {\n const insert = this.db.prepare(`\n INSERT OR IGNORE INTO events (id, aggregate_id, type, payload, timestamp, version)\n VALUES (@id, @aggregateId, @type, @payload, @timestamp, @version)\n `);\n\n const tx = this.db.transaction((evts: DomainEvent[]) => {\n for (const evt of evts) {\n insert.run({\n id: evt.eventId || randomUUID(),\n aggregateId: evt.aggregateId,\n type: evt.type,\n payload: JSON.stringify(evt.payload),\n timestamp: evt.timestamp,\n version: evt.version\n });\n }\n });\n\n tx(events);\n }\n\n async getByAggregate(aggregateId: string, sinceVersion = 0): Promise<DomainEvent[]> {\n const rows = this.db.prepare(`\n SELECT * FROM events\n WHERE aggregate_id = ? AND version >= ?\n ORDER BY version ASC\n `).all(aggregateId, sinceVersion) as Array<Record<string, unknown>>;\n\n return rows.map(row => ({\n ...row,\n payload: JSON.parse(row.payload as string)\n })) as DomainEvent[];\n }\n\n async replay(sinceTimestamp?: string, handler?: (event: DomainEvent) => Promise<void>): Promise<void> {\n const query = sinceTimestamp\n ? `SELECT * FROM events WHERE timestamp >= ? ORDER BY timestamp ASC`\n : `SELECT * FROM events ORDER BY timestamp ASC`;\n\n const stmt = this.db.prepare(query);\n const rows = (sinceTimestamp ? stmt.all(sinceTimestamp) : stmt.all()) as Array<Record<string, unknown>>;\n\n if (!handler) return;\n\n const batchSize = 100;\n for (let i = 0; i < rows.length; i += batchSize) {\n const batch = rows.slice(i, i + batchSize).map(row => {\n const event: DomainEvent = { ...row, payload: JSON.parse(row.payload as string) } as DomainEvent;\n return handler(event).catch(err => {\n console.error(`Replay error for event ${row.id}:`, err);\n });\n });\n await Promise.allSettled(batch);\n }\n }\n\n close(): void {\n this.db.close();\n }\n}\n","import { DomainEvent } from '../interfaces/event-store.interface';\n\nexport class ShipmentCreatedEvent implements DomainEvent {\n readonly type = 'shipment.created';\n constructor(\n public readonly eventId: string,\n public readonly aggregateId: string,\n public readonly payload: { accountId: string; trackingNumber?: string },\n public readonly timestamp: string = new Date().toISOString(),\n public readonly version: number = 1\n ) {}\n}\n\nexport class ShipmentStatusChangedEvent implements DomainEvent {\n readonly type = 'shipment.status_changed';\n constructor(\n public readonly eventId: string,\n public readonly aggregateId: string,\n public readonly payload: { status: string; previousStatus: string; updatedAt: string },\n public readonly timestamp: string = new Date().toISOString(),\n public readonly version: number = 1\n ) {}\n}\n\nexport class ShipmentUpdatedEvent implements DomainEvent {\n readonly type = 'shipment.updated';\n constructor(\n public readonly eventId: string,\n public readonly aggregateId: string,\n public readonly payload: { accountId: string; fields: string[] },\n public readonly timestamp: string = new Date().toISOString(),\n public readonly version: number = 1\n ) {}\n}\n"],"mappings":"AAWO,IAAMA,EAAN,KAAkD,CAGvD,YAA6BC,EAAyB,CAAzB,gBAAAA,EAF7B,KAAQ,cAAgB,IAAI,GAE2B,CAEvD,MAAM,SAASC,EAAuBC,UAA+C,CAOnF,OANY,MAAM,KAAK,WAAW,QAAQ,CACxC,OAAQ,OACR,IAAK,0BACL,KAAMD,EACN,QAAS,CAAE,kBAAmBC,CAAK,CACrC,CAAC,GACU,IACb,CAEA,MAAM,eAAeD,EAA2BC,UAA+C,CAO7F,OANY,MAAM,KAAK,WAAW,QAAQ,CACxC,OAAQ,OACR,IAAK,oBACL,KAAMD,EACN,QAAS,CAAE,kBAAmBC,CAAK,CACrC,CAAC,GACU,IACb,CAEA,MAAM,eAAeC,EAAYF,EAAoCC,UAA+C,CAOlH,OANY,MAAM,KAAK,WAAW,QAAQ,CACxC,OAAQ,MACN,IAAK,qBAAqB,mBAAmBC,CAAE,CAAC,GAClD,KAAMF,EACN,QAAS,CAAE,kBAAmBC,CAAK,CACrC,CAAC,GACU,IACb,CAEA,MAAM,qBAAqBE,EAAqBF,UAA+C,CAC7F,IAAMG,EAAW,GAAGD,CAAW,IAAIF,CAAI,GACjCI,EAAS,KAAK,cAAc,IAAID,CAAQ,EAC9C,GAAIC,GAAUA,EAAO,QAAU,KAAK,IAAI,EAAG,OAAOA,EAAO,KACzD,IAAMC,EAAM,MAAM,KAAK,WAAW,QAAQ,CACxC,OAAQ,MACN,IAAK,oDAAoD,mBAAmBH,CAAW,CAAC,GAC1F,QAAS,CAAE,kBAAmBF,CAAK,CACrC,CAAC,EACD,YAAK,cAAc,IAAIG,EAAU,CAAE,KAAME,EAAI,KAAM,QAAS,KAAK,IAAI,EAAI,GAAQ,CAAC,EAC3EA,EAAI,IACb,CACF,ECrDO,IAAMC,EAAN,KAA4B,CACjC,YACmBC,EACAC,EACjB,CAFiB,iBAAAD,EACA,qBAAAC,CAChB,CAEH,MAAM,QACJC,EACAC,EACAC,EACAC,EACAC,UACkB,CAClB,aAAM,KAAK,YAAY,aAAaJ,EAAUC,EAAUG,CAAI,EACrD,KAAK,gBAAgB,eAAeF,EAAYC,EAASC,CAAI,CACtE,CACF,ECrBA,OAAS,cAAAC,MAAkB,SAE3B,OAAOC,MAAc,iBAEd,IAAMC,EAAN,KAA8C,CAGnD,YAAYC,EAAiB,WAAY,CACvC,KAAK,GAAK,IAAIF,EAASE,CAAM,EAC7B,KAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KASZ,EACD,KAAK,GAAG,KAAK,kEAAkE,EAC/E,KAAK,GAAG,KAAK,+DAA+D,CAC9E,CAEA,MAAM,OAAOC,EAAqBC,EAAsC,CACtE,IAAMC,EAAS,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAG9B,EAEU,KAAK,GAAG,YAAaC,GAAwB,CACtD,QAAWC,KAAOD,EAChBD,EAAO,IAAI,CACT,GAAIE,EAAI,SAAWR,EAAW,EAC9B,YAAaQ,EAAI,YACjB,KAAMA,EAAI,KACV,QAAS,KAAK,UAAUA,EAAI,OAAO,EACnC,UAAWA,EAAI,UACf,QAASA,EAAI,OACf,CAAC,CAEL,CAAC,EAEEH,CAAM,CACX,CAEA,MAAM,eAAeD,EAAqBK,EAAe,EAA2B,CAOlF,OANa,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA,KAI5B,EAAE,IAAIL,EAAaK,CAAY,EAEpB,IAAIC,IAAQ,CACtB,GAAGA,EACH,QAAS,KAAK,MAAMA,EAAI,OAAiB,CAC3C,EAAE,CACJ,CAEA,MAAM,OAAOC,EAAyBC,EAAgE,CACpG,IAAMC,EAAQF,EACV,mEACA,8CAEEG,EAAO,KAAK,GAAG,QAAQD,CAAK,EAC5BE,EAAQJ,EAAiBG,EAAK,IAAIH,CAAc,EAAIG,EAAK,IAAI,EAEnE,GAAI,CAACF,EAAS,OAEd,IAAMI,EAAY,IAClB,QAASC,EAAI,EAAGA,EAAIF,EAAK,OAAQE,GAAKD,EAAW,CAC/C,IAAME,EAAQH,EAAK,MAAME,EAAGA,EAAID,CAAS,EAAE,IAAIN,GAAO,CACpD,IAAMS,EAAqB,CAAE,GAAGT,EAAK,QAAS,KAAK,MAAMA,EAAI,OAAiB,CAAE,EAChF,OAAOE,EAAQO,CAAK,EAAE,MAAMC,GAAO,CACjC,QAAQ,MAAM,0BAA0BV,EAAI,EAAE,IAAKU,CAAG,CACxD,CAAC,CACH,CAAC,EACD,MAAM,QAAQ,WAAWF,CAAK,CAChC,CACF,CAEA,OAAc,CACZ,KAAK,GAAG,MAAM,CAChB,CACF,ECjFO,IAAMG,EAAN,KAAkD,CAEvD,YACkBC,EACAC,EACAC,EACAC,EAAoB,IAAI,KAAK,EAAE,YAAY,EAC3CC,EAAkB,EAClC,CALgB,aAAAJ,EACA,iBAAAC,EACA,aAAAC,EACA,eAAAC,EACA,aAAAC,EANlB,KAAS,KAAO,kBAOb,CACL,EAEaC,EAAN,KAAwD,CAE7D,YACkBL,EACAC,EACAC,EACAC,EAAoB,IAAI,KAAK,EAAE,YAAY,EAC3CC,EAAkB,EAClC,CALgB,aAAAJ,EACA,iBAAAC,EACA,aAAAC,EACA,eAAAC,EACA,aAAAC,EANlB,KAAS,KAAO,yBAOb,CACL,EAEaE,EAAN,KAAkD,CAEvD,YACkBN,EACAC,EACAC,EACAC,EAAoB,IAAI,KAAK,EAAE,YAAY,EAC3CC,EAAkB,EAClC,CALgB,aAAAJ,EACA,iBAAAC,EACA,aAAAC,EACA,eAAAC,EACA,aAAAC,EANlB,KAAS,KAAO,kBAOb,CACL","names":["ShipmentService","httpClient","payload","lang","id","countryCode","cacheKey","cached","res","UpdateShipmentUseCase","authService","shipmentService","username","password","shipmentId","payload","lang","randomUUID","Database","SqliteEventStore","dbPath","aggregateId","events","insert","evts","evt","sinceVersion","row","sinceTimestamp","handler","query","stmt","rows","batchSize","i","batch","event","err","ShipmentCreatedEvent","eventId","aggregateId","payload","timestamp","version","ShipmentStatusChangedEvent","ShipmentUpdatedEvent"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }var c=class{constructor(e){this.httpClient=e;this.categoryCache=new Map}async getRates(e,t="en-US"){return(await this.httpClient.request({method:"POST",url:"/api/v1/shipments/rates",body:e,headers:{"Accept-Language":t}})).data}async createShipment(e,t="en-US"){return(await this.httpClient.request({method:"POST",url:"/api/v1/shipments",body:e,headers:{"Accept-Language":t}})).data}async updateShipment(e,t,n="en-US"){return(await this.httpClient.request({method:"PUT",url:`/api/v1/shipments/${encodeURIComponent(e)}`,body:t,headers:{"Accept-Language":n}})).data}async getCountryCategories(e,t="en-US"){let n=`${e}:${t}`,a=this.categoryCache.get(n);if(a&&a.expires>Date.now())return a.data;let r=await this.httpClient.request({method:"GET",url:`/api/v1/shipments/country-categories?countryCode=${encodeURIComponent(e)}`,headers:{"Accept-Language":t}});return this.categoryCache.set(n,{data:r.data,expires:Date.now()+3e5}),r.data}};var m=class{constructor(e,t){this.authService=e;this.shipmentService=t}async execute(e,t,n,a,r="en-US"){return await this.authService.authenticate(e,t,r),this.shipmentService.updateShipment(n,a,r)}};var _crypto = require('crypto');var _bettersqlite3 = require('better-sqlite3'); var _bettersqlite32 = _interopRequireDefault(_bettersqlite3);var d=class{constructor(e=":memory:"){this.db=new (0, _bettersqlite32.default)(e),this.db.exec(`
|
|
2
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
3
|
+
id TEXT PRIMARY KEY,
|
|
4
|
+
aggregate_id TEXT NOT NULL,
|
|
5
|
+
type TEXT NOT NULL,
|
|
6
|
+
payload TEXT NOT NULL,
|
|
7
|
+
timestamp TEXT NOT NULL,
|
|
8
|
+
version INTEGER NOT NULL
|
|
9
|
+
)
|
|
10
|
+
`),this.db.exec("CREATE INDEX IF NOT EXISTS idx_aggregate ON events(aggregate_id)"),this.db.exec("CREATE INDEX IF NOT EXISTS idx_timestamp ON events(timestamp)")}async append(e,t){let n=this.db.prepare(`
|
|
11
|
+
INSERT OR IGNORE INTO events (id, aggregate_id, type, payload, timestamp, version)
|
|
12
|
+
VALUES (@id, @aggregateId, @type, @payload, @timestamp, @version)
|
|
13
|
+
`);this.db.transaction(r=>{for(let i of r)n.run({id:i.eventId||_crypto.randomUUID.call(void 0, ),aggregateId:i.aggregateId,type:i.type,payload:JSON.stringify(i.payload),timestamp:i.timestamp,version:i.version})})(t)}async getByAggregate(e,t=0){return this.db.prepare(`
|
|
14
|
+
SELECT * FROM events
|
|
15
|
+
WHERE aggregate_id = ? AND version >= ?
|
|
16
|
+
ORDER BY version ASC
|
|
17
|
+
`).all(e,t).map(a=>({...a,payload:JSON.parse(a.payload)}))}async replay(e,t){let n=e?"SELECT * FROM events WHERE timestamp >= ? ORDER BY timestamp ASC":"SELECT * FROM events ORDER BY timestamp ASC",a=this.db.prepare(n),r=e?a.all(e):a.all();if(!t)return;let i=100;for(let o=0;o<r.length;o+=i){let y=r.slice(o,o+i).map(p=>{let h={...p,payload:JSON.parse(p.payload)};return t(h).catch(v=>{console.error(`Replay error for event ${p.id}:`,v)})});await Promise.allSettled(y)}}close(){this.db.close()}};var g=class{constructor(e,t,n,a=new Date().toISOString(),r=1){this.eventId=e;this.aggregateId=t;this.payload=n;this.timestamp=a;this.version=r;this.type="shipment.created"}},u= exports.e =class{constructor(e,t,n,a=new Date().toISOString(),r=1){this.eventId=e;this.aggregateId=t;this.payload=n;this.timestamp=a;this.version=r;this.type="shipment.status_changed"}},l= exports.f =class{constructor(e,t,n,a=new Date().toISOString(),r=1){this.eventId=e;this.aggregateId=t;this.payload=n;this.timestamp=a;this.version=r;this.type="shipment.updated"}};exports.a = c; exports.b = m; exports.c = d; exports.d = g; exports.e = u; exports.f = l;
|
|
18
|
+
//# sourceMappingURL=chunk-RM3JFTCX.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/application/services/shipment.service.ts","../src/infrastructure/event-store/sqlite-event-store.impl.ts","../src/domain/events/shipment.events.ts"],"names":["ShipmentService","httpClient","payload","lang","id"],"mappings":"AAWO,6KAAMA,CAAAA,CAAN,KAAkD,CAGvD,WAAA,CAA6BC,CAAAA,CAAyB,CAAzB,IAAA,CAAA,UAAA,CAAAA,CAAAA,CAF7B,IAAA,CAAQ,aAAA,CAAgB,IAAI,GAE2B,CAEvD,MAAM,QAAA,CAASC,CAAAA,CAAuBC,CAAAA,CAAAA,OAAAA,CAA+C,CAOnF,MAAA,CANY,MAAM,IAAA,CAAK,UAAA,CAAW,OAAA,CAAQ,CACxC,MAAA,CAAQ,MAAA,CACR,GAAA,CAAK,yBAAA,CACL,IAAA,CAAMD,CAAAA,CACN,OAAA,CAAS,CAAE,iBAAA,CAAmBC,CAAK,CACrC,CAAC,CAAA,CAAA,CACU,IACb,CAEA,MAAM,cAAA,CAAeD,CAAAA,CAA2BC,CAAAA,CAAAA,OAAAA,CAA+C,CAO7F,MAAA,CANY,MAAM,IAAA,CAAK,UAAA,CAAW,OAAA,CAAQ,CACxC,MAAA,CAAQ,MAAA,CACR,GAAA,CAAK,mBAAA,CACL,IAAA,CAAMD,CAAAA,CACN,OAAA,CAAS,CAAE,iBAAA,CAAmBC,CAAK,CACrC,CAAC,CAAA,CAAA,CACU,IACb,CAEA,MAAM,cAAA,CAAeC,CAAAA,CAAYF,CAAAA,CAAoCC,CAAAA,CAAAA,OAAAA,CAA+C,CAOlH,MAAA,CANY,MAAM,IAAA,CAAK,UAAA,CAAW,OAAA,CAAQ,CACxC,MAAA,CAAQ,KAAA,CACN,GAAA,CAAK,CAAA,kBAAA,EAAqB,kBAAA,CAAmBC,CAAE,CAAC,CAAA,CAAA;AC9BvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAekB,IAAA;AAAA;AAAA;AAsBF,IAAA;AAAA;AAAA;AAAA;AC1C/B,IAAA","file":"/home/osamud/Escritorio/WeShipYou/dist/chunk-RM3JFTCX.cjs","sourcesContent":["import { IHttpClient } from '../../domain/interfaces/http-client.interface';\nimport { AcceptLanguage } from '../../domain/interfaces/language.enum';\nimport { IRateRequest, IShipmentRequest } from '../../domain/interfaces/shipment.interface';\n\nexport interface IShipmentService {\n getRates(payload: IRateRequest, lang?: AcceptLanguage): Promise<unknown>;\n createShipment(payload: IShipmentRequest, lang?: AcceptLanguage): Promise<unknown>;\n updateShipment(id: string, payload: Partial<IShipmentRequest>, lang?: AcceptLanguage): Promise<unknown>;\n getCountryCategories(countryCode: string, lang?: AcceptLanguage): Promise<unknown>;\n}\n\nexport class ShipmentService implements IShipmentService {\n private categoryCache = new Map<string, { data: unknown; expires: number }>();\n\n constructor(private readonly httpClient: IHttpClient) {}\n\n async getRates(payload: IRateRequest, lang = AcceptLanguage.EN_US): Promise<unknown> {\n const res = await this.httpClient.request({\n method: 'POST',\n url: '/api/v1/shipments/rates',\n body: payload as unknown as Record<string, unknown>,\n headers: { 'Accept-Language': lang }\n });\n return res.data;\n }\n\n async createShipment(payload: IShipmentRequest, lang = AcceptLanguage.EN_US): Promise<unknown> {\n const res = await this.httpClient.request({\n method: 'POST',\n url: '/api/v1/shipments',\n body: payload as unknown as Record<string, unknown>,\n headers: { 'Accept-Language': lang }\n });\n return res.data;\n }\n\n async updateShipment(id: string, payload: Partial<IShipmentRequest>, lang = AcceptLanguage.EN_US): Promise<unknown> {\n const res = await this.httpClient.request({\n method: 'PUT',\n url: `/api/v1/shipments/${encodeURIComponent(id)}`,\n body: payload as unknown as Record<string, unknown>,\n headers: { 'Accept-Language': lang }\n });\n return res.data;\n }\n\n async getCountryCategories(countryCode: string, lang = AcceptLanguage.EN_US): Promise<unknown> {\n const cacheKey = `${countryCode}:${lang}`;\n const cached = this.categoryCache.get(cacheKey);\n if (cached && cached.expires > Date.now()) return cached.data;\n const res = await this.httpClient.request({\n method: 'GET',\n url: `/api/v1/shipments/country-categories?countryCode=${encodeURIComponent(countryCode)}`,\n headers: { 'Accept-Language': lang }\n });\n this.categoryCache.set(cacheKey, { data: res.data, expires: Date.now() + 300_000 });\n return res.data;\n }\n}\n","import { randomUUID } from 'crypto';\nimport { IEventStore, DomainEvent } from '../../domain/interfaces/event-store.interface';\nimport Database from 'better-sqlite3';\n\nexport class SqliteEventStore implements IEventStore {\n private db: Database.Database;\n\n constructor(dbPath: string = ':memory:') {\n this.db = new Database(dbPath);\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n aggregate_id TEXT NOT NULL,\n type TEXT NOT NULL,\n payload TEXT NOT NULL,\n timestamp TEXT NOT NULL,\n version INTEGER NOT NULL\n )\n `);\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_aggregate ON events(aggregate_id)`);\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_timestamp ON events(timestamp)`);\n }\n\n async append(aggregateId: string, events: DomainEvent[]): Promise<void> {\n const insert = this.db.prepare(`\n INSERT OR IGNORE INTO events (id, aggregate_id, type, payload, timestamp, version)\n VALUES (@id, @aggregateId, @type, @payload, @timestamp, @version)\n `);\n\n const tx = this.db.transaction((evts: DomainEvent[]) => {\n for (const evt of evts) {\n insert.run({\n id: evt.eventId || randomUUID(),\n aggregateId: evt.aggregateId,\n type: evt.type,\n payload: JSON.stringify(evt.payload),\n timestamp: evt.timestamp,\n version: evt.version\n });\n }\n });\n\n tx(events);\n }\n\n async getByAggregate(aggregateId: string, sinceVersion = 0): Promise<DomainEvent[]> {\n const rows = this.db.prepare(`\n SELECT * FROM events\n WHERE aggregate_id = ? AND version >= ?\n ORDER BY version ASC\n `).all(aggregateId, sinceVersion) as Array<Record<string, unknown>>;\n\n return rows.map(row => ({\n ...row,\n payload: JSON.parse(row.payload as string)\n })) as DomainEvent[];\n }\n\n async replay(sinceTimestamp?: string, handler?: (event: DomainEvent) => Promise<void>): Promise<void> {\n const query = sinceTimestamp\n ? `SELECT * FROM events WHERE timestamp >= ? ORDER BY timestamp ASC`\n : `SELECT * FROM events ORDER BY timestamp ASC`;\n\n const stmt = this.db.prepare(query);\n const rows = (sinceTimestamp ? stmt.all(sinceTimestamp) : stmt.all()) as Array<Record<string, unknown>>;\n\n if (!handler) return;\n\n const batchSize = 100;\n for (let i = 0; i < rows.length; i += batchSize) {\n const batch = rows.slice(i, i + batchSize).map(row => {\n const event: DomainEvent = { ...row, payload: JSON.parse(row.payload as string) } as DomainEvent;\n return handler(event).catch(err => {\n console.error(`Replay error for event ${row.id}:`, err);\n });\n });\n await Promise.allSettled(batch);\n }\n }\n\n close(): void {\n this.db.close();\n }\n}\n","import { DomainEvent } from '../interfaces/event-store.interface';\n\nexport class ShipmentCreatedEvent implements DomainEvent {\n readonly type = 'shipment.created';\n constructor(\n public readonly eventId: string,\n public readonly aggregateId: string,\n public readonly payload: { accountId: string; trackingNumber?: string },\n public readonly timestamp: string = new Date().toISOString(),\n public readonly version: number = 1\n ) {}\n}\n\nexport class ShipmentStatusChangedEvent implements DomainEvent {\n readonly type = 'shipment.status_changed';\n constructor(\n public readonly eventId: string,\n public readonly aggregateId: string,\n public readonly payload: { status: string; previousStatus: string; updatedAt: string },\n public readonly timestamp: string = new Date().toISOString(),\n public readonly version: number = 1\n ) {}\n}\n\nexport class ShipmentUpdatedEvent implements DomainEvent {\n readonly type = 'shipment.updated';\n constructor(\n public readonly eventId: string,\n public readonly aggregateId: string,\n public readonly payload: { accountId: string; fields: string[] },\n public readonly timestamp: string = new Date().toISOString(),\n public readonly version: number = 1\n ) {}\n}\n"]}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";var I=Object.create;var y=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var C=Object.getPrototypeOf,b=Object.prototype.hasOwnProperty;var w=(n,e,t,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of D(e))!b.call(n,o)&&o!==t&&y(n,o,{get:()=>e[o],enumerable:!(a=f(e,o))||a.enumerable});return n};var _=(n,e,t)=>(t=n!=null?I(C(n)):{},w(e||!n||!n.__esModule?y(t,"default",{value:n,enumerable:!0}):t,n));var h=require("commander");var v=require("commander"),s=require("fs"),i=require("path"),c=class{getCommand(){return new v.Command("init").description("Initialize project with WeShipYou SDK base configuration").option("-n, --name <name>","Project name","weshipyou-project").option("-d, --dir <dir>","Target directory",".").action(async e=>{await this.execute(e)})}async execute(e){let t=(0,i.join)(e.dir,e.name);(0,s.existsSync)(t)||(0,s.mkdirSync)(t,{recursive:!0}),(0,s.writeFileSync)((0,i.join)(t,".env.example"),["WSY_BASE_URL=https://weshipyou.com/api/v1","WSY_USERNAME=your_username","WSY_PASSWORD=your_password","WSY_ACCOUNT_ID=your_account_id","WSY_WEBHOOK_SECRET=your_webhook_secret","OTEL_ENDPOINT=http://localhost:4318/v1/traces",""].join(`
|
|
3
|
+
`)),(0,s.writeFileSync)((0,i.join)(t,"package.json"),JSON.stringify({name:e.name,version:"1.0.0",private:!0,scripts:{start:"node dist/main.js",build:"tsc",test:"jest"},dependencies:{"weshipyou-sdk":"^1.0.0"}},null,2)),(0,s.writeFileSync)((0,i.join)(t,"tsconfig.json"),JSON.stringify({compilerOptions:{target:"ES2022",module:"commonjs",lib:["ES2022"],outDir:"dist",rootDir:"src",strict:!0,esModuleInterop:!0,resolveJsonModule:!0,declaration:!0},include:["src"]},null,2)),console.log(`Project initialized at ${t}`),console.log("Next steps:"),console.log(` cd ${t}`),console.log(" npm install"),console.log(" cp .env.example .env # configure your credentials"),console.log(" npm start")}};var T=require("commander");var u=require("crypto"),S=_(require("better-sqlite3")),d=class{constructor(e=":memory:"){this.db=new S.default(e),this.db.exec(`
|
|
4
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
5
|
+
id TEXT PRIMARY KEY,
|
|
6
|
+
aggregate_id TEXT NOT NULL,
|
|
7
|
+
type TEXT NOT NULL,
|
|
8
|
+
payload TEXT NOT NULL,
|
|
9
|
+
timestamp TEXT NOT NULL,
|
|
10
|
+
version INTEGER NOT NULL
|
|
11
|
+
)
|
|
12
|
+
`),this.db.exec("CREATE INDEX IF NOT EXISTS idx_aggregate ON events(aggregate_id)"),this.db.exec("CREATE INDEX IF NOT EXISTS idx_timestamp ON events(timestamp)")}async append(e,t){let a=this.db.prepare(`
|
|
13
|
+
INSERT OR IGNORE INTO events (id, aggregate_id, type, payload, timestamp, version)
|
|
14
|
+
VALUES (@id, @aggregateId, @type, @payload, @timestamp, @version)
|
|
15
|
+
`);this.db.transaction(m=>{for(let r of m)a.run({id:r.eventId||(0,u.randomUUID)(),aggregateId:r.aggregateId,type:r.type,payload:JSON.stringify(r.payload),timestamp:r.timestamp,version:r.version})})(t)}async getByAggregate(e,t=0){return this.db.prepare(`
|
|
16
|
+
SELECT * FROM events
|
|
17
|
+
WHERE aggregate_id = ? AND version >= ?
|
|
18
|
+
ORDER BY version ASC
|
|
19
|
+
`).all(e,t).map(o=>({...o,payload:JSON.parse(o.payload)}))}async replay(e,t){let a=e?"SELECT * FROM events WHERE timestamp >= ? ORDER BY timestamp ASC":"SELECT * FROM events ORDER BY timestamp ASC",o=this.db.prepare(a),m=e?o.all(e):o.all();if(!t)return;let r=100;for(let p=0;p<m.length;p+=r){let N=m.slice(p,p+r).map(E=>{let O={...E,payload:JSON.parse(E.payload)};return t(O).catch(R=>{console.error(`Replay error for event ${E.id}:`,R)})});await Promise.allSettled(N)}}close(){this.db.close()}};var l=class{getCommand(){return new T.Command("replay").description("Replay stored events from the event store").option("-s, --since <timestamp>","Replay events since this ISO timestamp").option("-p, --path <path>","SQLite database path",":memory:").action(async e=>{await this.execute(e)})}async execute(e){let t=new d(e.path);try{console.log(`Replaying events from ${e.since||"beginning"}...`);let a=0;await t.replay(e.since,async o=>{console.log(`[${o.timestamp}] ${o.type} (${o.aggregateId})`),a++}),console.log(`Replay complete. ${a} events processed.`)}finally{t.close()}}};var g=new h.Command;g.name("weshipyou").description("WeShipYou SDK Command Line Interface").version("1.0.0");g.addCommand(new c().getCommand());g.addCommand(new l().getCommand());g.parse(process.argv);
|
|
20
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/infrastructure/cli/cli.ts","../src/infrastructure/cli/commands/init.command.ts","../src/infrastructure/cli/commands/replay.command.ts","../src/infrastructure/event-store/sqlite-event-store.impl.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from 'commander';\nimport { InitCommand } from './commands/init.command';\nimport { ReplayCommand } from './commands/replay.command';\n\nconst program = new Command();\n\nprogram\n .name('weshipyou')\n .description('WeShipYou SDK Command Line Interface')\n .version('1.0.0');\n\nprogram.addCommand(new InitCommand().getCommand());\nprogram.addCommand(new ReplayCommand().getCommand());\n\nprogram.parse(process.argv);\n","import { Command } from 'commander';\nimport { writeFileSync, mkdirSync, existsSync } from 'fs';\nimport { join } from 'path';\n\nexport class InitCommand {\n getCommand(): Command {\n return new Command('init')\n .description('Initialize project with WeShipYou SDK base configuration')\n .option('-n, --name <name>', 'Project name', 'weshipyou-project')\n .option('-d, --dir <dir>', 'Target directory', '.')\n .action(async (options) => {\n await this.execute(options);\n });\n }\n\n async execute(options: { name: string; dir: string }): Promise<void> {\n const targetDir = join(options.dir, options.name);\n\n if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });\n\n writeFileSync(join(targetDir, '.env.example'), [\n 'WSY_BASE_URL=https://weshipyou.com/api/v1',\n 'WSY_USERNAME=your_username',\n 'WSY_PASSWORD=your_password',\n 'WSY_ACCOUNT_ID=your_account_id',\n 'WSY_WEBHOOK_SECRET=your_webhook_secret',\n 'OTEL_ENDPOINT=http://localhost:4318/v1/traces',\n ''\n ].join('\\n'));\n\n writeFileSync(join(targetDir, 'package.json'), JSON.stringify({\n name: options.name,\n version: '1.0.0',\n private: true,\n scripts: {\n start: 'node dist/main.js',\n build: 'tsc',\n test: 'jest'\n },\n dependencies: {\n 'weshipyou-sdk': '^1.0.0'\n }\n }, null, 2));\n\n writeFileSync(join(targetDir, 'tsconfig.json'), JSON.stringify({\n compilerOptions: {\n target: 'ES2022',\n module: 'commonjs',\n lib: ['ES2022'],\n outDir: 'dist',\n rootDir: 'src',\n strict: true,\n esModuleInterop: true,\n resolveJsonModule: true,\n declaration: true\n },\n include: ['src']\n }, null, 2));\n\n console.log(`Project initialized at ${targetDir}`);\n console.log('Next steps:');\n console.log(` cd ${targetDir}`);\n console.log(' npm install');\n console.log(' cp .env.example .env # configure your credentials');\n console.log(' npm start');\n }\n}\n","import { Command } from 'commander';\nimport { SqliteEventStore } from '../../event-store/sqlite-event-store.impl';\n\nexport class ReplayCommand {\n getCommand(): Command {\n return new Command('replay')\n .description('Replay stored events from the event store')\n .option('-s, --since <timestamp>', 'Replay events since this ISO timestamp')\n .option('-p, --path <path>', 'SQLite database path', ':memory:')\n .action(async (options) => {\n await this.execute(options);\n });\n }\n\n async execute(options: { since?: string; path: string }): Promise<void> {\n const store = new SqliteEventStore(options.path);\n\n try {\n console.log(`Replaying events from ${options.since || 'beginning'}...`);\n\n let count = 0;\n await store.replay(options.since, async (event) => {\n console.log(`[${event.timestamp}] ${event.type} (${event.aggregateId})`);\n count++;\n });\n\n console.log(`Replay complete. ${count} events processed.`);\n } finally {\n store.close();\n }\n }\n}\n","import { randomUUID } from 'crypto';\nimport { IEventStore, DomainEvent } from '../../domain/interfaces/event-store.interface';\nimport Database from 'better-sqlite3';\n\nexport class SqliteEventStore implements IEventStore {\n private db: Database.Database;\n\n constructor(dbPath: string = ':memory:') {\n this.db = new Database(dbPath);\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n aggregate_id TEXT NOT NULL,\n type TEXT NOT NULL,\n payload TEXT NOT NULL,\n timestamp TEXT NOT NULL,\n version INTEGER NOT NULL\n )\n `);\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_aggregate ON events(aggregate_id)`);\n this.db.exec(`CREATE INDEX IF NOT EXISTS idx_timestamp ON events(timestamp)`);\n }\n\n async append(aggregateId: string, events: DomainEvent[]): Promise<void> {\n const insert = this.db.prepare(`\n INSERT OR IGNORE INTO events (id, aggregate_id, type, payload, timestamp, version)\n VALUES (@id, @aggregateId, @type, @payload, @timestamp, @version)\n `);\n\n const tx = this.db.transaction((evts: DomainEvent[]) => {\n for (const evt of evts) {\n insert.run({\n id: evt.eventId || randomUUID(),\n aggregateId: evt.aggregateId,\n type: evt.type,\n payload: JSON.stringify(evt.payload),\n timestamp: evt.timestamp,\n version: evt.version\n });\n }\n });\n\n tx(events);\n }\n\n async getByAggregate(aggregateId: string, sinceVersion = 0): Promise<DomainEvent[]> {\n const rows = this.db.prepare(`\n SELECT * FROM events\n WHERE aggregate_id = ? AND version >= ?\n ORDER BY version ASC\n `).all(aggregateId, sinceVersion) as Array<Record<string, unknown>>;\n\n return rows.map(row => ({\n ...row,\n payload: JSON.parse(row.payload as string)\n })) as DomainEvent[];\n }\n\n async replay(sinceTimestamp?: string, handler?: (event: DomainEvent) => Promise<void>): Promise<void> {\n const query = sinceTimestamp\n ? `SELECT * FROM events WHERE timestamp >= ? ORDER BY timestamp ASC`\n : `SELECT * FROM events ORDER BY timestamp ASC`;\n\n const stmt = this.db.prepare(query);\n const rows = (sinceTimestamp ? stmt.all(sinceTimestamp) : stmt.all()) as Array<Record<string, unknown>>;\n\n if (!handler) return;\n\n const batchSize = 100;\n for (let i = 0; i < rows.length; i += batchSize) {\n const batch = rows.slice(i, i + batchSize).map(row => {\n const event: DomainEvent = { ...row, payload: JSON.parse(row.payload as string) } as DomainEvent;\n return handler(event).catch(err => {\n console.error(`Replay error for event ${row.id}:`, err);\n });\n });\n await Promise.allSettled(batch);\n }\n }\n\n close(): void {\n this.db.close();\n }\n}\n"],"mappings":"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","names":["import_commander","import_commander","import_fs","import_path","InitCommand","options","targetDir","import_commander","import_crypto","import_better_sqlite3","SqliteEventStore","dbPath","Database","aggregateId","events","insert","evts","evt","sinceVersion","row","sinceTimestamp","handler","query","stmt","rows","batchSize","i","batch","event","err","ReplayCommand","options","store","SqliteEventStore","count","event","program","InitCommand","ReplayCommand"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _chunkRM3JFTCXcjs = require('./chunk-RM3JFTCX.cjs');var _async_hooks = require('async_hooks');var s=class{constructor(){this.storage=new _async_hooks.AsyncLocalStorage}getCurrent(){return this.storage.getStore()||null}set(e){this.storage.enterWith(e)}clear(){this.storage.disable()}async isolate(e,t){return this.storage.run(e,t)}};var d=class{constructor(e){this.schemas=new Map;this.tenantProvider=e}register(e,t){this.schemas.set(e,t)}getCurrentSchema(){let e=this.tenantProvider.getCurrent();return e&&_optionalChain([this, 'access', _2 => _2.schemas, 'access', _3 => _3.get, 'call', _4 => _4(e.tenantId), 'optionalAccess', _5 => _5.schemaName])||null}getSchemaFor(e){return _optionalChain([this, 'access', _6 => _6.schemas, 'access', _7 => _7.get, 'call', _8 => _8(e), 'optionalAccess', _9 => _9.schemaName])||null}deregister(e){this.schemas.delete(e)}};var _pg = require('pg');var m=class{constructor(e,t){this.pool=new (0, _pg.Pool)({connectionString:e,max:10,idleTimeoutMillis:3e4,connectionTimeoutMillis:1e4,...t})}async init(){await this.pool.query(`
|
|
2
|
+
CREATE TABLE IF NOT EXISTS shipment_read_model (
|
|
3
|
+
id TEXT PRIMARY KEY,
|
|
4
|
+
tenant_id TEXT NOT NULL,
|
|
5
|
+
account_id TEXT NOT NULL,
|
|
6
|
+
status TEXT NOT NULL,
|
|
7
|
+
tracking_number TEXT,
|
|
8
|
+
total_value NUMERIC(10,2) NOT NULL,
|
|
9
|
+
incoterms TEXT CHECK (incoterms IN ('DDU', 'DDP')),
|
|
10
|
+
sender_name TEXT NOT NULL,
|
|
11
|
+
recipient_name TEXT NOT NULL,
|
|
12
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
13
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
14
|
+
metadata JSONB
|
|
15
|
+
)
|
|
16
|
+
`),await this.pool.query(`
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_shipment_tenant_account
|
|
18
|
+
ON shipment_read_model (tenant_id, account_id)
|
|
19
|
+
`),await this.pool.query(`
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_shipment_status
|
|
21
|
+
ON shipment_read_model (status)
|
|
22
|
+
`),await this.pool.query(`
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_shipment_created
|
|
24
|
+
ON shipment_read_model (created_at)
|
|
25
|
+
`)}async upsert(e){await this.pool.query(`
|
|
26
|
+
INSERT INTO shipment_read_model (
|
|
27
|
+
id, tenant_id, account_id, status, tracking_number,
|
|
28
|
+
total_value, incoterms, sender_name, recipient_name,
|
|
29
|
+
created_at, updated_at, metadata
|
|
30
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
31
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
32
|
+
status = EXCLUDED.status,
|
|
33
|
+
tracking_number = EXCLUDED.tracking_number,
|
|
34
|
+
total_value = EXCLUDED.total_value,
|
|
35
|
+
incoterms = EXCLUDED.incoterms,
|
|
36
|
+
sender_name = EXCLUDED.sender_name,
|
|
37
|
+
recipient_name = EXCLUDED.recipient_name,
|
|
38
|
+
updated_at = NOW(),
|
|
39
|
+
metadata = EXCLUDED.metadata
|
|
40
|
+
`,[e.id,e.tenantId,e.accountId,e.status,e.trackingNumber,e.totalValue,e.incoterms,e.senderName,e.recipientName,e.createdAt,e.updatedAt,e.metadata?JSON.stringify(e.metadata):null])}async getById(e,t){let r=(await this.pool.query("SELECT * FROM shipment_read_model WHERE id = $1 AND tenant_id = $2",[e,t])).rows[0];return r?{...r,metadata:r.metadata||void 0}:null}async query(e){let t="SELECT * FROM shipment_read_model WHERE tenant_id = $1",a=[e.tenantId],r=2;return e.accountId&&(t+=` AND account_id = $${r++}`,a.push(e.accountId)),e.status&&(t+=` AND status = $${r++}`,a.push(e.status)),t+=" ORDER BY created_at DESC",e.limit&&(t+=` LIMIT $${r++}`,a.push(e.limit)),e.offset&&(t+=` OFFSET $${r++}`,a.push(e.offset)),(await this.pool.query(t,a)).rows.map(o=>({...o,metadata:o.metadata||void 0}))}async close(){await this.pool.end()}};var _redis = require('redis');var p=class{constructor(e,t="weshipyou:events",a="projections",r="shipment-projection"){this.client=_redis.createClient.call(void 0, {url:e}),this.streamName=t,this.groupName=a,this.consumerName=r}async init(){await this.client.connect();try{await this.client.xGroupCreate(this.streamName,this.groupName,"$",{MKSTREAM:!0})}catch(e){if(!(_optionalChain([e, 'optionalAccess', _10 => _10.message])||"").includes("BUSYGROUP"))throw e}}async publish(e){await this.client.xAdd(this.streamName,"*",{eventId:e.eventId,aggregateId:e.aggregateId,type:e.type,payload:JSON.stringify(e.payload),timestamp:e.timestamp,version:e.version.toString()})}async consume(e,t=5e3){let a=await this.client.xReadGroup(this.groupName,this.consumerName,{key:this.streamName,id:">"},{COUNT:10,BLOCK:t});if(!(!a||!_optionalChain([a, 'access', _11 => _11[0], 'optionalAccess', _12 => _12.messages])))for(let r of a[0].messages)try{let n={eventId:r.message.eventId,aggregateId:r.message.aggregateId,type:r.message.type,payload:JSON.parse(r.message.payload),timestamp:r.message.timestamp,version:parseInt(r.message.version,10)};await e.handle(n),await this.client.xAck(this.streamName,this.groupName,r.id)}catch(n){await this.client.xAdd(`${this.streamName}:dead`,"*",r.message),await this.client.xAck(this.streamName,this.groupName,r.id),console.error("Projection consumer error \u2014 moved to dead-letter:",n)}}async close(){await this.client.quit()}};var c=class{constructor(e){this.readModel=e}async handle(e){switch(e.type){case"shipment.created":await this.onShipmentCreated(e);break;case"shipment.status_changed":await this.onStatusChanged(e);break}}async onShipmentCreated(e){let t=e.payload,a=e,r={id:e.aggregateId,tenantId:a.tenantId||"default",accountId:t.accountId,status:"created",trackingNumber:t.trackingNumber,totalValue:Number(t.totalValue)||0,incoterms:t.incoterms||"DDU",senderName:_optionalChain([t, 'access', _13 => _13.sender, 'optionalAccess', _14 => _14.name])||"",recipientName:_optionalChain([t, 'access', _15 => _15.recipient, 'optionalAccess', _16 => _16.name])||"",createdAt:e.timestamp,updatedAt:e.timestamp,metadata:{sourceEvent:e.eventId}};await this.readModel.upsert(r)}async onStatusChanged(e){let t=e.payload,a=e,r=await this.readModel.getById(e.aggregateId,a.tenantId||"default");r&&await this.readModel.upsert({...r,status:t.status||r.status,updatedAt:t.updatedAt||e.timestamp,metadata:{...r.metadata,previousStatus:t.previousStatus,lastEventId:e.eventId}})}};var _crypto = require('crypto');var _bettersqlite3 = require('better-sqlite3'); var _bettersqlite32 = _interopRequireDefault(_bettersqlite3);var u=class{constructor(e=":memory:"){this.algorithm="sha256";this.db=new (0, _bettersqlite32.default)(e),this.db.exec(`
|
|
41
|
+
CREATE TABLE IF NOT EXISTS audit_logs (
|
|
42
|
+
id TEXT PRIMARY KEY,
|
|
43
|
+
tenant_id TEXT NOT NULL,
|
|
44
|
+
aggregate_id TEXT NOT NULL DEFAULT '',
|
|
45
|
+
type TEXT NOT NULL DEFAULT '',
|
|
46
|
+
actor_id TEXT NOT NULL,
|
|
47
|
+
actor_type TEXT NOT NULL,
|
|
48
|
+
action TEXT NOT NULL,
|
|
49
|
+
resource_type TEXT NOT NULL,
|
|
50
|
+
resource_id TEXT NOT NULL,
|
|
51
|
+
payload TEXT NOT NULL,
|
|
52
|
+
signature TEXT NOT NULL,
|
|
53
|
+
timestamp TEXT NOT NULL,
|
|
54
|
+
ip_address TEXT,
|
|
55
|
+
user_agent TEXT,
|
|
56
|
+
version INTEGER DEFAULT 1
|
|
57
|
+
)
|
|
58
|
+
`),this.db.exec("CREATE INDEX IF NOT EXISTS idx_audit_tenant ON audit_logs(tenant_id, resource_type, resource_id)"),this.db.exec("CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_logs(timestamp)")}async log(e,t){let a=JSON.stringify({eventId:e.eventId,aggregateId:e.aggregateId,type:e.type,payload:e.payload,timestamp:e.timestamp,actorId:e.actorId,action:e.action,resourceType:e.resourceType,resourceId:e.resourceId}),r=_crypto.createHmac.call(void 0, this.algorithm,t).update(a).digest("hex");this.db.prepare(`
|
|
59
|
+
INSERT INTO audit_logs (
|
|
60
|
+
id, tenant_id, aggregate_id, type, actor_id, actor_type, action,
|
|
61
|
+
resource_type, resource_id, payload, signature,
|
|
62
|
+
timestamp, ip_address, user_agent, version
|
|
63
|
+
) VALUES (
|
|
64
|
+
@id, @tenantId, @aggregateId, @type, @actorId, @actorType, @action,
|
|
65
|
+
@resourceType, @resourceId, @payloadText, @signature,
|
|
66
|
+
@timestamp, @ipAddress, @userAgent, @version
|
|
67
|
+
)
|
|
68
|
+
`).run({id:e.eventId,tenantId:e.tenantId,aggregateId:e.aggregateId,type:e.type,actorId:e.actorId,actorType:e.actorType,action:e.action,resourceType:e.resourceType,resourceId:e.resourceId,payloadText:JSON.stringify(e.payload),version:e.version,signature:r,timestamp:e.timestamp,ipAddress:e.ipAddress||null,userAgent:e.userAgent||null})}verify(e,t){let a=JSON.stringify({eventId:e.eventId,aggregateId:e.aggregateId,type:e.type,payload:e.payload,timestamp:e.timestamp,actorId:e.actorId,action:e.action,resourceType:e.resourceType,resourceId:e.resourceId});return _crypto.createHmac.call(void 0, this.algorithm,t).update(a).digest("hex")===e.signature}async query(e){let t="SELECT * FROM audit_logs WHERE tenant_id = ?",a=[e.tenantId];return e.resourceType&&(t+=" AND resource_type = ?",a.push(e.resourceType)),e.resourceId&&(t+=" AND resource_id = ?",a.push(e.resourceId)),e.since&&(t+=" AND timestamp >= ?",a.push(e.since)),e.until&&(t+=" AND timestamp <= ?",a.push(e.until)),t+=" ORDER BY timestamp DESC",this.db.prepare(t).all(...a).map(n=>({eventId:n.id,aggregateId:n.aggregate_id,type:n.type,payload:JSON.parse(n.payload),timestamp:n.timestamp,version:Number(n.version)||1,tenantId:n.tenant_id,actorId:n.actor_id,actorType:n.actor_type,action:n.action,resourceType:n.resource_type,resourceId:n.resource_id,signature:n.signature,ipAddress:n.ip_address,userAgent:n.user_agent}))}async exportToSIEM(e){let a=this.db.prepare("SELECT * FROM audit_logs ORDER BY timestamp ASC").all().map(r=>{let n=JSON.parse(r.payload);return{...r,payload:n}});switch(e){case"cef":return a.map(r=>`CEF:0|WeShipYou|SDK|1.0|${r.action}|${r.resource_type} operation|5|src=${r.ip_address||"unknown"} rt=${new Date(r.timestamp).getTime()} externalId=${r.id} msg=${JSON.stringify(r.payload)}`).join(`
|
|
69
|
+
`);case"leef":return a.map(r=>`LEEF:2.0|WeShipYou|SDK|1.0|${r.action}|sev=5 cat=${r.resource_type} src=${r.ip_address||"unknown"} rt=${new Date(r.timestamp).toISOString()} msg=${JSON.stringify(r.payload)}`).join(`
|
|
70
|
+
`);default:return JSON.stringify(a,null,2)}}close(){this.db.close()}};var l=class{export(e,t){switch(t){case"cef":return this.toCEF(e);case"leef":return this.toLEEF(e);default:return JSON.stringify(e,null,2)}}toCEF(e){return e.map(t=>`CEF:0|WeShipYou|SDK|1.0|${t.action}|${t.resourceType} operation|5|src=${t.ipAddress||"unknown"} rt=${new Date(t.timestamp).getTime()} externalId=${t.eventId} msg=${JSON.stringify(t.payload)}`).join(`
|
|
71
|
+
`)}toLEEF(e){return e.map(t=>`LEEF:2.0|WeShipYou|SDK|1.0|${t.action}|sev=5 cat=${t.resourceType} src=${t.ipAddress||"unknown"} rt=${new Date(t.timestamp).toISOString()} msg=${JSON.stringify(t.payload)}`).join(`
|
|
72
|
+
`)}};var g=class{constructor(e,t,a){this.auditLog=e;this.tenantProvider=t;this.privateKey=a}async record(e){let t=this.tenantProvider.getCurrent(),a=new Date().toISOString(),r={eventId:_crypto.randomUUID.call(void 0, ),aggregateId:e.resourceId,type:`audit.${e.action}`,payload:e.payload,timestamp:a,version:1,tenantId:_optionalChain([t, 'optionalAccess', _17 => _17.tenantId])||"default",actorId:_optionalChain([t, 'optionalAccess', _18 => _18.accountId])||"system",actorType:"system",action:e.action,resourceType:e.resourceType,resourceId:e.resourceId,ipAddress:_optionalChain([t, 'optionalAccess', _19 => _19.metadata, 'optionalAccess', _20 => _20.ipAddress]),userAgent:_optionalChain([t, 'optionalAccess', _21 => _21.metadata, 'optionalAccess', _22 => _22.userAgent])};await this.auditLog.log(r,this.privateKey)}};var h=class{constructor(e){this.httpClient=e;this.active=!1}async inject(e,t=5e3){switch(this.active=!0,e){case"latency":await this.injectLatency(t);break;case"error":await this.injectErrors(t);break;case"timeout":await this.injectTimeout(t);break;case"crash":await this.injectCrash();break}this.active=!1}async recover(){this.active=!1}isActive(){return this.active}async injectLatency(e){let t=Date.now();for(;Date.now()-t<e&&this.active;)await new Promise(a=>setTimeout(a,100))}async injectErrors(e){throw new Error("Simulated chaos error")}async injectTimeout(e){await new Promise(()=>{})}async injectCrash(){process.exit(1)}};function R(i,e){return async(t,a,r)=>{try{let{tenantId:n,accountId:o}=await e(t),x={tenantId:n,accountId:o,permissions:_optionalChain([t, 'access', _23 => _23.user, 'optionalAccess', _24 => _24.permissions])||[],metadata:{ipAddress:t.ip,userAgent:t.get("User-Agent")}};await i.isolate(x,async()=>{r()})}catch(n){r(n)}}}var w=class{constructor(e,t,a,r="en-US"){this.username=e;this.password=t;this.payload=a;this.lang=r;this.type="CreateShipment"}},I= exports.CreateShipmentHandler =class{constructor(e,t,a){this.createShipmentUseCase=e;this.eventStore=t;this.tenantId=a}async execute(e){let a=await this.createShipmentUseCase.execute(e.username,e.password,e.payload,e.lang),r=a.id||a.shipmentId||a.uid,n=new (0, _chunkRM3JFTCXcjs.d)(_crypto.randomUUID.call(void 0, ),r,{accountId:e.payload.accountId,trackingNumber:a.trackingNumber});return await this.eventStore.append(r,[n]),{shipmentId:r,trackingNumber:a.trackingNumber}}};var L=class{constructor(e,t,a){this.shipmentId=e;this.tenantId=t;this.accountId=a;this.type="CancelShipment"}},y= exports.CancelShipmentHandler =class{constructor(e,t){this.httpClient=e;this.eventStore=t}async execute(e){await this.httpClient.request({method:"POST",url:"/api/v1/shipments/cancel",body:{accountId:e.accountId,ids:[e.shipmentId]}});let t={eventId:_crypto.randomUUID.call(void 0, ),aggregateId:e.shipmentId,type:"shipment.cancelled",payload:{accountId:e.accountId,tenantId:e.tenantId},timestamp:new Date().toISOString(),version:1};return await this.eventStore.append(e.shipmentId,[t]),{success:!0,shipmentId:e.shipmentId}}};var _=class{constructor(e,t,a,r="en-US"){this.username=e;this.password=t;this.payload=a;this.lang=r;this.type="ExecuteRecharge"}},S= exports.ExecuteRechargeHandler =class{constructor(e,t){this.executeRechargeUseCase=e;this.eventStore=t}async execute(e){let a=await this.executeRechargeUseCase.execute(e.username,e.password,e.payload,e.lang),r=_crypto.randomUUID.call(void 0, );return await this.eventStore.append(r,[{eventId:_crypto.randomUUID.call(void 0, ),aggregateId:r,type:"recharge.executed",payload:{accountUid:e.payload.accountUid,amount:e.payload.amount,provider:a.provider,status:a.status||"completed"},timestamp:new Date().toISOString(),version:1}]),{transactionId:r,status:a.status||"completed"}}};var A=class{constructor(e,t,a,r,n="en-US"){this.username=e;this.password=t;this.shipmentId=a;this.payload=r;this.lang=n;this.type="UpdateShipment"}},f= exports.UpdateShipmentHandler =class{constructor(e,t,a){this.updateShipmentUseCase=e;this.eventStore=t;this.tenantId=a}async execute(e){await this.updateShipmentUseCase.execute(e.username,e.password,e.shipmentId,e.payload,e.lang);let t=Object.keys(e.payload),a=new (0, _chunkRM3JFTCXcjs.f)(_crypto.randomUUID.call(void 0, ),e.shipmentId,{accountId:e.payload.accountId||"",fields:t});return await this.eventStore.append(e.shipmentId,[a]),{shipmentId:e.shipmentId}}};var U=class{constructor(e,t){this.shipmentId=e;this.tenantId=t;this.type="GetShipment"}},E= exports.GetShipmentHandler =class{constructor(e){this.readModel=e}async execute(e){return this.readModel.getById(e.shipmentId,e.tenantId)}};var D=class{constructor(e){this.filter=e;this.type="ListShipments"}},T= exports.ListShipmentsHandler =class{constructor(e){this.readModel=e}async execute(e){return this.readModel.query(e.filter)}};var P=class{constructor(e,t){this.shipmentId=e;this.sinceVersion=t;this.type="GetShipmentHistory"}},v= exports.GetShipmentHistoryHandler =class{constructor(e){this.eventStore=e}async execute(e){return this.eventStore.getByAggregate(e.shipmentId,e.sinceVersion)}};function nt(i,e){let t=new s,a=new d(t),r=new m(e.postgresUrl),n=new _chunkRM3JFTCXcjs.c,o=new p(e.redisUrl,e.streamName,"projections",e.consumerName||"shipment-projection"),x=new c(r),O=new u,B=new l,G=new g(O,t,e.auditPrivateKey),W=new h(i.httpClient),K=R(t,async Y=>{let N=_optionalChain([Y, 'access', _25 => _25.headers, 'optionalAccess', _26 => _26.authorization])||"",b=N.startsWith("Bearer ")?N.slice(7):N,M="default",H="default";if(b)try{let C=JSON.parse(Buffer.from(b.split(".")[1],"base64url").toString());M=C.tenant_id||C.sub||"default",H=C.account_id||C.client_id||"default"}catch (e2){}return{tenantId:M,accountId:H}});return{tenantProvider:t,schemaIsolation:a,readModel:r,projection:o,shipmentProjection:x,eventStore:n,auditLog:O,siemExporter:B,auditService:G,chaosTester:W,tenantMiddleware:K,commands:{createShipment:new I(i.createShipment,n),cancelShipment:new y(i.httpClient,n),executeRecharge:new S(i.executeRecharge,n),updateShipment:new f(new (0, _chunkRM3JFTCXcjs.b)(i.auth,new (0, _chunkRM3JFTCXcjs.a)(i.httpClient)),n)},queries:{getShipment:new E(r),listShipments:new T(r),getShipmentHistory:new v(n)}}}exports.AsyncLocalStorageTenantContext = s; exports.AuditLogService = g; exports.CancelShipmentCommand = L; exports.CancelShipmentHandler = y; exports.CreateShipmentCommand = w; exports.CreateShipmentHandler = I; exports.ExecuteRechargeCommand = _; exports.ExecuteRechargeHandler = S; exports.GetShipmentHandler = E; exports.GetShipmentHistoryHandler = v; exports.GetShipmentHistoryQuery = P; exports.GetShipmentQuery = U; exports.HttpChaosTester = h; exports.ImmutableAuditLog = u; exports.ListShipmentsHandler = T; exports.ListShipmentsQuery = D; exports.PostgresReadModel = m; exports.RedisStreamsProjection = p; exports.SchemaIsolationManager = d; exports.ShipmentProjection = c; exports.SiemExporter = l; exports.UpdateShipmentCommand = A; exports.UpdateShipmentHandler = f; exports.bootstrapEnterprise = nt; exports.createTenantMiddleware = R;
|
|
73
|
+
//# sourceMappingURL=enterprise.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/osamud/Escritorio/WeShipYou/dist/enterprise.cjs","../src/infrastructure/multi-tenant/tenant-context.impl.ts","../src/infrastructure/multi-tenant/schema-isolation.impl.ts","../src/infrastructure/cqrs/postgres-read-model.impl.ts","../src/infrastructure/audit/immutable-log.impl.ts","../src/infrastructure/audit/siem-exporter.impl.ts","../src/application/services/audit-log.service.ts"],"names":["AsyncLocalStorageTenantContext","AsyncLocalStorage","context","fn","SchemaIsolationManager","tenantProvider","tenantId","config","PostgresReadModel","connectionString","poolConfig","Pool","shipment","id","row","filters","query","params","paramIndex"],"mappings":"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","file":"/home/osamud/Escritorio/WeShipYou/dist/enterprise.cjs","sourcesContent":[null,"import { AsyncLocalStorage } from 'async_hooks';\nimport { ITenantContextProvider, TenantContext } from '../../domain/interfaces/tenant-context.interface';\n\nexport class AsyncLocalStorageTenantContext implements ITenantContextProvider {\n private storage = new AsyncLocalStorage<TenantContext>();\n\n getCurrent(): TenantContext | null {\n return this.storage.getStore() || null;\n }\n\n set(context: TenantContext): void {\n this.storage.enterWith(context);\n }\n\n clear(): void {\n this.storage.disable();\n }\n\n async isolate<T>(context: TenantContext, fn: () => Promise<T>): Promise<T> {\n return this.storage.run(context, fn);\n }\n}\n","import { ITenantContextProvider } from '../../domain/interfaces/tenant-context.interface';\n\nexport interface SchemaIsolationConfig {\n tenantId: string;\n schemaName: string;\n migrationStatements: string[];\n}\n\nexport class SchemaIsolationManager {\n private tenantProvider: ITenantContextProvider;\n private schemas: Map<string, SchemaIsolationConfig> = new Map();\n\n constructor(tenantProvider: ITenantContextProvider) {\n this.tenantProvider = tenantProvider;\n }\n\n register(tenantId: string, config: SchemaIsolationConfig): void {\n this.schemas.set(tenantId, config);\n }\n\n getCurrentSchema(): string | null {\n const context = this.tenantProvider.getCurrent();\n if (!context) return null;\n const config = this.schemas.get(context.tenantId);\n return config?.schemaName || null;\n }\n\n getSchemaFor(tenantId: string): string | null {\n return this.schemas.get(tenantId)?.schemaName || null;\n }\n\n deregister(tenantId: string): void {\n this.schemas.delete(tenantId);\n }\n}\n","import { Pool } from 'pg';\nimport { IReadModel, ShipmentReadModel } from '../../domain/interfaces/read-model.interface';\n\nexport class PostgresReadModel implements IReadModel {\n private pool: Pool;\n\n constructor(connectionString: string, poolConfig?: { max?: number; idleTimeoutMillis?: number; connectionTimeoutMillis?: number }) {\n this.pool = new Pool({ connectionString, max: 10, idleTimeoutMillis: 30000, connectionTimeoutMillis: 10000, ...poolConfig });\n }\n\n async init(): Promise<void> {\n await this.pool.query(`\n CREATE TABLE IF NOT EXISTS shipment_read_model (\n id TEXT PRIMARY KEY,\n tenant_id TEXT NOT NULL,\n account_id TEXT NOT NULL,\n status TEXT NOT NULL,\n tracking_number TEXT,\n total_value NUMERIC(10,2) NOT NULL,\n incoterms TEXT CHECK (incoterms IN ('DDU', 'DDP')),\n sender_name TEXT NOT NULL,\n recipient_name TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n metadata JSONB\n )\n `);\n await this.pool.query(`\n CREATE INDEX IF NOT EXISTS idx_shipment_tenant_account\n ON shipment_read_model (tenant_id, account_id)\n `);\n await this.pool.query(`\n CREATE INDEX IF NOT EXISTS idx_shipment_status\n ON shipment_read_model (status)\n `);\n await this.pool.query(`\n CREATE INDEX IF NOT EXISTS idx_shipment_created\n ON shipment_read_model (created_at)\n `);\n }\n\n async upsert(shipment: ShipmentReadModel): Promise<void> {\n await this.pool.query(`\n INSERT INTO shipment_read_model (\n id, tenant_id, account_id, status, tracking_number,\n total_value, incoterms, sender_name, recipient_name,\n created_at, updated_at, metadata\n ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)\n ON CONFLICT (id) DO UPDATE SET\n status = EXCLUDED.status,\n tracking_number = EXCLUDED.tracking_number,\n total_value = EXCLUDED.total_value,\n incoterms = EXCLUDED.incoterms,\n sender_name = EXCLUDED.sender_name,\n recipient_name = EXCLUDED.recipient_name,\n updated_at = NOW(),\n metadata = EXCLUDED.metadata\n `, [\n shipment.id, shipment.tenantId, shipment.accountId,\n shipment.status, shipment.trackingNumber,\n shipment.totalValue, shipment.incoterms,\n shipment.senderName, shipment.recipientName,\n shipment.createdAt, shipment.updatedAt,\n shipment.metadata ? JSON.stringify(shipment.metadata) : null\n ]);\n }\n\n async getById(id: string, tenantId: string): Promise<ShipmentReadModel | null> {\n const result = await this.pool.query(\n 'SELECT * FROM shipment_read_model WHERE id = $1 AND tenant_id = $2',\n [id, tenantId]\n );\n const row = result.rows[0];\n if (!row) return null;\n return {\n ...row,\n metadata: row.metadata || undefined\n };\n }\n\n async query(filters: {\n tenantId: string;\n accountId?: string;\n status?: string;\n limit?: number;\n offset?: number;\n }): Promise<ShipmentReadModel[]> {\n let query = 'SELECT * FROM shipment_read_model WHERE tenant_id = $1';\n const params: unknown[] = [filters.tenantId];\n let paramIndex = 2;\n\n if (filters.accountId) {\n query += ` AND account_id = $${paramIndex++}`;\n params.push(filters.accountId);\n }\n if (filters.status) {\n query += ` AND status = $${paramIndex++}`;\n params.push(filters.status);\n }\n\n query += ' ORDER BY created_at DESC';\n\n if (filters.limit) {\n query += ` LIMIT $${paramIndex++}`;\n params.push(filters.limit);\n }\n if (filters.offset) {\n query += ` OFFSET $${paramIndex++}`;\n params.push(filters.offset);\n }\n\n const result = await this.pool.query(query, params);\n return result.rows.map((row: Record<string, unknown>) => ({\n ...row,\n metadata: row.metadata || undefined\n })) as ShipmentReadModel[];\n }\n\n async close(): Promise<void> {\n await this.pool.end();\n }\n}\n","import { createHmac } from 'crypto';\nimport Database from 'better-sqlite3';\nimport { IAuditLogService, AuditLogEntry } from '../../domain/interfaces/audit-log.interface';\n\nexport class ImmutableAuditLog implements IAuditLogService {\n private db: Database.Database;\n private readonly algorithm = 'sha256';\n\n constructor(dbPath = ':memory:') {\n this.db = new Database(dbPath);\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS audit_logs (\n id TEXT PRIMARY KEY,\n tenant_id TEXT NOT NULL,\n aggregate_id TEXT NOT NULL DEFAULT '',\n type TEXT NOT NULL DEFAULT '',\n actor_id TEXT NOT NULL,\n actor_type TEXT NOT NULL,\n action TEXT NOT NULL,\n resource_type TEXT NOT NULL,\n resource_id TEXT NOT NULL,\n payload TEXT NOT NULL,\n signature TEXT NOT NULL,\n timestamp TEXT NOT NULL,\n ip_address TEXT,\n user_agent TEXT,\n version INTEGER DEFAULT 1\n )\n `);\n this.db.exec('CREATE INDEX IF NOT EXISTS idx_audit_tenant ON audit_logs(tenant_id, resource_type, resource_id)');\n this.db.exec('CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_logs(timestamp)');\n }\n\n async log(entry: Omit<AuditLogEntry, 'signature'>, privateKey: string): Promise<void> {\n const canonical = JSON.stringify({\n eventId: entry.eventId,\n aggregateId: entry.aggregateId,\n type: entry.type,\n payload: entry.payload,\n timestamp: entry.timestamp,\n actorId: entry.actorId,\n action: entry.action,\n resourceType: entry.resourceType,\n resourceId: entry.resourceId\n });\n\n const signature = createHmac(this.algorithm, privateKey)\n .update(canonical)\n .digest('hex');\n\n const insert = this.db.prepare(`\n INSERT INTO audit_logs (\n id, tenant_id, aggregate_id, type, actor_id, actor_type, action,\n resource_type, resource_id, payload, signature,\n timestamp, ip_address, user_agent, version\n ) VALUES (\n @id, @tenantId, @aggregateId, @type, @actorId, @actorType, @action,\n @resourceType, @resourceId, @payloadText, @signature,\n @timestamp, @ipAddress, @userAgent, @version\n )\n `);\n\n insert.run({\n id: entry.eventId,\n tenantId: entry.tenantId,\n aggregateId: entry.aggregateId,\n type: entry.type,\n actorId: entry.actorId,\n actorType: entry.actorType,\n action: entry.action,\n resourceType: entry.resourceType,\n resourceId: entry.resourceId,\n payloadText: JSON.stringify(entry.payload),\n version: entry.version,\n signature,\n timestamp: entry.timestamp,\n ipAddress: entry.ipAddress || null,\n userAgent: entry.userAgent || null\n });\n }\n\n verify(entry: AuditLogEntry, key: string): boolean {\n const canonical = JSON.stringify({\n eventId: entry.eventId,\n aggregateId: entry.aggregateId,\n type: entry.type,\n payload: entry.payload,\n timestamp: entry.timestamp,\n actorId: entry.actorId,\n action: entry.action,\n resourceType: entry.resourceType,\n resourceId: entry.resourceId\n });\n\n const expected = createHmac(this.algorithm, key)\n .update(canonical)\n .digest('hex');\n return expected === entry.signature;\n }\n\n async query(filters: {\n tenantId: string;\n resourceType?: string;\n resourceId?: string;\n since?: string;\n until?: string;\n }): Promise<AuditLogEntry[]> {\n let query = 'SELECT * FROM audit_logs WHERE tenant_id = ?';\n const params: unknown[] = [filters.tenantId];\n\n if (filters.resourceType) {\n query += ' AND resource_type = ?';\n params.push(filters.resourceType);\n }\n if (filters.resourceId) {\n query += ' AND resource_id = ?';\n params.push(filters.resourceId);\n }\n if (filters.since) {\n query += ' AND timestamp >= ?';\n params.push(filters.since);\n }\n if (filters.until) {\n query += ' AND timestamp <= ?';\n params.push(filters.until);\n }\n\n query += ' ORDER BY timestamp DESC';\n\n const rows = this.db.prepare(query).all(...params) as Array<Record<string, unknown>>;\n return rows.map(row => ({\n eventId: row.id as string,\n aggregateId: row.aggregate_id as string,\n type: row.type as string,\n payload: JSON.parse(row.payload as string),\n timestamp: row.timestamp as string,\n version: Number(row.version) || 1,\n tenantId: row.tenant_id as string,\n actorId: row.actor_id as string,\n actorType: row.actor_type as 'user' | 'system' | 'api_key',\n action: row.action as string,\n resourceType: row.resource_type as string,\n resourceId: row.resource_id as string,\n signature: row.signature as string,\n ipAddress: row.ip_address as string | undefined,\n userAgent: row.user_agent as string | undefined\n })) as AuditLogEntry[];\n }\n\n async exportToSIEM(format: 'json' | 'cef' | 'leef'): Promise<string> {\n const allLogs = this.db.prepare('SELECT * FROM audit_logs ORDER BY timestamp ASC')\n .all() as Array<Record<string, unknown>>;\n\n const parsed = allLogs.map(log => {\n const p = JSON.parse(log.payload as string);\n return { ...log, payload: p } as Record<string, unknown>;\n });\n\n switch (format) {\n case 'cef':\n return parsed.map(log =>\n `CEF:0|WeShipYou|SDK|1.0|${log.action}|${log.resource_type} operation|5|` +\n `src=${log.ip_address || 'unknown'} ` +\n `rt=${new Date(log.timestamp as string).getTime()} ` +\n `externalId=${log.id} ` +\n `msg=${JSON.stringify(log.payload)}`\n ).join('\\n');\n\n case 'leef':\n return parsed.map(log =>\n `LEEF:2.0|WeShipYou|SDK|1.0|${log.action}|` +\n `sev=5 cat=${log.resource_type} src=${log.ip_address || 'unknown'} ` +\n `rt=${new Date(log.timestamp as string).toISOString()} ` +\n `msg=${JSON.stringify(log.payload)}`\n ).join('\\n');\n\n default:\n return JSON.stringify(parsed, null, 2);\n }\n }\n\n close(): void {\n this.db.close();\n }\n}\n","import { AuditLogEntry } from '../../domain/interfaces/audit-log.interface';\n\nexport type SiemFormat = 'json' | 'cef' | 'leef';\n\nexport class SiemExporter {\n export(entries: AuditLogEntry[], format: SiemFormat): string {\n switch (format) {\n case 'cef':\n return this.toCEF(entries);\n case 'leef':\n return this.toLEEF(entries);\n default:\n return JSON.stringify(entries, null, 2);\n }\n }\n\n private toCEF(entries: AuditLogEntry[]): string {\n return entries.map(e =>\n `CEF:0|WeShipYou|SDK|1.0|${e.action}|${e.resourceType} operation|5|` +\n `src=${e.ipAddress || 'unknown'} ` +\n `rt=${new Date(e.timestamp).getTime()} ` +\n `externalId=${e.eventId} ` +\n `msg=${JSON.stringify(e.payload)}`\n ).join('\\n');\n }\n\n private toLEEF(entries: AuditLogEntry[]): string {\n return entries.map(e =>\n `LEEF:2.0|WeShipYou|SDK|1.0|${e.action}|` +\n `sev=5 cat=${e.resourceType} src=${e.ipAddress || 'unknown'} ` +\n `rt=${new Date(e.timestamp).toISOString()} ` +\n `msg=${JSON.stringify(e.payload)}`\n ).join('\\n');\n }\n}\n","import { randomUUID } from 'crypto';\nimport { IAuditLogService, AuditLogEntry } from '../../domain/interfaces/audit-log.interface';\nimport { ITenantContextProvider } from '../../domain/interfaces/tenant-context.interface';\n\nexport class AuditLogService {\n constructor(\n private readonly auditLog: IAuditLogService,\n private readonly tenantProvider: ITenantContextProvider,\n private readonly privateKey: string\n ) {}\n\n async record(partial: {\n action: string;\n resourceType: string;\n resourceId: string;\n payload: Record<string, unknown>;\n }): Promise<void> {\n const context = this.tenantProvider.getCurrent();\n const timestamp = new Date().toISOString();\n\n const entry: Omit<AuditLogEntry, 'signature'> = {\n eventId: randomUUID(),\n aggregateId: partial.resourceId,\n type: `audit.${partial.action}`,\n payload: partial.payload,\n timestamp,\n version: 1,\n tenantId: context?.tenantId || 'default',\n actorId: context?.accountId || 'system',\n actorType: 'system',\n action: partial.action,\n resourceType: partial.resourceType,\n resourceId: partial.resourceId,\n ipAddress: context?.metadata?.ipAddress as string | undefined,\n userAgent: context?.metadata?.userAgent as string | undefined\n };\n\n await this.auditLog.log(entry, this.privateKey);\n }\n}\n"]}
|