@medialane/sdk 0.3.1 → 0.4.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 +25 -0
- package/dist/index.cjs +209 -142
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -7
- package/dist/index.d.ts +50 -7
- package/dist/index.js +209 -142
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -181,7 +181,14 @@ const history = await client.api.getTokenHistory(contract, tokenId);
|
|
|
181
181
|
### Query Collections
|
|
182
182
|
|
|
183
183
|
```typescript
|
|
184
|
+
// All collections — newest first by default
|
|
184
185
|
const collections = await client.api.getCollections();
|
|
186
|
+
|
|
187
|
+
// With sort and pagination
|
|
188
|
+
const byVolume = await client.api.getCollections(1, 20, undefined, "volume");
|
|
189
|
+
const verified = await client.api.getCollections(1, 18, true, "recent");
|
|
190
|
+
|
|
191
|
+
// Sort options: "recent" | "supply" | "floor" | "volume" | "name"
|
|
185
192
|
const collection = await client.api.getCollection(contract);
|
|
186
193
|
const tokens = await client.api.getCollectionTokens(contract);
|
|
187
194
|
```
|
|
@@ -406,6 +413,24 @@ Built with:
|
|
|
406
413
|
|
|
407
414
|
## Changelog
|
|
408
415
|
|
|
416
|
+
### v0.3.3
|
|
417
|
+
- `getCollections(page?, limit?, isKnown?, sort?)` — added `sort` parameter: `"recent"` (default) | `"supply"` | `"floor"` | `"volume"` | `"name"`
|
|
418
|
+
- Default sort changed from `totalSupply DESC` to `createdAt DESC` (newest first) — matches backend default
|
|
419
|
+
|
|
420
|
+
### v0.3.1
|
|
421
|
+
- `ApiCollection.collectionId: string | null` — on-chain registry numeric ID (decimal string). Required for `createMintIntent`. Populated for collections indexed after 2026-03-09.
|
|
422
|
+
|
|
423
|
+
### v0.3.0
|
|
424
|
+
- `normalizeAddress()` now applied internally before all API calls — callers no longer need to normalize Starknet addresses before passing them to SDK methods
|
|
425
|
+
- `getCollectionsByOwner(owner)` — fetch collections by wallet address via API
|
|
426
|
+
|
|
427
|
+
### v0.2.8
|
|
428
|
+
- `ApiCollection.owner: string | null`
|
|
429
|
+
- `ApiClient.getCollectionsByOwner(owner)`
|
|
430
|
+
|
|
431
|
+
### v0.2.6
|
|
432
|
+
- `ApiOrder.token: ApiOrderTokenMeta | null` — token name/image/description on orders (batchTokenMeta)
|
|
433
|
+
|
|
409
434
|
### v0.2.0
|
|
410
435
|
- `IpAttribute` and `IpNftMetadata` interfaces for IP metadata
|
|
411
436
|
- `ApiTokenMetadata.attributes` typed as `IpAttribute[] | null` (was `unknown`)
|
package/dist/index.cjs
CHANGED
|
@@ -6,8 +6,10 @@ var starknet = require('starknet');
|
|
|
6
6
|
// src/config.ts
|
|
7
7
|
|
|
8
8
|
// src/constants.ts
|
|
9
|
-
var MARKETPLACE_CONTRACT_MAINNET = "
|
|
9
|
+
var MARKETPLACE_CONTRACT_MAINNET = "0x04299b51289aa700de4ce19cc77bcea8430bfd1aef04193efab09d60a3a7ee0f";
|
|
10
10
|
var COLLECTION_CONTRACT_MAINNET = "0x05e73b7be06d82beeb390a0e0d655f2c9e7cf519658e04f05d9c690ccc41da03";
|
|
11
|
+
var MARKETPLACE_CONTRACT_SEPOLIA = "";
|
|
12
|
+
var COLLECTION_CONTRACT_SEPOLIA = "";
|
|
11
13
|
var SUPPORTED_TOKENS = [
|
|
12
14
|
{
|
|
13
15
|
// Circle-native USDC on Starknet (canonical — preferred)
|
|
@@ -44,122 +46,6 @@ var DEFAULT_RPC_URLS = {
|
|
|
44
46
|
sepolia: "https://rpc.starknet-sepolia.lava.build"
|
|
45
47
|
};
|
|
46
48
|
|
|
47
|
-
// src/config.ts
|
|
48
|
-
var MedialaneConfigSchema = zod.z.object({
|
|
49
|
-
network: zod.z.enum(SUPPORTED_NETWORKS).default("mainnet"),
|
|
50
|
-
rpcUrl: zod.z.string().url().optional(),
|
|
51
|
-
backendUrl: zod.z.string().url().optional(),
|
|
52
|
-
/** API key for authenticated /v1/* backend endpoints */
|
|
53
|
-
apiKey: zod.z.string().optional(),
|
|
54
|
-
marketplaceContract: zod.z.string().optional(),
|
|
55
|
-
collectionContract: zod.z.string().optional()
|
|
56
|
-
});
|
|
57
|
-
function resolveConfig(raw) {
|
|
58
|
-
const parsed = MedialaneConfigSchema.parse(raw);
|
|
59
|
-
return {
|
|
60
|
-
network: parsed.network,
|
|
61
|
-
rpcUrl: parsed.rpcUrl ?? DEFAULT_RPC_URLS[parsed.network],
|
|
62
|
-
backendUrl: parsed.backendUrl,
|
|
63
|
-
apiKey: parsed.apiKey,
|
|
64
|
-
marketplaceContract: parsed.marketplaceContract ?? MARKETPLACE_CONTRACT_MAINNET,
|
|
65
|
-
collectionContract: parsed.collectionContract ?? COLLECTION_CONTRACT_MAINNET
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function buildOrderTypedData(message, chainId) {
|
|
69
|
-
return {
|
|
70
|
-
domain: {
|
|
71
|
-
name: "Medialane",
|
|
72
|
-
version: "1",
|
|
73
|
-
chainId,
|
|
74
|
-
revision: starknet.TypedDataRevision.ACTIVE
|
|
75
|
-
},
|
|
76
|
-
primaryType: "OrderParameters",
|
|
77
|
-
types: {
|
|
78
|
-
StarknetDomain: [
|
|
79
|
-
{ name: "name", type: "shortstring" },
|
|
80
|
-
{ name: "version", type: "shortstring" },
|
|
81
|
-
{ name: "chainId", type: "shortstring" },
|
|
82
|
-
{ name: "revision", type: "shortstring" }
|
|
83
|
-
],
|
|
84
|
-
OrderParameters: [
|
|
85
|
-
{ name: "offerer", type: "ContractAddress" },
|
|
86
|
-
{ name: "offer", type: "OfferItem" },
|
|
87
|
-
{ name: "consideration", type: "ConsiderationItem" },
|
|
88
|
-
{ name: "start_time", type: "felt" },
|
|
89
|
-
{ name: "end_time", type: "felt" },
|
|
90
|
-
{ name: "salt", type: "felt" },
|
|
91
|
-
{ name: "nonce", type: "felt" }
|
|
92
|
-
],
|
|
93
|
-
OfferItem: [
|
|
94
|
-
{ name: "item_type", type: "shortstring" },
|
|
95
|
-
{ name: "token", type: "ContractAddress" },
|
|
96
|
-
{ name: "identifier_or_criteria", type: "felt" },
|
|
97
|
-
{ name: "start_amount", type: "felt" },
|
|
98
|
-
{ name: "end_amount", type: "felt" }
|
|
99
|
-
],
|
|
100
|
-
ConsiderationItem: [
|
|
101
|
-
{ name: "item_type", type: "shortstring" },
|
|
102
|
-
{ name: "token", type: "ContractAddress" },
|
|
103
|
-
{ name: "identifier_or_criteria", type: "felt" },
|
|
104
|
-
{ name: "start_amount", type: "felt" },
|
|
105
|
-
{ name: "end_amount", type: "felt" },
|
|
106
|
-
{ name: "recipient", type: "ContractAddress" }
|
|
107
|
-
]
|
|
108
|
-
},
|
|
109
|
-
message
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
function buildFulfillmentTypedData(message, chainId) {
|
|
113
|
-
return {
|
|
114
|
-
domain: {
|
|
115
|
-
name: "Medialane",
|
|
116
|
-
version: "1",
|
|
117
|
-
chainId,
|
|
118
|
-
revision: starknet.TypedDataRevision.ACTIVE
|
|
119
|
-
},
|
|
120
|
-
primaryType: "OrderFulfillment",
|
|
121
|
-
types: {
|
|
122
|
-
StarknetDomain: [
|
|
123
|
-
{ name: "name", type: "shortstring" },
|
|
124
|
-
{ name: "version", type: "shortstring" },
|
|
125
|
-
{ name: "chainId", type: "shortstring" },
|
|
126
|
-
{ name: "revision", type: "shortstring" }
|
|
127
|
-
],
|
|
128
|
-
OrderFulfillment: [
|
|
129
|
-
{ name: "order_hash", type: "felt" },
|
|
130
|
-
{ name: "fulfiller", type: "ContractAddress" },
|
|
131
|
-
{ name: "nonce", type: "felt" }
|
|
132
|
-
]
|
|
133
|
-
},
|
|
134
|
-
message
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
function buildCancellationTypedData(message, chainId) {
|
|
138
|
-
return {
|
|
139
|
-
domain: {
|
|
140
|
-
name: "Medialane",
|
|
141
|
-
version: "1",
|
|
142
|
-
chainId,
|
|
143
|
-
revision: starknet.TypedDataRevision.ACTIVE
|
|
144
|
-
},
|
|
145
|
-
primaryType: "OrderCancellation",
|
|
146
|
-
types: {
|
|
147
|
-
StarknetDomain: [
|
|
148
|
-
{ name: "name", type: "shortstring" },
|
|
149
|
-
{ name: "version", type: "shortstring" },
|
|
150
|
-
{ name: "chainId", type: "shortstring" },
|
|
151
|
-
{ name: "revision", type: "shortstring" }
|
|
152
|
-
],
|
|
153
|
-
OrderCancellation: [
|
|
154
|
-
{ name: "order_hash", type: "felt" },
|
|
155
|
-
{ name: "offerer", type: "ContractAddress" },
|
|
156
|
-
{ name: "nonce", type: "felt" }
|
|
157
|
-
]
|
|
158
|
-
},
|
|
159
|
-
message
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
49
|
// src/abis.ts
|
|
164
50
|
var IPMarketplaceABI = [
|
|
165
51
|
{
|
|
@@ -512,11 +398,106 @@ function getTokenBySymbol(symbol) {
|
|
|
512
398
|
const upper = symbol.toUpperCase();
|
|
513
399
|
return SUPPORTED_TOKENS.find((t) => t.symbol === upper);
|
|
514
400
|
}
|
|
401
|
+
function buildOrderTypedData(message, chainId) {
|
|
402
|
+
return {
|
|
403
|
+
domain: {
|
|
404
|
+
name: "Medialane",
|
|
405
|
+
version: "1",
|
|
406
|
+
chainId,
|
|
407
|
+
revision: starknet.TypedDataRevision.ACTIVE
|
|
408
|
+
},
|
|
409
|
+
primaryType: "OrderParameters",
|
|
410
|
+
types: {
|
|
411
|
+
StarknetDomain: [
|
|
412
|
+
{ name: "name", type: "shortstring" },
|
|
413
|
+
{ name: "version", type: "shortstring" },
|
|
414
|
+
{ name: "chainId", type: "shortstring" },
|
|
415
|
+
{ name: "revision", type: "shortstring" }
|
|
416
|
+
],
|
|
417
|
+
OrderParameters: [
|
|
418
|
+
{ name: "offerer", type: "ContractAddress" },
|
|
419
|
+
{ name: "offer", type: "OfferItem" },
|
|
420
|
+
{ name: "consideration", type: "ConsiderationItem" },
|
|
421
|
+
{ name: "start_time", type: "felt" },
|
|
422
|
+
{ name: "end_time", type: "felt" },
|
|
423
|
+
{ name: "salt", type: "felt" },
|
|
424
|
+
{ name: "nonce", type: "felt" }
|
|
425
|
+
],
|
|
426
|
+
OfferItem: [
|
|
427
|
+
{ name: "item_type", type: "shortstring" },
|
|
428
|
+
{ name: "token", type: "ContractAddress" },
|
|
429
|
+
{ name: "identifier_or_criteria", type: "felt" },
|
|
430
|
+
{ name: "start_amount", type: "felt" },
|
|
431
|
+
{ name: "end_amount", type: "felt" }
|
|
432
|
+
],
|
|
433
|
+
ConsiderationItem: [
|
|
434
|
+
{ name: "item_type", type: "shortstring" },
|
|
435
|
+
{ name: "token", type: "ContractAddress" },
|
|
436
|
+
{ name: "identifier_or_criteria", type: "felt" },
|
|
437
|
+
{ name: "start_amount", type: "felt" },
|
|
438
|
+
{ name: "end_amount", type: "felt" },
|
|
439
|
+
{ name: "recipient", type: "ContractAddress" }
|
|
440
|
+
]
|
|
441
|
+
},
|
|
442
|
+
message
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function buildFulfillmentTypedData(message, chainId) {
|
|
446
|
+
return {
|
|
447
|
+
domain: {
|
|
448
|
+
name: "Medialane",
|
|
449
|
+
version: "1",
|
|
450
|
+
chainId,
|
|
451
|
+
revision: starknet.TypedDataRevision.ACTIVE
|
|
452
|
+
},
|
|
453
|
+
primaryType: "OrderFulfillment",
|
|
454
|
+
types: {
|
|
455
|
+
StarknetDomain: [
|
|
456
|
+
{ name: "name", type: "shortstring" },
|
|
457
|
+
{ name: "version", type: "shortstring" },
|
|
458
|
+
{ name: "chainId", type: "shortstring" },
|
|
459
|
+
{ name: "revision", type: "shortstring" }
|
|
460
|
+
],
|
|
461
|
+
OrderFulfillment: [
|
|
462
|
+
{ name: "order_hash", type: "felt" },
|
|
463
|
+
{ name: "fulfiller", type: "ContractAddress" },
|
|
464
|
+
{ name: "nonce", type: "felt" }
|
|
465
|
+
]
|
|
466
|
+
},
|
|
467
|
+
message
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function buildCancellationTypedData(message, chainId) {
|
|
471
|
+
return {
|
|
472
|
+
domain: {
|
|
473
|
+
name: "Medialane",
|
|
474
|
+
version: "1",
|
|
475
|
+
chainId,
|
|
476
|
+
revision: starknet.TypedDataRevision.ACTIVE
|
|
477
|
+
},
|
|
478
|
+
primaryType: "OrderCancellation",
|
|
479
|
+
types: {
|
|
480
|
+
StarknetDomain: [
|
|
481
|
+
{ name: "name", type: "shortstring" },
|
|
482
|
+
{ name: "version", type: "shortstring" },
|
|
483
|
+
{ name: "chainId", type: "shortstring" },
|
|
484
|
+
{ name: "revision", type: "shortstring" }
|
|
485
|
+
],
|
|
486
|
+
OrderCancellation: [
|
|
487
|
+
{ name: "order_hash", type: "felt" },
|
|
488
|
+
{ name: "offerer", type: "ContractAddress" },
|
|
489
|
+
{ name: "nonce", type: "felt" }
|
|
490
|
+
]
|
|
491
|
+
},
|
|
492
|
+
message
|
|
493
|
+
};
|
|
494
|
+
}
|
|
515
495
|
|
|
516
496
|
// src/marketplace/orders.ts
|
|
517
497
|
var MedialaneError = class extends Error {
|
|
518
|
-
constructor(message, cause) {
|
|
498
|
+
constructor(message, code = "UNKNOWN", cause) {
|
|
519
499
|
super(message);
|
|
500
|
+
this.code = code;
|
|
520
501
|
this.cause = cause;
|
|
521
502
|
this.name = "MedialaneError";
|
|
522
503
|
}
|
|
@@ -556,7 +537,7 @@ function resolveToken(currency) {
|
|
|
556
537
|
const token = SUPPORTED_TOKENS.find(
|
|
557
538
|
(t) => t.symbol === currency.toUpperCase() || t.address.toLowerCase() === currency.toLowerCase()
|
|
558
539
|
);
|
|
559
|
-
if (!token) throw new MedialaneError(`Unsupported currency: ${currency}
|
|
540
|
+
if (!token) throw new MedialaneError(`Unsupported currency: ${currency}`, "INVALID_PARAMS");
|
|
560
541
|
return token;
|
|
561
542
|
}
|
|
562
543
|
async function createListing(account, params, config) {
|
|
@@ -640,7 +621,7 @@ async function createListing(account, params, config) {
|
|
|
640
621
|
await provider.waitForTransaction(tx.transaction_hash);
|
|
641
622
|
return { txHash: tx.transaction_hash };
|
|
642
623
|
} catch (err) {
|
|
643
|
-
throw new MedialaneError("Failed to create listing", err);
|
|
624
|
+
throw new MedialaneError("Failed to create listing", "TRANSACTION_FAILED", err);
|
|
644
625
|
}
|
|
645
626
|
}
|
|
646
627
|
async function makeOffer(account, params, config) {
|
|
@@ -711,7 +692,7 @@ async function makeOffer(account, params, config) {
|
|
|
711
692
|
await provider.waitForTransaction(tx.transaction_hash);
|
|
712
693
|
return { txHash: tx.transaction_hash };
|
|
713
694
|
} catch (err) {
|
|
714
|
-
throw new MedialaneError("Failed to make offer", err);
|
|
695
|
+
throw new MedialaneError("Failed to make offer", "TRANSACTION_FAILED", err);
|
|
715
696
|
}
|
|
716
697
|
}
|
|
717
698
|
async function fulfillOrder(account, params, config) {
|
|
@@ -739,7 +720,7 @@ async function fulfillOrder(account, params, config) {
|
|
|
739
720
|
await provider.waitForTransaction(tx.transaction_hash);
|
|
740
721
|
return { txHash: tx.transaction_hash };
|
|
741
722
|
} catch (err) {
|
|
742
|
-
throw new MedialaneError("Failed to fulfill order", err);
|
|
723
|
+
throw new MedialaneError("Failed to fulfill order", "TRANSACTION_FAILED", err);
|
|
743
724
|
}
|
|
744
725
|
}
|
|
745
726
|
async function cancelOrder(account, params, config) {
|
|
@@ -767,7 +748,7 @@ async function cancelOrder(account, params, config) {
|
|
|
767
748
|
await provider.waitForTransaction(tx.transaction_hash);
|
|
768
749
|
return { txHash: tx.transaction_hash };
|
|
769
750
|
} catch (err) {
|
|
770
|
-
throw new MedialaneError("Failed to cancel order", err);
|
|
751
|
+
throw new MedialaneError("Failed to cancel order", "TRANSACTION_FAILED", err);
|
|
771
752
|
}
|
|
772
753
|
}
|
|
773
754
|
function encodeByteArray(str) {
|
|
@@ -790,7 +771,7 @@ async function mint(account, params, config) {
|
|
|
790
771
|
await provider.waitForTransaction(tx.transaction_hash);
|
|
791
772
|
return { txHash: tx.transaction_hash };
|
|
792
773
|
} catch (err) {
|
|
793
|
-
throw new MedialaneError("Failed to mint NFT", err);
|
|
774
|
+
throw new MedialaneError("Failed to mint NFT", "TRANSACTION_FAILED", err);
|
|
794
775
|
}
|
|
795
776
|
}
|
|
796
777
|
async function createCollection(account, params, config) {
|
|
@@ -807,11 +788,11 @@ async function createCollection(account, params, config) {
|
|
|
807
788
|
await provider.waitForTransaction(tx.transaction_hash);
|
|
808
789
|
return { txHash: tx.transaction_hash };
|
|
809
790
|
} catch (err) {
|
|
810
|
-
throw new MedialaneError("Failed to create collection", err);
|
|
791
|
+
throw new MedialaneError("Failed to create collection", "TRANSACTION_FAILED", err);
|
|
811
792
|
}
|
|
812
793
|
}
|
|
813
794
|
async function checkoutCart(account, items, config) {
|
|
814
|
-
if (items.length === 0) throw new MedialaneError("Cart is empty");
|
|
795
|
+
if (items.length === 0) throw new MedialaneError("Cart is empty", "INVALID_PARAMS");
|
|
815
796
|
const { contract, provider } = makeContract(config);
|
|
816
797
|
const tokenTotals = /* @__PURE__ */ new Map();
|
|
817
798
|
for (const item of items) {
|
|
@@ -858,8 +839,47 @@ async function checkoutCart(account, items, config) {
|
|
|
858
839
|
await provider.waitForTransaction(tx.transaction_hash);
|
|
859
840
|
return { txHash: tx.transaction_hash };
|
|
860
841
|
} catch (err) {
|
|
861
|
-
throw new MedialaneError("Cart checkout failed", err);
|
|
842
|
+
throw new MedialaneError("Cart checkout failed", "TRANSACTION_FAILED", err);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// src/config.ts
|
|
847
|
+
var MedialaneConfigSchema = zod.z.object({
|
|
848
|
+
network: zod.z.enum(SUPPORTED_NETWORKS).default("mainnet"),
|
|
849
|
+
rpcUrl: zod.z.string().url().optional(),
|
|
850
|
+
backendUrl: zod.z.string().url().optional(),
|
|
851
|
+
/** API key for authenticated /v1/* backend endpoints */
|
|
852
|
+
apiKey: zod.z.string().optional(),
|
|
853
|
+
marketplaceContract: zod.z.string().optional(),
|
|
854
|
+
collectionContract: zod.z.string().optional(),
|
|
855
|
+
retryOptions: zod.z.object({
|
|
856
|
+
maxAttempts: zod.z.number().int().min(1).max(10).optional(),
|
|
857
|
+
baseDelayMs: zod.z.number().int().min(0).optional(),
|
|
858
|
+
maxDelayMs: zod.z.number().int().min(0).optional()
|
|
859
|
+
}).optional()
|
|
860
|
+
});
|
|
861
|
+
function resolveConfig(raw) {
|
|
862
|
+
const parsed = MedialaneConfigSchema.parse(raw);
|
|
863
|
+
const isMainnet = parsed.network === "mainnet";
|
|
864
|
+
const defaultMarketplace = isMainnet ? MARKETPLACE_CONTRACT_MAINNET : MARKETPLACE_CONTRACT_SEPOLIA;
|
|
865
|
+
const defaultCollection = isMainnet ? COLLECTION_CONTRACT_MAINNET : COLLECTION_CONTRACT_SEPOLIA;
|
|
866
|
+
const marketplaceContract = parsed.marketplaceContract ?? defaultMarketplace;
|
|
867
|
+
const collectionContract = parsed.collectionContract ?? defaultCollection;
|
|
868
|
+
if (!marketplaceContract || !collectionContract) {
|
|
869
|
+
throw new MedialaneError(
|
|
870
|
+
`Sepolia network is not yet supported: marketplace and collection contract addresses are not configured. Pass 'marketplaceContract' and 'collectionContract' explicitly in your MedialaneClient config.`,
|
|
871
|
+
"NETWORK_NOT_SUPPORTED"
|
|
872
|
+
);
|
|
862
873
|
}
|
|
874
|
+
return {
|
|
875
|
+
network: parsed.network,
|
|
876
|
+
rpcUrl: parsed.rpcUrl ?? DEFAULT_RPC_URLS[parsed.network],
|
|
877
|
+
backendUrl: parsed.backendUrl,
|
|
878
|
+
apiKey: parsed.apiKey,
|
|
879
|
+
marketplaceContract,
|
|
880
|
+
collectionContract,
|
|
881
|
+
retryOptions: parsed.retryOptions
|
|
882
|
+
};
|
|
863
883
|
}
|
|
864
884
|
|
|
865
885
|
// src/marketplace/index.ts
|
|
@@ -911,18 +931,60 @@ function shortenAddress(address, chars = 4) {
|
|
|
911
931
|
return `${norm.slice(0, chars + 2)}...${norm.slice(-chars)}`;
|
|
912
932
|
}
|
|
913
933
|
|
|
934
|
+
// src/utils/retry.ts
|
|
935
|
+
var DEFAULT_MAX_ATTEMPTS = 3;
|
|
936
|
+
var DEFAULT_BASE_DELAY_MS = 300;
|
|
937
|
+
var DEFAULT_MAX_DELAY_MS = 5e3;
|
|
938
|
+
function sleep(ms) {
|
|
939
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
940
|
+
}
|
|
941
|
+
async function withRetry(fn, opts) {
|
|
942
|
+
const maxAttempts = opts?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
943
|
+
const baseDelayMs = opts?.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
|
|
944
|
+
const maxDelayMs = opts?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
|
|
945
|
+
let lastError;
|
|
946
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
947
|
+
try {
|
|
948
|
+
return await fn();
|
|
949
|
+
} catch (err) {
|
|
950
|
+
lastError = err;
|
|
951
|
+
if (err instanceof MedialaneApiError && err.status < 500) {
|
|
952
|
+
throw err;
|
|
953
|
+
}
|
|
954
|
+
const isRetryable = err instanceof MedialaneApiError && err.status >= 500 || err instanceof TypeError;
|
|
955
|
+
if (!isRetryable || attempt === maxAttempts - 1) {
|
|
956
|
+
throw err;
|
|
957
|
+
}
|
|
958
|
+
const jitter = Math.random() * baseDelayMs;
|
|
959
|
+
const delay = Math.min(baseDelayMs * Math.pow(2, attempt) + jitter, maxDelayMs);
|
|
960
|
+
await sleep(delay);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
throw lastError;
|
|
964
|
+
}
|
|
965
|
+
|
|
914
966
|
// src/api/client.ts
|
|
967
|
+
function deriveErrorCode(status) {
|
|
968
|
+
if (status === 404) return "TOKEN_NOT_FOUND";
|
|
969
|
+
if (status === 429) return "RATE_LIMITED";
|
|
970
|
+
if (status === 410) return "INTENT_EXPIRED";
|
|
971
|
+
if (status === 401 || status === 403) return "UNAUTHORIZED";
|
|
972
|
+
if (status === 400) return "INVALID_PARAMS";
|
|
973
|
+
return "UNKNOWN";
|
|
974
|
+
}
|
|
915
975
|
var MedialaneApiError = class extends Error {
|
|
916
976
|
constructor(status, message) {
|
|
917
977
|
super(message);
|
|
918
978
|
this.status = status;
|
|
919
979
|
this.name = "MedialaneApiError";
|
|
980
|
+
this.code = deriveErrorCode(status);
|
|
920
981
|
}
|
|
921
982
|
};
|
|
922
983
|
var ApiClient = class {
|
|
923
|
-
constructor(baseUrl, apiKey) {
|
|
984
|
+
constructor(baseUrl, apiKey, retryOptions) {
|
|
924
985
|
this.baseUrl = baseUrl;
|
|
925
986
|
this.baseHeaders = apiKey ? { "x-api-key": apiKey } : {};
|
|
987
|
+
this.retryOptions = retryOptions;
|
|
926
988
|
}
|
|
927
989
|
async request(path, init) {
|
|
928
990
|
const url = `${this.baseUrl.replace(/\/$/, "")}${path}`;
|
|
@@ -930,19 +992,23 @@ var ApiClient = class {
|
|
|
930
992
|
if (!(init?.body instanceof FormData)) {
|
|
931
993
|
headers["Content-Type"] = "application/json";
|
|
932
994
|
}
|
|
933
|
-
const res = await
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
995
|
+
const res = await withRetry(async () => {
|
|
996
|
+
const response = await fetch(url, {
|
|
997
|
+
...init,
|
|
998
|
+
headers: { ...headers, ...init?.headers }
|
|
999
|
+
});
|
|
1000
|
+
if (!response.ok) {
|
|
1001
|
+
const text = await response.text().catch(() => response.statusText);
|
|
1002
|
+
let message = text;
|
|
1003
|
+
try {
|
|
1004
|
+
const body = JSON.parse(text);
|
|
1005
|
+
if (body.error) message = body.error;
|
|
1006
|
+
} catch {
|
|
1007
|
+
}
|
|
1008
|
+
throw new MedialaneApiError(response.status, message);
|
|
943
1009
|
}
|
|
944
|
-
|
|
945
|
-
}
|
|
1010
|
+
return response;
|
|
1011
|
+
}, this.retryOptions);
|
|
946
1012
|
return res.json();
|
|
947
1013
|
}
|
|
948
1014
|
get(path) {
|
|
@@ -1000,9 +1066,10 @@ var ApiClient = class {
|
|
|
1000
1066
|
);
|
|
1001
1067
|
}
|
|
1002
1068
|
// ─── Collections ───────────────────────────────────────────────────────────
|
|
1003
|
-
getCollections(page = 1, limit = 20, isKnown) {
|
|
1069
|
+
getCollections(page = 1, limit = 20, isKnown, sort) {
|
|
1004
1070
|
const params = new URLSearchParams({ page: String(page), limit: String(limit) });
|
|
1005
1071
|
if (isKnown !== void 0) params.set("isKnown", String(isKnown));
|
|
1072
|
+
if (sort) params.set("sort", sort);
|
|
1006
1073
|
return this.get(`/v1/collections?${params}`);
|
|
1007
1074
|
}
|
|
1008
1075
|
getCollectionsByOwner(owner, page = 1, limit = 50) {
|
|
@@ -1127,7 +1194,7 @@ var MedialaneClient = class {
|
|
|
1127
1194
|
}
|
|
1128
1195
|
});
|
|
1129
1196
|
} else {
|
|
1130
|
-
this.api = new ApiClient(this.config.backendUrl, this.config.apiKey);
|
|
1197
|
+
this.api = new ApiClient(this.config.backendUrl, this.config.apiKey, this.config.retryOptions);
|
|
1131
1198
|
}
|
|
1132
1199
|
}
|
|
1133
1200
|
get network() {
|