@vallum/sdk 0.0.0-prerelease → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/contracts/iotaEscrowSettlement.d.ts +92 -0
- package/dist/contracts/iotaEscrowSettlement.js +225 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -4,14 +4,14 @@ TypeScript client scaffold for applications integrating with an Vallum policy ga
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
For the npm
|
|
7
|
+
For the npm release, install:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
npm install @vallum/sdk
|
|
10
|
+
npm install @vallum/sdk
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
See the full package selection and configuration guide in the repository:
|
|
14
|
-
https://github.com/0xCozart/
|
|
14
|
+
https://github.com/0xCozart/vallum/blob/main/docs/vallum/package-integration-guide.md
|
|
15
15
|
|
|
16
16
|
## Usage
|
|
17
17
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type EscrowReceipt, type EscrowSettlementRail, type EscrowSettlementReleaseMode, type ReceiptAmount } from "@vallum/receipts";
|
|
2
|
+
export type EscrowSettlementErrorCode = "IDEMPOTENCY_REPLAYED" | "ESCROW_BINDING_MISMATCH" | "ESCROW_SETTLEMENT_NOT_OPEN" | "ESCROW_STORE_CONFLICT";
|
|
3
|
+
export declare class EscrowSettlementError extends Error {
|
|
4
|
+
readonly code: EscrowSettlementErrorCode;
|
|
5
|
+
constructor(code: EscrowSettlementErrorCode, message: string);
|
|
6
|
+
}
|
|
7
|
+
export interface IotaEscrowOpenExecutionRequest {
|
|
8
|
+
readonly receipt: EscrowReceipt;
|
|
9
|
+
readonly settlementRail: EscrowSettlementRail;
|
|
10
|
+
readonly releaseMode: EscrowSettlementReleaseMode;
|
|
11
|
+
readonly invocationId: string;
|
|
12
|
+
readonly actionId: string;
|
|
13
|
+
readonly actionContractId: string;
|
|
14
|
+
readonly actionContractVersion: string;
|
|
15
|
+
readonly providerPayoutRef: string;
|
|
16
|
+
readonly platformFeeRef: string;
|
|
17
|
+
readonly refundDestinationRef: string;
|
|
18
|
+
readonly providerNetAmount: ReceiptAmount;
|
|
19
|
+
readonly platformFeeAmount: ReceiptAmount;
|
|
20
|
+
}
|
|
21
|
+
export interface IotaEscrowReleaseExecutionRequest {
|
|
22
|
+
readonly receipt: EscrowReceipt;
|
|
23
|
+
readonly verifierId: string;
|
|
24
|
+
readonly escrowId: string;
|
|
25
|
+
readonly invocationId: string;
|
|
26
|
+
readonly releaseProofHash: string;
|
|
27
|
+
readonly providerExecutionReceiptHash: string;
|
|
28
|
+
readonly evidenceAttestationHash: string;
|
|
29
|
+
readonly settlementReceiptHash: string;
|
|
30
|
+
readonly buyerFacingReceiptHash: string;
|
|
31
|
+
}
|
|
32
|
+
export interface IotaEscrowRefundExecutionRequest {
|
|
33
|
+
readonly receipt: EscrowReceipt;
|
|
34
|
+
readonly escrowId: string;
|
|
35
|
+
readonly invocationId: string;
|
|
36
|
+
readonly reason: string;
|
|
37
|
+
readonly settlementReceiptHash: string;
|
|
38
|
+
readonly buyerFacingReceiptHash: string;
|
|
39
|
+
}
|
|
40
|
+
export interface IotaEscrowOpenExecutionResult {
|
|
41
|
+
readonly escrowId: string;
|
|
42
|
+
readonly transactionDigest: string;
|
|
43
|
+
}
|
|
44
|
+
export interface IotaEscrowSettlementExecutionResult {
|
|
45
|
+
readonly transactionDigest: string;
|
|
46
|
+
}
|
|
47
|
+
export interface IotaEscrowSettlementExecutor {
|
|
48
|
+
readonly open: (request: IotaEscrowOpenExecutionRequest) => Promise<IotaEscrowOpenExecutionResult>;
|
|
49
|
+
readonly release: (request: IotaEscrowReleaseExecutionRequest) => Promise<IotaEscrowSettlementExecutionResult>;
|
|
50
|
+
readonly refund: (request: IotaEscrowRefundExecutionRequest) => Promise<IotaEscrowSettlementExecutionResult>;
|
|
51
|
+
}
|
|
52
|
+
export interface EscrowSettlementStoreRecord {
|
|
53
|
+
readonly idempotencyKey: string;
|
|
54
|
+
readonly receiptId: string;
|
|
55
|
+
readonly agentId: string;
|
|
56
|
+
readonly ownerId: string;
|
|
57
|
+
readonly providerId: string;
|
|
58
|
+
readonly verifierId: string;
|
|
59
|
+
readonly escrowId: string;
|
|
60
|
+
readonly invocationId: string;
|
|
61
|
+
readonly status: "open" | "released" | "refunded";
|
|
62
|
+
}
|
|
63
|
+
export interface EscrowSettlementStore {
|
|
64
|
+
readonly getByIdempotencyKey: (idempotencyKey: string) => Promise<EscrowSettlementStoreRecord | undefined>;
|
|
65
|
+
readonly getByEscrowId: (escrowId: string) => Promise<EscrowSettlementStoreRecord | undefined>;
|
|
66
|
+
/**
|
|
67
|
+
* Must reject conflicting idempotency-key or escrow-id bindings. A live executor
|
|
68
|
+
* should back this with a durable conditional write before funds can move.
|
|
69
|
+
*/
|
|
70
|
+
readonly put: (record: EscrowSettlementStoreRecord) => Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
export interface IotaEscrowSettlementClientOptions {
|
|
73
|
+
readonly executor: IotaEscrowSettlementExecutor;
|
|
74
|
+
readonly store?: EscrowSettlementStore;
|
|
75
|
+
readonly now?: () => Date;
|
|
76
|
+
}
|
|
77
|
+
export type IotaEscrowOpenInput = IotaEscrowOpenExecutionRequest;
|
|
78
|
+
export type IotaEscrowReleaseInput = IotaEscrowReleaseExecutionRequest;
|
|
79
|
+
export type IotaEscrowRefundInput = IotaEscrowRefundExecutionRequest;
|
|
80
|
+
export interface IotaEscrowSettlementClientResult {
|
|
81
|
+
readonly receipt: EscrowReceipt;
|
|
82
|
+
}
|
|
83
|
+
export interface IotaEscrowOpenResult extends IotaEscrowSettlementClientResult {
|
|
84
|
+
readonly escrowId: string;
|
|
85
|
+
readonly transactionDigest: string;
|
|
86
|
+
}
|
|
87
|
+
export declare function createInMemoryEscrowSettlementStore(): EscrowSettlementStore;
|
|
88
|
+
export declare function createIotaEscrowSettlementClient(options: IotaEscrowSettlementClientOptions): {
|
|
89
|
+
open(input: IotaEscrowOpenInput): Promise<IotaEscrowOpenResult>;
|
|
90
|
+
release(input: IotaEscrowReleaseInput): Promise<IotaEscrowSettlementClientResult>;
|
|
91
|
+
refund(input: IotaEscrowRefundInput): Promise<IotaEscrowSettlementClientResult>;
|
|
92
|
+
};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { recordEscrowSettlementOpen, recordEscrowSettlementRefund, recordEscrowSettlementRelease, } from "@vallum/receipts";
|
|
2
|
+
export class EscrowSettlementError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
constructor(code, message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.name = "EscrowSettlementError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function createInMemoryEscrowSettlementStore() {
|
|
11
|
+
const byIdempotencyKey = new Map();
|
|
12
|
+
const byEscrowId = new Map();
|
|
13
|
+
return {
|
|
14
|
+
async getByIdempotencyKey(idempotencyKey) {
|
|
15
|
+
return byIdempotencyKey.get(idempotencyKey);
|
|
16
|
+
},
|
|
17
|
+
async getByEscrowId(escrowId) {
|
|
18
|
+
return byEscrowId.get(escrowId);
|
|
19
|
+
},
|
|
20
|
+
async put(record) {
|
|
21
|
+
const existingByIdempotencyKey = byIdempotencyKey.get(record.idempotencyKey);
|
|
22
|
+
if (existingByIdempotencyKey && !hasSameEscrowStoreBinding(existingByIdempotencyKey, record)) {
|
|
23
|
+
throw new EscrowSettlementError("ESCROW_STORE_CONFLICT", "Escrow idempotency key is already bound to another settlement.");
|
|
24
|
+
}
|
|
25
|
+
const existingByEscrowId = byEscrowId.get(record.escrowId);
|
|
26
|
+
if (existingByEscrowId && !hasSameEscrowStoreBinding(existingByEscrowId, record)) {
|
|
27
|
+
throw new EscrowSettlementError("ESCROW_STORE_CONFLICT", "Escrow id is already bound to another receipt.");
|
|
28
|
+
}
|
|
29
|
+
byIdempotencyKey.set(record.idempotencyKey, record);
|
|
30
|
+
byEscrowId.set(record.escrowId, record);
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function createIotaEscrowSettlementClient(options) {
|
|
35
|
+
const store = options.store ?? createInMemoryEscrowSettlementStore();
|
|
36
|
+
const now = () => options.now?.() ?? new Date();
|
|
37
|
+
const openingIdempotencyKeys = new Set();
|
|
38
|
+
return {
|
|
39
|
+
async open(input) {
|
|
40
|
+
const idempotencyKey = input.receipt.idempotencyKey;
|
|
41
|
+
if (openingIdempotencyKeys.has(idempotencyKey)) {
|
|
42
|
+
throw new EscrowSettlementError("IDEMPOTENCY_REPLAYED", "Escrow idempotency key has already been used.");
|
|
43
|
+
}
|
|
44
|
+
openingIdempotencyKeys.add(idempotencyKey);
|
|
45
|
+
try {
|
|
46
|
+
const existing = await store.getByIdempotencyKey(idempotencyKey);
|
|
47
|
+
if (existing) {
|
|
48
|
+
throw new EscrowSettlementError("IDEMPOTENCY_REPLAYED", "Escrow idempotency key has already been used.");
|
|
49
|
+
}
|
|
50
|
+
preflightEscrowSettlementOpen(input);
|
|
51
|
+
const opened = await options.executor.open(input);
|
|
52
|
+
const receipt = recordEscrowSettlementOpen(input.receipt, {
|
|
53
|
+
at: now(),
|
|
54
|
+
settlementRail: input.settlementRail,
|
|
55
|
+
escrowId: opened.escrowId,
|
|
56
|
+
releaseMode: input.releaseMode,
|
|
57
|
+
invocationId: input.invocationId,
|
|
58
|
+
actionId: input.actionId,
|
|
59
|
+
actionContractId: input.actionContractId,
|
|
60
|
+
actionContractVersion: input.actionContractVersion,
|
|
61
|
+
providerPayoutRef: input.providerPayoutRef,
|
|
62
|
+
platformFeeRef: input.platformFeeRef,
|
|
63
|
+
refundDestinationRef: input.refundDestinationRef,
|
|
64
|
+
providerNetAmount: input.providerNetAmount,
|
|
65
|
+
platformFeeAmount: input.platformFeeAmount,
|
|
66
|
+
transactionDigest: opened.transactionDigest,
|
|
67
|
+
});
|
|
68
|
+
await store.put(toEscrowSettlementStoreRecord({
|
|
69
|
+
receipt: input.receipt,
|
|
70
|
+
escrowId: opened.escrowId,
|
|
71
|
+
invocationId: input.invocationId,
|
|
72
|
+
status: "open",
|
|
73
|
+
}));
|
|
74
|
+
return {
|
|
75
|
+
receipt,
|
|
76
|
+
escrowId: opened.escrowId,
|
|
77
|
+
transactionDigest: opened.transactionDigest,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
openingIdempotencyKeys.delete(idempotencyKey);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
async release(input) {
|
|
85
|
+
const record = await requireOpenStoreRecord(store, input.escrowId, input.invocationId);
|
|
86
|
+
requireStoreReceiptBinding(record, input.receipt);
|
|
87
|
+
requireReceiptBinding(input.receipt, input.escrowId, input.invocationId);
|
|
88
|
+
preflightEscrowSettlementRelease(input);
|
|
89
|
+
const released = await options.executor.release(input);
|
|
90
|
+
const receipt = recordEscrowSettlementRelease(input.receipt, {
|
|
91
|
+
at: now(),
|
|
92
|
+
verifierId: input.verifierId,
|
|
93
|
+
escrowId: input.escrowId,
|
|
94
|
+
invocationId: input.invocationId,
|
|
95
|
+
releaseProofHash: input.releaseProofHash,
|
|
96
|
+
providerExecutionReceiptHash: input.providerExecutionReceiptHash,
|
|
97
|
+
evidenceAttestationHash: input.evidenceAttestationHash,
|
|
98
|
+
settlementReceiptHash: input.settlementReceiptHash,
|
|
99
|
+
buyerFacingReceiptHash: input.buyerFacingReceiptHash,
|
|
100
|
+
transactionDigest: released.transactionDigest,
|
|
101
|
+
});
|
|
102
|
+
await store.put(toEscrowSettlementStoreRecord({
|
|
103
|
+
receipt: input.receipt,
|
|
104
|
+
escrowId: input.escrowId,
|
|
105
|
+
invocationId: input.invocationId,
|
|
106
|
+
status: "released",
|
|
107
|
+
}));
|
|
108
|
+
return { receipt };
|
|
109
|
+
},
|
|
110
|
+
async refund(input) {
|
|
111
|
+
const record = await requireOpenStoreRecord(store, input.escrowId, input.invocationId);
|
|
112
|
+
requireStoreReceiptBinding(record, input.receipt);
|
|
113
|
+
requireReceiptBinding(input.receipt, input.escrowId, input.invocationId);
|
|
114
|
+
preflightEscrowSettlementRefund(input);
|
|
115
|
+
const refunded = await options.executor.refund(input);
|
|
116
|
+
const receipt = recordEscrowSettlementRefund(input.receipt, {
|
|
117
|
+
at: now(),
|
|
118
|
+
escrowId: input.escrowId,
|
|
119
|
+
invocationId: input.invocationId,
|
|
120
|
+
reason: input.reason,
|
|
121
|
+
settlementReceiptHash: input.settlementReceiptHash,
|
|
122
|
+
buyerFacingReceiptHash: input.buyerFacingReceiptHash,
|
|
123
|
+
transactionDigest: refunded.transactionDigest,
|
|
124
|
+
});
|
|
125
|
+
await store.put(toEscrowSettlementStoreRecord({
|
|
126
|
+
receipt: input.receipt,
|
|
127
|
+
escrowId: input.escrowId,
|
|
128
|
+
invocationId: input.invocationId,
|
|
129
|
+
status: "refunded",
|
|
130
|
+
}));
|
|
131
|
+
return { receipt };
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function hasSameEscrowStoreBinding(left, right) {
|
|
136
|
+
return left.idempotencyKey === right.idempotencyKey
|
|
137
|
+
&& left.receiptId === right.receiptId
|
|
138
|
+
&& left.agentId === right.agentId
|
|
139
|
+
&& left.ownerId === right.ownerId
|
|
140
|
+
&& left.providerId === right.providerId
|
|
141
|
+
&& left.verifierId === right.verifierId
|
|
142
|
+
&& left.escrowId === right.escrowId
|
|
143
|
+
&& left.invocationId === right.invocationId;
|
|
144
|
+
}
|
|
145
|
+
function toEscrowSettlementStoreRecord(input) {
|
|
146
|
+
return {
|
|
147
|
+
idempotencyKey: input.receipt.idempotencyKey,
|
|
148
|
+
receiptId: input.receipt.receiptId,
|
|
149
|
+
agentId: input.receipt.agentId,
|
|
150
|
+
ownerId: input.receipt.ownerId,
|
|
151
|
+
providerId: input.receipt.escrow.providerId,
|
|
152
|
+
verifierId: input.receipt.escrow.verifierId,
|
|
153
|
+
escrowId: input.escrowId,
|
|
154
|
+
invocationId: input.invocationId,
|
|
155
|
+
status: input.status,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function preflightEscrowSettlementOpen(input) {
|
|
159
|
+
recordEscrowSettlementOpen(input.receipt, {
|
|
160
|
+
at: new Date(0),
|
|
161
|
+
settlementRail: input.settlementRail,
|
|
162
|
+
escrowId: "preflight-escrow",
|
|
163
|
+
releaseMode: input.releaseMode,
|
|
164
|
+
invocationId: input.invocationId,
|
|
165
|
+
actionId: input.actionId,
|
|
166
|
+
actionContractId: input.actionContractId,
|
|
167
|
+
actionContractVersion: input.actionContractVersion,
|
|
168
|
+
providerPayoutRef: input.providerPayoutRef,
|
|
169
|
+
platformFeeRef: input.platformFeeRef,
|
|
170
|
+
refundDestinationRef: input.refundDestinationRef,
|
|
171
|
+
providerNetAmount: input.providerNetAmount,
|
|
172
|
+
platformFeeAmount: input.platformFeeAmount,
|
|
173
|
+
transactionDigest: "preflight-digest",
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function preflightEscrowSettlementRelease(input) {
|
|
177
|
+
recordEscrowSettlementRelease(input.receipt, {
|
|
178
|
+
at: new Date(0),
|
|
179
|
+
verifierId: input.verifierId,
|
|
180
|
+
escrowId: input.escrowId,
|
|
181
|
+
invocationId: input.invocationId,
|
|
182
|
+
releaseProofHash: input.releaseProofHash,
|
|
183
|
+
providerExecutionReceiptHash: input.providerExecutionReceiptHash,
|
|
184
|
+
evidenceAttestationHash: input.evidenceAttestationHash,
|
|
185
|
+
settlementReceiptHash: input.settlementReceiptHash,
|
|
186
|
+
buyerFacingReceiptHash: input.buyerFacingReceiptHash,
|
|
187
|
+
transactionDigest: "preflight-digest",
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
function preflightEscrowSettlementRefund(input) {
|
|
191
|
+
recordEscrowSettlementRefund(input.receipt, {
|
|
192
|
+
at: new Date(0),
|
|
193
|
+
escrowId: input.escrowId,
|
|
194
|
+
invocationId: input.invocationId,
|
|
195
|
+
reason: input.reason,
|
|
196
|
+
settlementReceiptHash: input.settlementReceiptHash,
|
|
197
|
+
buyerFacingReceiptHash: input.buyerFacingReceiptHash,
|
|
198
|
+
transactionDigest: "preflight-digest",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
async function requireOpenStoreRecord(store, escrowId, invocationId) {
|
|
202
|
+
const record = await store.getByEscrowId(escrowId);
|
|
203
|
+
if (!record || record.status !== "open") {
|
|
204
|
+
throw new EscrowSettlementError("ESCROW_SETTLEMENT_NOT_OPEN", "Escrow settlement is not open.");
|
|
205
|
+
}
|
|
206
|
+
if (record.invocationId !== invocationId) {
|
|
207
|
+
throw new EscrowSettlementError("ESCROW_BINDING_MISMATCH", "Escrow invocation binding does not match.");
|
|
208
|
+
}
|
|
209
|
+
return record;
|
|
210
|
+
}
|
|
211
|
+
function requireStoreReceiptBinding(record, receipt) {
|
|
212
|
+
if (record.idempotencyKey !== receipt.idempotencyKey ||
|
|
213
|
+
record.receiptId !== receipt.receiptId ||
|
|
214
|
+
record.agentId !== receipt.agentId ||
|
|
215
|
+
record.ownerId !== receipt.ownerId ||
|
|
216
|
+
record.providerId !== receipt.escrow.providerId ||
|
|
217
|
+
record.verifierId !== receipt.escrow.verifierId) {
|
|
218
|
+
throw new EscrowSettlementError("ESCROW_BINDING_MISMATCH", "Escrow receipt binding does not match the stored settlement.");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function requireReceiptBinding(receipt, escrowId, invocationId) {
|
|
222
|
+
if (receipt.escrowSettlement?.escrowId !== escrowId || receipt.escrowSettlement.invocationId !== invocationId) {
|
|
223
|
+
throw new EscrowSettlementError("ESCROW_BINDING_MISMATCH", "Escrow proof binding does not match the receipt.");
|
|
224
|
+
}
|
|
225
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./IotaAgent.js";
|
|
2
2
|
export * from "./client.js";
|
|
3
3
|
export * from "./contracts/dataLicense.js";
|
|
4
|
+
export * from "./contracts/iotaEscrowSettlement.js";
|
|
4
5
|
export * from "./contracts/openEscrow.js";
|
|
5
6
|
export * from "./contracts/payPerCall.js";
|
|
6
7
|
export * from "./contracts/reputationReceipt.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./IotaAgent.js";
|
|
2
2
|
export * from "./client.js";
|
|
3
3
|
export * from "./contracts/dataLicense.js";
|
|
4
|
+
export * from "./contracts/iotaEscrowSettlement.js";
|
|
4
5
|
export * from "./contracts/openEscrow.js";
|
|
5
6
|
export * from "./contracts/payPerCall.js";
|
|
6
7
|
export * from "./contracts/reputationReceipt.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vallum/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
},
|
|
13
13
|
"license": "Apache-2.0",
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@vallum/manifest": "0.
|
|
16
|
-
"@vallum/registry": "0.
|
|
17
|
-
"@vallum/receipts": "0.
|
|
18
|
-
"@vallum/shared-types": "0.
|
|
15
|
+
"@vallum/manifest": "0.1.0",
|
|
16
|
+
"@vallum/registry": "0.1.0",
|
|
17
|
+
"@vallum/receipts": "0.1.0",
|
|
18
|
+
"@vallum/shared-types": "0.1.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@vallum/policy-gateway": "0.
|
|
21
|
+
"@vallum/policy-gateway": "0.1.0"
|
|
22
22
|
},
|
|
23
23
|
"description": "TypeScript SDK scaffold for Vallum sponsorship gateways.",
|
|
24
24
|
"files": [
|
|
@@ -36,6 +36,6 @@
|
|
|
36
36
|
},
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public",
|
|
39
|
-
"tag": "
|
|
39
|
+
"tag": "latest"
|
|
40
40
|
}
|
|
41
41
|
}
|