@vivoa/plugin-sdk 0.6.3 → 0.6.6

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.js CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  ANALYTICS_PLUGIN_TOKEN: () => ANALYTICS_PLUGIN_TOKEN,
24
+ AUTHOR_HANDLE_RE: () => AUTHOR_HANDLE_RE,
24
25
  ActionVariant: () => ActionVariant,
25
26
  AnalyticsFeature: () => AnalyticsFeature,
26
27
  BlockType: () => BlockType,
@@ -30,6 +31,10 @@ __export(index_exports, {
30
31
  FieldType: () => FieldType,
31
32
  FieldVariant: () => FieldVariant,
32
33
  GatewayEnvironment: () => GatewayEnvironment,
34
+ INVOICING_PLUGIN_TOKEN: () => INVOICING_PLUGIN_TOKEN,
35
+ InvoiceDocumentType: () => InvoiceDocumentType,
36
+ InvoiceStatus: () => InvoiceStatus,
37
+ InvoicingFeature: () => InvoicingFeature,
33
38
  LayoutMode: () => LayoutMode,
34
39
  MessageVariant: () => MessageVariant,
35
40
  PAYMENT_PLUGIN_TOKEN: () => PAYMENT_PLUGIN_TOKEN,
@@ -49,6 +54,7 @@ __export(index_exports, {
49
54
  crossValidateSetupLayout: () => crossValidateSetupLayout,
50
55
  definePlugin: () => definePlugin,
51
56
  field: () => field,
57
+ invoicingCapability: () => invoicingCapability,
52
58
  isConfigurablePlugin: () => isConfigurablePlugin,
53
59
  isPermissionGranted: () => isPermissionGranted,
54
60
  isPluginFactory: () => isPluginFactory,
@@ -57,6 +63,7 @@ __export(index_exports, {
57
63
  paymentCapability: () => paymentCapability,
58
64
  shippingCapability: () => shippingCapability,
59
65
  suppliersCapability: () => suppliersCapability,
66
+ validatePluginAuthor: () => validatePluginAuthor,
60
67
  validateSetupLayout: () => validateSetupLayout
61
68
  });
62
69
  module.exports = __toCommonJS(index_exports);
@@ -66,12 +73,14 @@ var PAYMENT_PLUGIN_TOKEN = "PAYMENT_PLUGINS";
66
73
  var SHIPPING_PLUGIN_TOKEN = "SHIPPING_PLUGINS";
67
74
  var SUPPLIERS_PLUGIN_TOKEN = "SUPPLIERS_PLUGINS";
68
75
  var ANALYTICS_PLUGIN_TOKEN = "ANALYTICS_PLUGINS";
76
+ var INVOICING_PLUGIN_TOKEN = "INVOICING_PLUGINS";
69
77
 
70
78
  // src/plugin.ts
71
79
  var PluginType = /* @__PURE__ */ ((PluginType2) => {
72
80
  PluginType2["Payment"] = "payment";
73
81
  PluginType2["Shipping"] = "shipping";
74
82
  PluginType2["Analytics"] = "analytics";
83
+ PluginType2["Invoicing"] = "invoicing";
75
84
  PluginType2["Generic"] = "generic";
76
85
  return PluginType2;
77
86
  })(PluginType || {});
@@ -211,6 +220,56 @@ var AnalyticsFeature = /* @__PURE__ */ ((AnalyticsFeature2) => {
211
220
  return AnalyticsFeature2;
212
221
  })(AnalyticsFeature || {});
213
222
 
223
+ // src/invoicing.ts
224
+ var InvoicingFeature = /* @__PURE__ */ ((InvoicingFeature2) => {
225
+ InvoicingFeature2["Issue"] = "issue";
226
+ InvoicingFeature2["Void"] = "void";
227
+ InvoicingFeature2["StatusQuery"] = "status-query";
228
+ InvoicingFeature2["Pdf"] = "pdf";
229
+ return InvoicingFeature2;
230
+ })(InvoicingFeature || {});
231
+ var InvoiceDocumentType = /* @__PURE__ */ ((InvoiceDocumentType2) => {
232
+ InvoiceDocumentType2["Invoice"] = "invoice";
233
+ InvoiceDocumentType2["CreditInvoice"] = "credit-invoice";
234
+ InvoiceDocumentType2["CreditNote"] = "credit-note";
235
+ InvoiceDocumentType2["DebitNote"] = "debit-note";
236
+ InvoiceDocumentType2["ExcludedSubject"] = "excluded-subject";
237
+ InvoiceDocumentType2["Export"] = "export";
238
+ return InvoiceDocumentType2;
239
+ })(InvoiceDocumentType || {});
240
+ var InvoiceStatus = /* @__PURE__ */ ((InvoiceStatus2) => {
241
+ InvoiceStatus2["Pending"] = "pending";
242
+ InvoiceStatus2["Accepted"] = "accepted";
243
+ InvoiceStatus2["Rejected"] = "rejected";
244
+ InvoiceStatus2["Voided"] = "voided";
245
+ InvoiceStatus2["Unknown"] = "unknown";
246
+ return InvoiceStatus2;
247
+ })(InvoiceStatus || {});
248
+
249
+ // src/author.ts
250
+ var AUTHOR_HANDLE_RE = /^[a-z0-9][a-z0-9-]*$/;
251
+ function validatePluginAuthor(value) {
252
+ const errors = [];
253
+ if (typeof value !== "object" || value === null) {
254
+ return { ok: false, errors: ["author is required and must be an object"] };
255
+ }
256
+ const a = value;
257
+ if (typeof a.handle !== "string" || a.handle.length === 0) {
258
+ errors.push("author.handle is required");
259
+ } else if (!AUTHOR_HANDLE_RE.test(a.handle)) {
260
+ errors.push(
261
+ `author.handle "${a.handle}" must be a lowercase slug (a-z, 0-9, dash; start alphanumeric)`
262
+ );
263
+ }
264
+ if (typeof a.displayName !== "string" || a.displayName.trim().length === 0) {
265
+ errors.push("author.displayName is required");
266
+ }
267
+ if (a.url !== void 0 && typeof a.url !== "string") {
268
+ errors.push("author.url must be a string when present");
269
+ }
270
+ return { ok: errors.length === 0, errors };
271
+ }
272
+
214
273
  // src/setup-layout.ts
215
274
  var import_superstruct2 = require("superstruct");
216
275
  var FieldVariant = /* @__PURE__ */ ((FieldVariant2) => {
@@ -482,7 +541,8 @@ var CapabilityKind = {
482
541
  Payment: "payment",
483
542
  Shipping: "shipping",
484
543
  Suppliers: "suppliers",
485
- Analytics: "analytics"
544
+ Analytics: "analytics",
545
+ Invoicing: "invoicing"
486
546
  };
487
547
  function capability(kind, impl) {
488
548
  return { kind, impl };
@@ -499,6 +559,9 @@ function suppliersCapability(impl) {
499
559
  function analyticsCapability(impl) {
500
560
  return { kind: CapabilityKind.Analytics, impl };
501
561
  }
562
+ function invoicingCapability(impl) {
563
+ return { kind: CapabilityKind.Invoicing, impl };
564
+ }
502
565
  var PLUGIN_FACTORY_BRAND = /* @__PURE__ */ Symbol.for("vivoa.plugin.factory");
503
566
  function definePlugin(factory) {
504
567
  Object.defineProperty(factory, PLUGIN_FACTORY_BRAND, {
@@ -544,7 +607,7 @@ function checkPermissions(required, grantedRaw) {
544
607
  }
545
608
 
546
609
  // src/version.ts
547
- var SDK_VERSION = "0.6.3";
610
+ var SDK_VERSION = "0.6.6";
548
611
  function isSdkCompatible(pluginSdkVersion, hostSdkVersion) {
549
612
  const parse = (v) => {
550
613
  const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
@@ -562,6 +625,7 @@ function isSdkCompatible(pluginSdkVersion, hostSdkVersion) {
562
625
  // Annotate the CommonJS export names for ESM import in node:
563
626
  0 && (module.exports = {
564
627
  ANALYTICS_PLUGIN_TOKEN,
628
+ AUTHOR_HANDLE_RE,
565
629
  ActionVariant,
566
630
  AnalyticsFeature,
567
631
  BlockType,
@@ -571,6 +635,10 @@ function isSdkCompatible(pluginSdkVersion, hostSdkVersion) {
571
635
  FieldType,
572
636
  FieldVariant,
573
637
  GatewayEnvironment,
638
+ INVOICING_PLUGIN_TOKEN,
639
+ InvoiceDocumentType,
640
+ InvoiceStatus,
641
+ InvoicingFeature,
574
642
  LayoutMode,
575
643
  MessageVariant,
576
644
  PAYMENT_PLUGIN_TOKEN,
@@ -590,6 +658,7 @@ function isSdkCompatible(pluginSdkVersion, hostSdkVersion) {
590
658
  crossValidateSetupLayout,
591
659
  definePlugin,
592
660
  field,
661
+ invoicingCapability,
593
662
  isConfigurablePlugin,
594
663
  isPermissionGranted,
595
664
  isPluginFactory,
@@ -598,5 +667,6 @@ function isSdkCompatible(pluginSdkVersion, hostSdkVersion) {
598
667
  paymentCapability,
599
668
  shippingCapability,
600
669
  suppliersCapability,
670
+ validatePluginAuthor,
601
671
  validateSetupLayout
602
672
  });
package/dist/index.mjs CHANGED
@@ -1,14 +1,22 @@
1
+ import {
2
+ checkPermissions,
3
+ isPermissionGranted,
4
+ parsePermission
5
+ } from "./chunk-DGJZW46B.mjs";
6
+
1
7
  // src/tokens.ts
2
8
  var PAYMENT_PLUGIN_TOKEN = "PAYMENT_PLUGINS";
3
9
  var SHIPPING_PLUGIN_TOKEN = "SHIPPING_PLUGINS";
4
10
  var SUPPLIERS_PLUGIN_TOKEN = "SUPPLIERS_PLUGINS";
5
11
  var ANALYTICS_PLUGIN_TOKEN = "ANALYTICS_PLUGINS";
12
+ var INVOICING_PLUGIN_TOKEN = "INVOICING_PLUGINS";
6
13
 
7
14
  // src/plugin.ts
8
15
  var PluginType = /* @__PURE__ */ ((PluginType2) => {
9
16
  PluginType2["Payment"] = "payment";
10
17
  PluginType2["Shipping"] = "shipping";
11
18
  PluginType2["Analytics"] = "analytics";
19
+ PluginType2["Invoicing"] = "invoicing";
12
20
  PluginType2["Generic"] = "generic";
13
21
  return PluginType2;
14
22
  })(PluginType || {});
@@ -156,6 +164,56 @@ var AnalyticsFeature = /* @__PURE__ */ ((AnalyticsFeature2) => {
156
164
  return AnalyticsFeature2;
157
165
  })(AnalyticsFeature || {});
158
166
 
167
+ // src/invoicing.ts
168
+ var InvoicingFeature = /* @__PURE__ */ ((InvoicingFeature2) => {
169
+ InvoicingFeature2["Issue"] = "issue";
170
+ InvoicingFeature2["Void"] = "void";
171
+ InvoicingFeature2["StatusQuery"] = "status-query";
172
+ InvoicingFeature2["Pdf"] = "pdf";
173
+ return InvoicingFeature2;
174
+ })(InvoicingFeature || {});
175
+ var InvoiceDocumentType = /* @__PURE__ */ ((InvoiceDocumentType2) => {
176
+ InvoiceDocumentType2["Invoice"] = "invoice";
177
+ InvoiceDocumentType2["CreditInvoice"] = "credit-invoice";
178
+ InvoiceDocumentType2["CreditNote"] = "credit-note";
179
+ InvoiceDocumentType2["DebitNote"] = "debit-note";
180
+ InvoiceDocumentType2["ExcludedSubject"] = "excluded-subject";
181
+ InvoiceDocumentType2["Export"] = "export";
182
+ return InvoiceDocumentType2;
183
+ })(InvoiceDocumentType || {});
184
+ var InvoiceStatus = /* @__PURE__ */ ((InvoiceStatus2) => {
185
+ InvoiceStatus2["Pending"] = "pending";
186
+ InvoiceStatus2["Accepted"] = "accepted";
187
+ InvoiceStatus2["Rejected"] = "rejected";
188
+ InvoiceStatus2["Voided"] = "voided";
189
+ InvoiceStatus2["Unknown"] = "unknown";
190
+ return InvoiceStatus2;
191
+ })(InvoiceStatus || {});
192
+
193
+ // src/author.ts
194
+ var AUTHOR_HANDLE_RE = /^[a-z0-9][a-z0-9-]*$/;
195
+ function validatePluginAuthor(value) {
196
+ const errors = [];
197
+ if (typeof value !== "object" || value === null) {
198
+ return { ok: false, errors: ["author is required and must be an object"] };
199
+ }
200
+ const a = value;
201
+ if (typeof a.handle !== "string" || a.handle.length === 0) {
202
+ errors.push("author.handle is required");
203
+ } else if (!AUTHOR_HANDLE_RE.test(a.handle)) {
204
+ errors.push(
205
+ `author.handle "${a.handle}" must be a lowercase slug (a-z, 0-9, dash; start alphanumeric)`
206
+ );
207
+ }
208
+ if (typeof a.displayName !== "string" || a.displayName.trim().length === 0) {
209
+ errors.push("author.displayName is required");
210
+ }
211
+ if (a.url !== void 0 && typeof a.url !== "string") {
212
+ errors.push("author.url must be a string when present");
213
+ }
214
+ return { ok: errors.length === 0, errors };
215
+ }
216
+
159
217
  // src/setup-layout.ts
160
218
  import {
161
219
  array,
@@ -440,7 +498,8 @@ var CapabilityKind = {
440
498
  Payment: "payment",
441
499
  Shipping: "shipping",
442
500
  Suppliers: "suppliers",
443
- Analytics: "analytics"
501
+ Analytics: "analytics",
502
+ Invoicing: "invoicing"
444
503
  };
445
504
  function capability(kind, impl) {
446
505
  return { kind, impl };
@@ -457,6 +516,9 @@ function suppliersCapability(impl) {
457
516
  function analyticsCapability(impl) {
458
517
  return { kind: CapabilityKind.Analytics, impl };
459
518
  }
519
+ function invoicingCapability(impl) {
520
+ return { kind: CapabilityKind.Invoicing, impl };
521
+ }
460
522
  var PLUGIN_FACTORY_BRAND = /* @__PURE__ */ Symbol.for("vivoa.plugin.factory");
461
523
  function definePlugin(factory) {
462
524
  Object.defineProperty(factory, PLUGIN_FACTORY_BRAND, {
@@ -469,40 +531,8 @@ function isPluginFactory(value) {
469
531
  return typeof value === "function" && value[PLUGIN_FACTORY_BRAND] === true;
470
532
  }
471
533
 
472
- // src/permissions.ts
473
- var PERMISSION_RE = /^([a-z0-9]+)\.([a-z0-9]+)(?::(.+))?$/i;
474
- function parsePermission(raw) {
475
- const m = PERMISSION_RE.exec(raw);
476
- if (!m) return null;
477
- const [, domain, action, resource] = m;
478
- return resource === void 0 ? { domain, action } : { domain, action, resource };
479
- }
480
- function isPermissionGranted(required, granted) {
481
- return granted.some((g) => {
482
- if (g.domain !== required.domain || g.action !== required.action) {
483
- return false;
484
- }
485
- if (required.resource === void 0) return true;
486
- return g.resource === "*" || g.resource === required.resource;
487
- });
488
- }
489
- function checkPermissions(required, grantedRaw) {
490
- const grantedParsed = grantedRaw.map(parsePermission).filter((p) => p !== null);
491
- const granted = [];
492
- const missing = [];
493
- for (const raw of required) {
494
- const parsed = parsePermission(raw);
495
- if (parsed && isPermissionGranted(parsed, grantedParsed)) {
496
- granted.push(raw);
497
- } else {
498
- missing.push(raw);
499
- }
500
- }
501
- return { ok: missing.length === 0, granted, missing };
502
- }
503
-
504
534
  // src/version.ts
505
- var SDK_VERSION = "0.6.3";
535
+ var SDK_VERSION = "0.6.6";
506
536
  function isSdkCompatible(pluginSdkVersion, hostSdkVersion) {
507
537
  const parse = (v) => {
508
538
  const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
@@ -519,6 +549,7 @@ function isSdkCompatible(pluginSdkVersion, hostSdkVersion) {
519
549
  }
520
550
  export {
521
551
  ANALYTICS_PLUGIN_TOKEN,
552
+ AUTHOR_HANDLE_RE,
522
553
  ActionVariant,
523
554
  AnalyticsFeature,
524
555
  BlockType,
@@ -528,6 +559,10 @@ export {
528
559
  FieldType,
529
560
  FieldVariant,
530
561
  GatewayEnvironment,
562
+ INVOICING_PLUGIN_TOKEN,
563
+ InvoiceDocumentType,
564
+ InvoiceStatus,
565
+ InvoicingFeature,
531
566
  LayoutMode,
532
567
  MessageVariant,
533
568
  PAYMENT_PLUGIN_TOKEN,
@@ -547,6 +582,7 @@ export {
547
582
  crossValidateSetupLayout,
548
583
  definePlugin,
549
584
  field,
585
+ invoicingCapability,
550
586
  isConfigurablePlugin,
551
587
  isPermissionGranted,
552
588
  isPluginFactory,
@@ -555,5 +591,6 @@ export {
555
591
  paymentCapability,
556
592
  shippingCapability,
557
593
  suppliersCapability,
594
+ validatePluginAuthor,
558
595
  validateSetupLayout
559
596
  };
@@ -0,0 +1,54 @@
1
+ import { X as HostApi, Z as HttpRequest, N as DomainEventName, u as Capability, M as DomainEventHandler, _ as HttpResponse, K as DispatchContext } from '../host-api-C1VylW5l.mjs';
2
+
3
+ /** Every recorded interaction, keyed by `"port.method"`. */
4
+ interface MockHostCalls {
5
+ 'http.request': HttpRequest[];
6
+ 'secrets.get': string[];
7
+ 'storage.getUrl': string[];
8
+ 'storage.put': Array<{
9
+ key: string;
10
+ body: Uint8Array | string;
11
+ }>;
12
+ 'sequence.next': string[];
13
+ 'host.on': DomainEventName[];
14
+ }
15
+ interface MockHostOptions {
16
+ /**
17
+ * Permissions the mock "grants". When set, the mock enforces them exactly as
18
+ * the real host is specified to: an un-granted `http.outbound:<host>` or
19
+ * `secrets.read:<key>` THROWS. Omit to grant everything (pure recorder mode).
20
+ */
21
+ grantedPermissions?: string[];
22
+ /** Canned secret values returned by `secrets.get`. */
23
+ secrets?: Record<string, string>;
24
+ /**
25
+ * Canned HTTP responses. Resolved against the request URL — exact match
26
+ * first, then the first entry whose key is a substring of the URL. Defaults
27
+ * to `{ status: 200, headers: {}, body: {} }`.
28
+ */
29
+ httpResponses?: Record<string, HttpResponse>;
30
+ /** Seed values for named sequence scopes (monotonic from here). */
31
+ sequences?: Record<string, number>;
32
+ }
33
+ interface MockHostApi extends HostApi {
34
+ /** Every port call recorded in order. */
35
+ readonly calls: MockHostCalls;
36
+ /** Capabilities the plugin registered via `registerCapability`. */
37
+ readonly registered: Capability[];
38
+ /** Event handlers the plugin subscribed via `host.on`. */
39
+ readonly handlers: Map<DomainEventName, DomainEventHandler[]>;
40
+ /** Drives a subscribed handler as if the host emitted the event. */
41
+ emit(event: DomainEventName, payload: unknown): Promise<void>;
42
+ }
43
+ /**
44
+ * Builds a {@link MockHostApi}. Pass `grantedPermissions` to assert your plugin
45
+ * declared what it actually uses; leave it out to just record calls.
46
+ */
47
+ declare function createMockHostApi(opts?: MockHostOptions): MockHostApi;
48
+ /**
49
+ * Minimal {@link DispatchContext} for dispatch-method tests. Pass the merged
50
+ * config your plugin expects; the country fields mirror the host's resolution.
51
+ */
52
+ declare function createDispatchContext(config?: Record<string, unknown>, overrides?: Partial<DispatchContext>): DispatchContext;
53
+
54
+ export { type MockHostApi, type MockHostCalls, type MockHostOptions, createDispatchContext, createMockHostApi };
@@ -0,0 +1,54 @@
1
+ import { X as HostApi, Z as HttpRequest, N as DomainEventName, u as Capability, M as DomainEventHandler, _ as HttpResponse, K as DispatchContext } from '../host-api-C1VylW5l.js';
2
+
3
+ /** Every recorded interaction, keyed by `"port.method"`. */
4
+ interface MockHostCalls {
5
+ 'http.request': HttpRequest[];
6
+ 'secrets.get': string[];
7
+ 'storage.getUrl': string[];
8
+ 'storage.put': Array<{
9
+ key: string;
10
+ body: Uint8Array | string;
11
+ }>;
12
+ 'sequence.next': string[];
13
+ 'host.on': DomainEventName[];
14
+ }
15
+ interface MockHostOptions {
16
+ /**
17
+ * Permissions the mock "grants". When set, the mock enforces them exactly as
18
+ * the real host is specified to: an un-granted `http.outbound:<host>` or
19
+ * `secrets.read:<key>` THROWS. Omit to grant everything (pure recorder mode).
20
+ */
21
+ grantedPermissions?: string[];
22
+ /** Canned secret values returned by `secrets.get`. */
23
+ secrets?: Record<string, string>;
24
+ /**
25
+ * Canned HTTP responses. Resolved against the request URL — exact match
26
+ * first, then the first entry whose key is a substring of the URL. Defaults
27
+ * to `{ status: 200, headers: {}, body: {} }`.
28
+ */
29
+ httpResponses?: Record<string, HttpResponse>;
30
+ /** Seed values for named sequence scopes (monotonic from here). */
31
+ sequences?: Record<string, number>;
32
+ }
33
+ interface MockHostApi extends HostApi {
34
+ /** Every port call recorded in order. */
35
+ readonly calls: MockHostCalls;
36
+ /** Capabilities the plugin registered via `registerCapability`. */
37
+ readonly registered: Capability[];
38
+ /** Event handlers the plugin subscribed via `host.on`. */
39
+ readonly handlers: Map<DomainEventName, DomainEventHandler[]>;
40
+ /** Drives a subscribed handler as if the host emitted the event. */
41
+ emit(event: DomainEventName, payload: unknown): Promise<void>;
42
+ }
43
+ /**
44
+ * Builds a {@link MockHostApi}. Pass `grantedPermissions` to assert your plugin
45
+ * declared what it actually uses; leave it out to just record calls.
46
+ */
47
+ declare function createMockHostApi(opts?: MockHostOptions): MockHostApi;
48
+ /**
49
+ * Minimal {@link DispatchContext} for dispatch-method tests. Pass the merged
50
+ * config your plugin expects; the country fields mirror the host's resolution.
51
+ */
52
+ declare function createDispatchContext(config?: Record<string, unknown>, overrides?: Partial<DispatchContext>): DispatchContext;
53
+
54
+ export { type MockHostApi, type MockHostCalls, type MockHostOptions, createDispatchContext, createMockHostApi };
@@ -0,0 +1,169 @@
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/testing/index.ts
21
+ var testing_exports = {};
22
+ __export(testing_exports, {
23
+ createDispatchContext: () => createDispatchContext,
24
+ createMockHostApi: () => createMockHostApi
25
+ });
26
+ module.exports = __toCommonJS(testing_exports);
27
+
28
+ // src/permissions.ts
29
+ var PERMISSION_RE = /^([a-z0-9]+)\.([a-z0-9]+)(?::(.+))?$/i;
30
+ function parsePermission(raw) {
31
+ const m = PERMISSION_RE.exec(raw);
32
+ if (!m) return null;
33
+ const [, domain, action, resource] = m;
34
+ return resource === void 0 ? { domain, action } : { domain, action, resource };
35
+ }
36
+ function isPermissionGranted(required, granted) {
37
+ return granted.some((g) => {
38
+ if (g.domain !== required.domain || g.action !== required.action) {
39
+ return false;
40
+ }
41
+ if (required.resource === void 0) return true;
42
+ return g.resource === "*" || g.resource === required.resource;
43
+ });
44
+ }
45
+ function checkPermissions(required, grantedRaw) {
46
+ const grantedParsed = grantedRaw.map(parsePermission).filter((p) => p !== null);
47
+ const granted = [];
48
+ const missing = [];
49
+ for (const raw of required) {
50
+ const parsed = parsePermission(raw);
51
+ if (parsed && isPermissionGranted(parsed, grantedParsed)) {
52
+ granted.push(raw);
53
+ } else {
54
+ missing.push(raw);
55
+ }
56
+ }
57
+ return { ok: missing.length === 0, granted, missing };
58
+ }
59
+
60
+ // src/testing/mock-host.ts
61
+ var DEFAULT_RESPONSE = { status: 200, headers: {}, body: {} };
62
+ function createMockHostApi(opts = {}) {
63
+ const calls = {
64
+ "http.request": [],
65
+ "secrets.get": [],
66
+ "storage.getUrl": [],
67
+ "storage.put": [],
68
+ "sequence.next": [],
69
+ "host.on": []
70
+ };
71
+ const registered = [];
72
+ const handlers = /* @__PURE__ */ new Map();
73
+ const seqState = new Map(
74
+ Object.entries(opts.sequences ?? {})
75
+ );
76
+ const enforce = (required) => {
77
+ if (!opts.grantedPermissions) return;
78
+ const check = checkPermissions([required], opts.grantedPermissions);
79
+ if (!check.ok) {
80
+ throw new Error(
81
+ `MockHostApi: permission denied \u2014 plugin needs "${required}" but it is not in grantedPermissions. Declare it in your manifest.`
82
+ );
83
+ }
84
+ };
85
+ const noop = () => {
86
+ };
87
+ const logger = {
88
+ debug: noop,
89
+ info: noop,
90
+ warn: noop,
91
+ error: noop
92
+ };
93
+ const http = {
94
+ // async so a denied-permission throw surfaces as a rejected promise, not a
95
+ // synchronous throw — matches the real host's async port contract.
96
+ request: async (req) => {
97
+ const hostname = safeHostname(req.url);
98
+ enforce(`http.outbound:${hostname}`);
99
+ calls["http.request"].push(req);
100
+ return opts.httpResponses?.[req.url] ?? Object.entries(opts.httpResponses ?? {}).find(
101
+ ([k]) => req.url.includes(k)
102
+ )?.[1] ?? DEFAULT_RESPONSE;
103
+ }
104
+ };
105
+ const secrets = {
106
+ get: async (key) => {
107
+ enforce(`secrets.read:${key}`);
108
+ calls["secrets.get"].push(key);
109
+ return opts.secrets?.[key];
110
+ }
111
+ };
112
+ const storage = {
113
+ getUrl: (key) => {
114
+ calls["storage.getUrl"].push(key);
115
+ return Promise.resolve(`mock://storage/${key}`);
116
+ },
117
+ put: (key, body) => {
118
+ calls["storage.put"].push({ key, body });
119
+ return Promise.resolve();
120
+ }
121
+ };
122
+ const sequence = {
123
+ next: (scope) => {
124
+ calls["sequence.next"].push(scope);
125
+ const current = seqState.get(scope) ?? 0;
126
+ const next = current + 1;
127
+ seqState.set(scope, next);
128
+ return Promise.resolve(next);
129
+ }
130
+ };
131
+ const host = {
132
+ logger,
133
+ http,
134
+ secrets,
135
+ storage,
136
+ sequence,
137
+ calls,
138
+ registered,
139
+ handlers,
140
+ on: (event, handler) => {
141
+ calls["host.on"].push(event);
142
+ const list = handlers.get(event) ?? [];
143
+ list.push(handler);
144
+ handlers.set(event, list);
145
+ },
146
+ registerCapability: (cap) => {
147
+ registered.push(cap);
148
+ },
149
+ emit: async (event, payload) => {
150
+ for (const h of handlers.get(event) ?? []) await h(payload);
151
+ }
152
+ };
153
+ return host;
154
+ }
155
+ function safeHostname(url) {
156
+ try {
157
+ return new URL(url).hostname;
158
+ } catch {
159
+ return url;
160
+ }
161
+ }
162
+ function createDispatchContext(config = {}, overrides = {}) {
163
+ return { config, ...overrides };
164
+ }
165
+ // Annotate the CommonJS export names for ESM import in node:
166
+ 0 && (module.exports = {
167
+ createDispatchContext,
168
+ createMockHostApi
169
+ });