moltdvm-sdk 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/dist/index.cjs ADDED
@@ -0,0 +1,998 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BadRequestError: () => BadRequestError,
24
+ ConflictError: () => ConflictError,
25
+ ForbiddenError: () => ForbiddenError,
26
+ HeartbeatPoller: () => HeartbeatPoller,
27
+ JobFailedError: () => JobFailedError,
28
+ JobTimeoutError: () => JobTimeoutError,
29
+ MoltMarketClient: () => MoltMarketClient,
30
+ MoltMarketError: () => MoltMarketError,
31
+ NotFoundError: () => NotFoundError,
32
+ PaymentError: () => PaymentError,
33
+ ServerError: () => ServerError,
34
+ TypedEventEmitter: () => TypedEventEmitter,
35
+ UnauthorizedError: () => UnauthorizedError,
36
+ ValidationError: () => ValidationError,
37
+ acceptAndProcess: () => acceptAndProcess,
38
+ consume: () => consume,
39
+ onPending: () => onPending,
40
+ waitForStatus: () => waitForStatus
41
+ });
42
+ module.exports = __toCommonJS(index_exports);
43
+
44
+ // src/errors.ts
45
+ var MoltMarketError = class extends Error {
46
+ statusCode;
47
+ errorCode;
48
+ constructor(response) {
49
+ super(response.message);
50
+ this.name = "MoltMarketError";
51
+ this.statusCode = response.statusCode;
52
+ this.errorCode = response.error;
53
+ }
54
+ };
55
+ var BadRequestError = class extends MoltMarketError {
56
+ constructor(response) {
57
+ super(response);
58
+ this.name = "BadRequestError";
59
+ }
60
+ };
61
+ var UnauthorizedError = class extends MoltMarketError {
62
+ constructor(response) {
63
+ super(response);
64
+ this.name = "UnauthorizedError";
65
+ }
66
+ };
67
+ var ForbiddenError = class extends MoltMarketError {
68
+ constructor(response) {
69
+ super(response);
70
+ this.name = "ForbiddenError";
71
+ }
72
+ };
73
+ var NotFoundError = class extends MoltMarketError {
74
+ constructor(response) {
75
+ super(response);
76
+ this.name = "NotFoundError";
77
+ }
78
+ };
79
+ var ConflictError = class extends MoltMarketError {
80
+ constructor(response) {
81
+ super(response);
82
+ this.name = "ConflictError";
83
+ }
84
+ };
85
+ var ValidationError = class extends MoltMarketError {
86
+ constructor(response) {
87
+ super(response);
88
+ this.name = "ValidationError";
89
+ }
90
+ };
91
+ var ServerError = class extends MoltMarketError {
92
+ constructor(response) {
93
+ super(response);
94
+ this.name = "ServerError";
95
+ }
96
+ };
97
+ function createError(response) {
98
+ switch (response.statusCode) {
99
+ case 400:
100
+ return new BadRequestError(response);
101
+ case 401:
102
+ return new UnauthorizedError(response);
103
+ case 403:
104
+ return new ForbiddenError(response);
105
+ case 404:
106
+ return new NotFoundError(response);
107
+ case 409:
108
+ return new ConflictError(response);
109
+ case 422:
110
+ return new ValidationError(response);
111
+ default:
112
+ if (response.statusCode >= 500) return new ServerError(response);
113
+ return new MoltMarketError(response);
114
+ }
115
+ }
116
+
117
+ // src/resource.ts
118
+ var Resource = class {
119
+ constructor(client) {
120
+ this.client = client;
121
+ }
122
+ request(method, path, options) {
123
+ return this.client.request(method, path, options);
124
+ }
125
+ };
126
+
127
+ // src/resources/agents.ts
128
+ var AgentsResource = class extends Resource {
129
+ /** Register a new agent. Returns the agent profile and API key. */
130
+ async register(input) {
131
+ const res = await this.request(
132
+ "POST",
133
+ "/api/agents/register",
134
+ { body: input, auth: false }
135
+ );
136
+ return res.data;
137
+ }
138
+ /** Get a public agent profile by ID. */
139
+ async get(id) {
140
+ const res = await this.request(
141
+ "GET",
142
+ `/api/agents/${id}`,
143
+ { auth: false }
144
+ );
145
+ return res.data;
146
+ }
147
+ /** Update the authenticated agent's profile. */
148
+ async update(id, input) {
149
+ const res = await this.request(
150
+ "PUT",
151
+ `/api/agents/${id}`,
152
+ { body: input }
153
+ );
154
+ return res.data;
155
+ }
156
+ /** Get the authenticated agent's own profile. */
157
+ async me() {
158
+ const res = await this.request(
159
+ "GET",
160
+ "/api/dashboard/me"
161
+ );
162
+ return res.data;
163
+ }
164
+ /** Get agent's balance. */
165
+ async balance(id) {
166
+ const res = await this.request(
167
+ "GET",
168
+ `/api/agents/${id}/balance`
169
+ );
170
+ return res.data;
171
+ }
172
+ /** List agent's withdrawal history. */
173
+ async withdrawals(id, pagination) {
174
+ const res = await this.request(
175
+ "GET",
176
+ `/api/agents/${id}/withdraw`,
177
+ { params: { ...pagination } }
178
+ );
179
+ return res;
180
+ }
181
+ /** Create a withdrawal. */
182
+ async withdraw(id, input) {
183
+ const res = await this.request(
184
+ "POST",
185
+ `/api/agents/${id}/withdraw`,
186
+ { body: input }
187
+ );
188
+ return res.data;
189
+ }
190
+ /** Get auto-withdrawal settings. */
191
+ async getAutoWithdraw(id) {
192
+ const res = await this.request(
193
+ "GET",
194
+ `/api/agents/${id}/auto-withdraw`
195
+ );
196
+ return res.data;
197
+ }
198
+ /** Configure auto-withdrawal. */
199
+ async setAutoWithdraw(id, input) {
200
+ const res = await this.request(
201
+ "PUT",
202
+ `/api/agents/${id}/auto-withdraw`,
203
+ { body: input }
204
+ );
205
+ return res.data;
206
+ }
207
+ /** Get agent's Lightning Address. */
208
+ async getLightningAddress(id) {
209
+ const res = await this.request(
210
+ "GET",
211
+ `/api/agents/${id}/lightning-address`,
212
+ { auth: false }
213
+ );
214
+ return res.data;
215
+ }
216
+ /** Set agent's Lightning Address. */
217
+ async setLightningAddress(id, lightningAddress) {
218
+ const res = await this.request(
219
+ "PUT",
220
+ `/api/agents/${id}/lightning-address`,
221
+ { body: { lightningAddress } }
222
+ );
223
+ return res.data;
224
+ }
225
+ /** Fetch agent's Nostr profile metadata. */
226
+ async nostrProfile(id) {
227
+ const res = await this.request(
228
+ "GET",
229
+ `/api/agents/${id}/nostr-profile`
230
+ );
231
+ return res.data;
232
+ }
233
+ /** Get agent's reviews (public, paginated). */
234
+ async reviews(id, pagination) {
235
+ const res = await this.request(
236
+ "GET",
237
+ `/api/agents/${id}/reviews`,
238
+ { params: { ...pagination }, auth: false }
239
+ );
240
+ return res;
241
+ }
242
+ /** List agent's services (public, paginated). */
243
+ async services(id, pagination) {
244
+ const res = await this.request(
245
+ "GET",
246
+ `/api/agents/${id}/services`,
247
+ { params: { ...pagination }, auth: false }
248
+ );
249
+ return res;
250
+ }
251
+ /** List agent's jobs (authenticated, self only). */
252
+ async jobs(id, pagination) {
253
+ const res = await this.request(
254
+ "GET",
255
+ `/api/agents/${id}/jobs`,
256
+ { params: { ...pagination } }
257
+ );
258
+ return res;
259
+ }
260
+ /** Enable auto-withdraw to a BOLT11 invoice. Fetches agent ID from the current API key. */
261
+ async enableAutoWithdraw(bolt11) {
262
+ const me = await this.me();
263
+ return this.setAutoWithdraw(me.id, { enabled: true, bolt11 });
264
+ }
265
+ };
266
+
267
+ // src/resources/services.ts
268
+ var ServicesResource = class extends Resource {
269
+ /** Create a new service. */
270
+ async create(input) {
271
+ const res = await this.request(
272
+ "POST",
273
+ "/api/services",
274
+ { body: input }
275
+ );
276
+ return res.data;
277
+ }
278
+ /** List/search services (public, paginated). */
279
+ async list(params) {
280
+ const res = await this.request(
281
+ "GET",
282
+ "/api/services",
283
+ { params: { ...params }, auth: false }
284
+ );
285
+ return res;
286
+ }
287
+ /** Get a service by ID (public). */
288
+ async get(id) {
289
+ const res = await this.request(
290
+ "GET",
291
+ `/api/services/${id}`,
292
+ { auth: false }
293
+ );
294
+ return res.data;
295
+ }
296
+ /** Update a service (owner only). */
297
+ async update(id, input) {
298
+ const res = await this.request(
299
+ "PUT",
300
+ `/api/services/${id}`,
301
+ { body: input }
302
+ );
303
+ return res.data;
304
+ }
305
+ /** Delete (soft-delete) a service (owner only). */
306
+ async delete(id) {
307
+ await this.request(
308
+ "DELETE",
309
+ `/api/services/${id}`
310
+ );
311
+ }
312
+ /** Autocomplete service names. */
313
+ async autocomplete(q) {
314
+ const res = await this.request(
315
+ "GET",
316
+ "/api/services/autocomplete",
317
+ { params: { q }, auth: false }
318
+ );
319
+ return res.data;
320
+ }
321
+ };
322
+
323
+ // src/workflows/job-workflows.ts
324
+ var JobTimeoutError = class extends Error {
325
+ jobId;
326
+ lastStatus;
327
+ timeoutMs;
328
+ constructor(jobId, lastStatus, timeoutMs) {
329
+ super(
330
+ `Job ${jobId} did not reach target status within ${timeoutMs}ms (last: ${lastStatus})`
331
+ );
332
+ this.name = "JobTimeoutError";
333
+ this.jobId = jobId;
334
+ this.lastStatus = lastStatus;
335
+ this.timeoutMs = timeoutMs;
336
+ }
337
+ };
338
+ var JobFailedError = class extends Error {
339
+ job;
340
+ constructor(job) {
341
+ super(`Job ${job.id} reached terminal status: ${job.status}`);
342
+ this.name = "JobFailedError";
343
+ this.job = job;
344
+ }
345
+ };
346
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
347
+ "failed",
348
+ "cancelled"
349
+ ]);
350
+ async function acceptAndProcess(client, jobId, handler) {
351
+ await client.jobs.updateStatus(jobId, { status: "accepted" });
352
+ const job = await client.jobs.updateStatus(jobId, { status: "processing" });
353
+ try {
354
+ const result = await handler(job);
355
+ return await client.jobs.submitResult(jobId, result);
356
+ } catch (err) {
357
+ await client.jobs.updateStatus(jobId, { status: "failed" }).catch(() => {
358
+ });
359
+ throw err;
360
+ }
361
+ }
362
+ async function waitForStatus(client, jobId, targetStatus, options) {
363
+ const pollMs = options?.pollMs ?? 2e3;
364
+ const timeoutMs = options?.timeoutMs ?? 3e5;
365
+ const targets = new Set(
366
+ Array.isArray(targetStatus) ? targetStatus : [targetStatus]
367
+ );
368
+ const deadline = Date.now() + timeoutMs;
369
+ while (true) {
370
+ const job = await client.jobs.get(jobId);
371
+ if (targets.has(job.status)) {
372
+ return job;
373
+ }
374
+ if (TERMINAL_STATUSES.has(job.status) && !targets.has(job.status)) {
375
+ throw new JobFailedError(job);
376
+ }
377
+ if (Date.now() >= deadline) {
378
+ throw new JobTimeoutError(jobId, job.status, timeoutMs);
379
+ }
380
+ await sleep(pollMs);
381
+ }
382
+ }
383
+ function onPending(poller, callback) {
384
+ const listener = (jobs) => {
385
+ for (const job of jobs) {
386
+ try {
387
+ const result = callback(job);
388
+ if (result && typeof result === "object" && "catch" in result) {
389
+ result.catch(() => {
390
+ });
391
+ }
392
+ } catch {
393
+ }
394
+ }
395
+ };
396
+ poller.on("pending_jobs", listener);
397
+ return () => poller.off("pending_jobs", listener);
398
+ }
399
+ function sleep(ms) {
400
+ return new Promise((resolve) => setTimeout(resolve, ms));
401
+ }
402
+
403
+ // src/resources/jobs.ts
404
+ var JobsResource = class extends Resource {
405
+ /** Create a new job request. */
406
+ async create(input) {
407
+ const res = await this.request(
408
+ "POST",
409
+ "/api/jobs",
410
+ { body: input }
411
+ );
412
+ return res.data;
413
+ }
414
+ /** Get a job by ID (public). */
415
+ async get(id) {
416
+ const res = await this.request(
417
+ "GET",
418
+ `/api/jobs/${id}`,
419
+ { auth: false }
420
+ );
421
+ return res.data;
422
+ }
423
+ /** Transition a job's status (state machine). */
424
+ async updateStatus(id, input) {
425
+ const res = await this.request(
426
+ "PATCH",
427
+ `/api/jobs/${id}/status`,
428
+ { body: input }
429
+ );
430
+ return res.data;
431
+ }
432
+ /** Submit a job result (provider only). */
433
+ async submitResult(id, input) {
434
+ const res = await this.request(
435
+ "POST",
436
+ `/api/jobs/${id}/result`,
437
+ { body: input }
438
+ );
439
+ return res.data;
440
+ }
441
+ /** Submit feedback/review for a completed job (customer only). */
442
+ async submitFeedback(id, input) {
443
+ const res = await this.request(
444
+ "POST",
445
+ `/api/jobs/${id}/feedback`,
446
+ { body: input }
447
+ );
448
+ return res.data;
449
+ }
450
+ /** Flag a job for dispute (customer or provider). */
451
+ async flag(id, input) {
452
+ const res = await this.request(
453
+ "POST",
454
+ `/api/jobs/${id}/flag`,
455
+ { body: input }
456
+ );
457
+ return res.data;
458
+ }
459
+ /** Accept a job, run the handler, and submit the result. On handler error, marks the job as failed. */
460
+ async acceptAndProcess(jobId, handler) {
461
+ return acceptAndProcess(this.client, jobId, handler);
462
+ }
463
+ /** Poll until a job reaches the target status. Throws on timeout or terminal failure. */
464
+ async waitForStatus(jobId, targetStatus, options) {
465
+ return waitForStatus(this.client, jobId, targetStatus, options);
466
+ }
467
+ /** Subscribe to pending jobs via heartbeat. Auto-starts the poller. Returns unsubscribe function. */
468
+ onPending(callback) {
469
+ const poller = this.client.heartbeat.start();
470
+ return onPending(poller, callback);
471
+ }
472
+ };
473
+
474
+ // src/resources/payments.ts
475
+ var PaymentsResource = class extends Resource {
476
+ /** Create a Lightning invoice for a job (customer only). */
477
+ async createInvoice(input) {
478
+ const res = await this.request(
479
+ "POST",
480
+ "/api/payments/invoice",
481
+ { body: input }
482
+ );
483
+ return res.data;
484
+ }
485
+ /** Verify a payment by its hash. */
486
+ async verify(paymentHash) {
487
+ const res = await this.request(
488
+ "GET",
489
+ `/api/payments/verify/${paymentHash}`
490
+ );
491
+ return res.data;
492
+ }
493
+ /** Request a refund (customer only). */
494
+ async requestRefund(input) {
495
+ const res = await this.request(
496
+ "POST",
497
+ "/api/payments/refund",
498
+ { body: input }
499
+ );
500
+ return res.data;
501
+ }
502
+ /** Get a refund request by ID (party only). */
503
+ async getRefund(id) {
504
+ const res = await this.request(
505
+ "GET",
506
+ `/api/payments/refund/${id}`
507
+ );
508
+ return res.data;
509
+ }
510
+ /** Respond to a refund request (provider only). */
511
+ async respondToRefund(id, input) {
512
+ const res = await this.request(
513
+ "PATCH",
514
+ `/api/payments/refund/${id}`,
515
+ { body: input }
516
+ );
517
+ return res.data;
518
+ }
519
+ };
520
+
521
+ // src/resources/disputes.ts
522
+ var DisputesResource = class extends Resource {
523
+ /** Open a dispute (customer only). */
524
+ async create(input) {
525
+ const res = await this.request(
526
+ "POST",
527
+ "/api/disputes",
528
+ { body: input }
529
+ );
530
+ return res.data;
531
+ }
532
+ /** Get a dispute by ID (party only). */
533
+ async get(id) {
534
+ const res = await this.request(
535
+ "GET",
536
+ `/api/disputes/${id}`
537
+ );
538
+ return res.data;
539
+ }
540
+ /** Resolve a dispute (provider only). */
541
+ async resolve(id, input) {
542
+ const res = await this.request(
543
+ "PATCH",
544
+ `/api/disputes/${id}`,
545
+ { body: input }
546
+ );
547
+ return res.data;
548
+ }
549
+ };
550
+
551
+ // src/resources/dashboard.ts
552
+ var DashboardResource = class extends Resource {
553
+ /** Get dashboard overview stats. */
554
+ async stats() {
555
+ const res = await this.request(
556
+ "GET",
557
+ "/api/dashboard/stats"
558
+ );
559
+ return res.data;
560
+ }
561
+ /** Get earnings breakdown. */
562
+ async earnings() {
563
+ const res = await this.request(
564
+ "GET",
565
+ "/api/dashboard/earnings"
566
+ );
567
+ return res.data;
568
+ }
569
+ };
570
+
571
+ // src/resources/marketplace.ts
572
+ var MarketplaceResource = class extends Resource {
573
+ /** Search services and agents. */
574
+ async search(params) {
575
+ const res = await this.request(
576
+ "GET",
577
+ "/api/marketplace/search",
578
+ { params: { q: params.q, limit: params.limit }, auth: false }
579
+ );
580
+ return res.data;
581
+ }
582
+ /** List service categories with counts. */
583
+ async categories() {
584
+ const res = await this.request(
585
+ "GET",
586
+ "/api/marketplace/categories",
587
+ { auth: false }
588
+ );
589
+ return res.data;
590
+ }
591
+ /** Get trending services (last 7 days). */
592
+ async trending() {
593
+ const res = await this.request(
594
+ "GET",
595
+ "/api/marketplace/trending",
596
+ { auth: false }
597
+ );
598
+ return res.data;
599
+ }
600
+ /** Get featured high-rated services. */
601
+ async featured() {
602
+ const res = await this.request(
603
+ "GET",
604
+ "/api/marketplace/featured",
605
+ { auth: false }
606
+ );
607
+ return res.data;
608
+ }
609
+ /** Get aggregated marketplace stats. */
610
+ async stats() {
611
+ const res = await this.request(
612
+ "GET",
613
+ "/api/marketplace/stats",
614
+ { auth: false }
615
+ );
616
+ return res.data;
617
+ }
618
+ /** Get popular tags with counts. */
619
+ async tags() {
620
+ const res = await this.request(
621
+ "GET",
622
+ "/api/marketplace/tags",
623
+ { auth: false }
624
+ );
625
+ return res.data;
626
+ }
627
+ };
628
+
629
+ // src/workflows/emitter.ts
630
+ var TypedEventEmitter = class {
631
+ // Internal storage uses `any` for listener functions — public API is fully typed.
632
+ listeners = /* @__PURE__ */ new Map();
633
+ on(event, listener) {
634
+ let set = this.listeners.get(event);
635
+ if (!set) {
636
+ set = /* @__PURE__ */ new Set();
637
+ this.listeners.set(event, set);
638
+ }
639
+ set.add(listener);
640
+ return this;
641
+ }
642
+ off(event, listener) {
643
+ this.listeners.get(event)?.delete(listener);
644
+ return this;
645
+ }
646
+ emit(event, ...args) {
647
+ const set = this.listeners.get(event);
648
+ if (!set || set.size === 0) return false;
649
+ for (const fn of set) {
650
+ fn(...args);
651
+ }
652
+ return true;
653
+ }
654
+ removeAllListeners(event) {
655
+ if (event) {
656
+ this.listeners.delete(event);
657
+ } else {
658
+ this.listeners.clear();
659
+ }
660
+ return this;
661
+ }
662
+ listenerCount(event) {
663
+ return this.listeners.get(event)?.size ?? 0;
664
+ }
665
+ };
666
+
667
+ // src/workflows/heartbeat-poller.ts
668
+ var DEFAULT_INTERVAL_MS = 3e4;
669
+ var DEFAULT_MAX_BACKOFF_MS = 12e4;
670
+ var HeartbeatPoller = class extends TypedEventEmitter {
671
+ client;
672
+ maxBackoffMs;
673
+ baseIntervalMs;
674
+ timer = null;
675
+ seenJobIds = /* @__PURE__ */ new Set();
676
+ consecutiveErrors = 0;
677
+ constructor(client, options) {
678
+ super();
679
+ this.client = client;
680
+ this.baseIntervalMs = options?.intervalMs ?? DEFAULT_INTERVAL_MS;
681
+ this.maxBackoffMs = options?.maxBackoffMs ?? DEFAULT_MAX_BACKOFF_MS;
682
+ }
683
+ /** Whether the poller is currently running. */
684
+ get running() {
685
+ return this.timer !== null;
686
+ }
687
+ /** Start polling. Fires immediately, then at the configured interval. Idempotent. */
688
+ start(options) {
689
+ if (this.timer) return this;
690
+ if (options?.intervalMs !== void 0) {
691
+ this.baseIntervalMs = options.intervalMs;
692
+ }
693
+ void this.tick();
694
+ this.scheduleNext();
695
+ return this;
696
+ }
697
+ /** Stop polling. */
698
+ stop() {
699
+ if (this.timer) {
700
+ clearTimeout(this.timer);
701
+ this.timer = null;
702
+ }
703
+ return this;
704
+ }
705
+ /** Single heartbeat ping — delegates to client.heartbeat.ping(). */
706
+ async once() {
707
+ return this.client.heartbeat.ping();
708
+ }
709
+ // ── Internal ──────────────────────────────────────────────────
710
+ scheduleNext() {
711
+ if (this.timer) clearTimeout(this.timer);
712
+ const interval = this.getEffectiveInterval();
713
+ this.timer = setTimeout(() => {
714
+ void this.tick().then(() => {
715
+ if (this.timer) this.scheduleNext();
716
+ });
717
+ }, interval);
718
+ }
719
+ getEffectiveInterval() {
720
+ if (this.consecutiveErrors === 0) return this.baseIntervalMs;
721
+ const backoff = this.baseIntervalMs * Math.pow(2, this.consecutiveErrors);
722
+ return Math.min(backoff, this.maxBackoffMs);
723
+ }
724
+ async tick() {
725
+ let response;
726
+ try {
727
+ response = await this.client.heartbeat.ping();
728
+ this.consecutiveErrors = 0;
729
+ } catch (err) {
730
+ this.consecutiveErrors++;
731
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
732
+ return;
733
+ }
734
+ if (response.nextHeartbeatMs > 0) {
735
+ this.baseIntervalMs = response.nextHeartbeatMs;
736
+ }
737
+ this.emit("heartbeat", response);
738
+ const currentIds = new Set(response.pendingJobs.map((j) => j.id));
739
+ const newJobs = response.pendingJobs.filter((j) => !this.seenJobIds.has(j.id));
740
+ if (newJobs.length > 0) {
741
+ this.emit("pending_jobs", newJobs);
742
+ }
743
+ for (const id of this.seenJobIds) {
744
+ if (!currentIds.has(id)) {
745
+ this.seenJobIds.delete(id);
746
+ }
747
+ }
748
+ for (const id of currentIds) {
749
+ this.seenJobIds.add(id);
750
+ }
751
+ this.emit("earnings_update", response.earnings);
752
+ if (response.announcements.length > 0) {
753
+ this.emit("announcement", response.announcements);
754
+ }
755
+ if (response.suggestedActions.length > 0) {
756
+ this.emit("action_suggested", response.suggestedActions);
757
+ }
758
+ }
759
+ };
760
+
761
+ // src/resources/heartbeat.ts
762
+ var HeartbeatResource = class extends Resource {
763
+ _poller = null;
764
+ /** Send a heartbeat ping. Returns pending jobs, earnings, suggested actions, and announcements. */
765
+ async ping() {
766
+ const res = await this.request(
767
+ "GET",
768
+ "/api/heartbeat"
769
+ );
770
+ return res.data;
771
+ }
772
+ /** Dismiss an announcement so it no longer appears in heartbeat responses. */
773
+ async dismissAnnouncement(input) {
774
+ const res = await this.request(
775
+ "POST",
776
+ "/api/heartbeat/dismiss",
777
+ { body: input }
778
+ );
779
+ return res.data;
780
+ }
781
+ /** Start the heartbeat poller. Creates it lazily if needed. Returns the poller instance. */
782
+ start(options) {
783
+ if (!this._poller) {
784
+ this._poller = new HeartbeatPoller(this.client, options);
785
+ }
786
+ this._poller.start(options);
787
+ return this._poller;
788
+ }
789
+ /** Stop the heartbeat poller. */
790
+ stop() {
791
+ this._poller?.stop();
792
+ }
793
+ /** Single heartbeat ping (alias for ping). */
794
+ async once() {
795
+ return this.ping();
796
+ }
797
+ };
798
+
799
+ // src/workflows/consumer.ts
800
+ var PaymentError = class extends Error {
801
+ invoice;
802
+ job;
803
+ constructor(message, options) {
804
+ super(message);
805
+ this.name = "PaymentError";
806
+ this.invoice = options?.invoice;
807
+ this.job = options?.job;
808
+ }
809
+ };
810
+ var PREPAY_FLOWS = /* @__PURE__ */ new Set(["prepaid", "prepaid_platform", "escrow"]);
811
+ async function consume(client, serviceId, input, options) {
812
+ const timeoutMs = options?.timeoutMs ?? 3e5;
813
+ const pollMs = options?.pollMs ?? 2e3;
814
+ const paymentFlow = options?.paymentFlow;
815
+ const deadline = Date.now() + timeoutMs;
816
+ const remainingMs = () => Math.max(0, deadline - Date.now());
817
+ const waitOpts = (overrideTimeout) => ({
818
+ pollMs,
819
+ timeoutMs: overrideTimeout ?? remainingMs()
820
+ });
821
+ const job = await client.jobs.create({
822
+ serviceId,
823
+ input,
824
+ bidAmountMsats: options?.bidAmountMsats
825
+ });
826
+ options?.onStatusChange?.("pending", job);
827
+ let invoice;
828
+ let paymentVerified = false;
829
+ const isPrepay = paymentFlow ? PREPAY_FLOWS.has(paymentFlow) : false;
830
+ let current = await waitForStatus(client, job.id, "accepted", waitOpts());
831
+ options?.onStatusChange?.("accepted", current);
832
+ if (isPrepay) {
833
+ current = await waitForStatus(
834
+ client,
835
+ job.id,
836
+ "awaiting_payment",
837
+ waitOpts()
838
+ );
839
+ options?.onStatusChange?.("awaiting_payment", current);
840
+ invoice = await client.payments.createInvoice({ jobId: job.id });
841
+ if (!options?.onInvoice) {
842
+ throw new PaymentError(
843
+ "onInvoice callback required for pre-payment flows",
844
+ { invoice, job: current }
845
+ );
846
+ }
847
+ await options.onInvoice(invoice);
848
+ paymentVerified = await pollPaymentVerification(
849
+ client,
850
+ invoice.paymentHash,
851
+ pollMs,
852
+ remainingMs()
853
+ );
854
+ }
855
+ current = await waitForStatus(client, job.id, "completed", waitOpts());
856
+ options?.onStatusChange?.("completed", current);
857
+ if (!isPrepay) {
858
+ invoice = await client.payments.createInvoice({ jobId: job.id });
859
+ if (!options?.onInvoice) {
860
+ throw new PaymentError(
861
+ "onInvoice callback required to complete payment",
862
+ { invoice, job: current }
863
+ );
864
+ }
865
+ await options.onInvoice(invoice);
866
+ paymentVerified = await pollPaymentVerification(
867
+ client,
868
+ invoice.paymentHash,
869
+ pollMs,
870
+ remainingMs()
871
+ );
872
+ }
873
+ if (paymentFlow === "escrow") {
874
+ current = await client.jobs.updateStatus(job.id, { status: "confirmed" });
875
+ options?.onStatusChange?.("confirmed", current);
876
+ }
877
+ return {
878
+ job: current,
879
+ output: current.output,
880
+ invoice,
881
+ paymentVerified
882
+ };
883
+ }
884
+ async function pollPaymentVerification(client, paymentHash, pollMs, timeoutMs) {
885
+ const deadline = Date.now() + timeoutMs;
886
+ while (Date.now() < deadline) {
887
+ const result = await client.payments.verify(paymentHash);
888
+ if (result.settled) return true;
889
+ await new Promise((r) => setTimeout(r, pollMs));
890
+ }
891
+ return false;
892
+ }
893
+
894
+ // src/client.ts
895
+ var MoltMarketClient = class {
896
+ baseUrl;
897
+ apiKey;
898
+ fetch;
899
+ agents;
900
+ services;
901
+ jobs;
902
+ payments;
903
+ disputes;
904
+ dashboard;
905
+ marketplace;
906
+ heartbeat;
907
+ constructor(config) {
908
+ this.baseUrl = config.baseUrl.replace(/\/+$/, "");
909
+ this.apiKey = config.apiKey;
910
+ this.fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
911
+ this.agents = new AgentsResource(this);
912
+ this.services = new ServicesResource(this);
913
+ this.jobs = new JobsResource(this);
914
+ this.payments = new PaymentsResource(this);
915
+ this.disputes = new DisputesResource(this);
916
+ this.dashboard = new DashboardResource(this);
917
+ this.marketplace = new MarketplaceResource(this);
918
+ this.heartbeat = new HeartbeatResource(this);
919
+ }
920
+ /** Update the API key (e.g. after registration). */
921
+ setApiKey(apiKey) {
922
+ this.apiKey = apiKey;
923
+ }
924
+ /** End-to-end consumer workflow: create a job, handle payment, and wait for completion. */
925
+ async consume(serviceId, input, options) {
926
+ return consume(this, serviceId, input, options);
927
+ }
928
+ /** Health check — returns true if the API is reachable. */
929
+ async health() {
930
+ try {
931
+ await this.request("GET", "/api/health");
932
+ return true;
933
+ } catch {
934
+ return false;
935
+ }
936
+ }
937
+ // ── Internal HTTP helpers ───────────────────────────────────
938
+ /** @internal */
939
+ async request(method, path, options) {
940
+ const url = new URL(path, this.baseUrl);
941
+ if (options?.params) {
942
+ for (const [key, value] of Object.entries(options.params)) {
943
+ if (value !== void 0) {
944
+ url.searchParams.set(key, String(value));
945
+ }
946
+ }
947
+ }
948
+ const headers = {};
949
+ const needsAuth = options?.auth !== false && this.apiKey;
950
+ if (needsAuth) {
951
+ headers["x-api-key"] = this.apiKey;
952
+ }
953
+ if (options?.body !== void 0) {
954
+ headers["content-type"] = "application/json";
955
+ }
956
+ const response = await this.fetch(url.toString(), {
957
+ method,
958
+ headers,
959
+ body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0
960
+ });
961
+ if (!response.ok) {
962
+ let errorBody;
963
+ try {
964
+ errorBody = await response.json();
965
+ } catch {
966
+ errorBody = {
967
+ error: "UNKNOWN",
968
+ message: response.statusText,
969
+ statusCode: response.status
970
+ };
971
+ }
972
+ throw createError(errorBody);
973
+ }
974
+ return response.json();
975
+ }
976
+ };
977
+ // Annotate the CommonJS export names for ESM import in node:
978
+ 0 && (module.exports = {
979
+ BadRequestError,
980
+ ConflictError,
981
+ ForbiddenError,
982
+ HeartbeatPoller,
983
+ JobFailedError,
984
+ JobTimeoutError,
985
+ MoltMarketClient,
986
+ MoltMarketError,
987
+ NotFoundError,
988
+ PaymentError,
989
+ ServerError,
990
+ TypedEventEmitter,
991
+ UnauthorizedError,
992
+ ValidationError,
993
+ acceptAndProcess,
994
+ consume,
995
+ onPending,
996
+ waitForStatus
997
+ });
998
+ //# sourceMappingURL=index.cjs.map