@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/README.md +178 -18
- package/dist/chunk-DGJZW46B.mjs +37 -0
- package/dist/host-api-C1VylW5l.d.mts +1416 -0
- package/dist/host-api-C1VylW5l.d.ts +1416 -0
- package/dist/index.d.mts +6 -1164
- package/dist/index.d.ts +6 -1164
- package/dist/index.js +72 -2
- package/dist/index.mjs +71 -34
- package/dist/testing/index.d.mts +54 -0
- package/dist/testing/index.d.ts +54 -0
- package/dist/testing/index.js +169 -0
- package/dist/testing/index.mjs +113 -0
- package/package.json +8 -3
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.
|
|
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.
|
|
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
|
+
});
|