@vivoa/plugin-sdk 0.5.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.mjs ADDED
@@ -0,0 +1,526 @@
1
+ // src/tokens.ts
2
+ var PAYMENT_PLUGIN_TOKEN = "PAYMENT_PLUGINS";
3
+ var SHIPPING_PLUGIN_TOKEN = "SHIPPING_PLUGINS";
4
+
5
+ // src/plugin.ts
6
+ var PluginType = /* @__PURE__ */ ((PluginType2) => {
7
+ PluginType2["Payment"] = "payment";
8
+ PluginType2["Shipping"] = "shipping";
9
+ PluginType2["Analytics"] = "analytics";
10
+ PluginType2["Generic"] = "generic";
11
+ return PluginType2;
12
+ })(PluginType || {});
13
+ var MessageVariant = /* @__PURE__ */ ((MessageVariant2) => {
14
+ MessageVariant2["Info"] = "info";
15
+ MessageVariant2["Warning"] = "warning";
16
+ MessageVariant2["Success"] = "success";
17
+ MessageVariant2["Danger"] = "danger";
18
+ return MessageVariant2;
19
+ })(MessageVariant || {});
20
+ function isConfigurablePlugin(plugin) {
21
+ return typeof plugin === "object" && plugin !== null && "loadConfig" in plugin && typeof plugin.loadConfig === "function";
22
+ }
23
+
24
+ // src/config.ts
25
+ var FieldType = /* @__PURE__ */ ((FieldType2) => {
26
+ FieldType2["String"] = "string";
27
+ FieldType2["Password"] = "password";
28
+ FieldType2["Url"] = "url";
29
+ FieldType2["Number"] = "number";
30
+ FieldType2["Boolean"] = "boolean";
31
+ FieldType2["Select"] = "select";
32
+ return FieldType2;
33
+ })(FieldType || {});
34
+ var ConfigScope = /* @__PURE__ */ ((ConfigScope2) => {
35
+ ConfigScope2["Global"] = "global";
36
+ ConfigScope2["Country"] = "country";
37
+ return ConfigScope2;
38
+ })(ConfigScope || {});
39
+
40
+ // src/config-schema.ts
41
+ import {
42
+ string,
43
+ number,
44
+ boolean,
45
+ optional,
46
+ enums,
47
+ type as structType,
48
+ validate
49
+ } from "superstruct";
50
+ function field(def) {
51
+ return { required: true, ...def };
52
+ }
53
+ function normalizeDependsOn(dep) {
54
+ if (!dep) return void 0;
55
+ return {
56
+ field: dep.field,
57
+ value: Array.isArray(dep.value) ? dep.value : [dep.value]
58
+ };
59
+ }
60
+ function buildStructForField(def) {
61
+ let base;
62
+ if (def.options) {
63
+ base = enums(def.options.map((o) => o.value));
64
+ } else if (def.type === "boolean" /* Boolean */) {
65
+ base = boolean();
66
+ } else if (def.type === "number" /* Number */) {
67
+ base = number();
68
+ } else {
69
+ base = string();
70
+ }
71
+ return def.required ? base : optional(base);
72
+ }
73
+ function createPluginConfig(fields) {
74
+ const shape = {};
75
+ const configFields = [];
76
+ for (const [key, def] of Object.entries(fields)) {
77
+ shape[key] = buildStructForField(def);
78
+ configFields.push({
79
+ key,
80
+ label: def.label,
81
+ type: def.type,
82
+ required: def.required ?? true,
83
+ scopedTo: def.scopedTo,
84
+ dependsOn: normalizeDependsOn(def.dependsOn),
85
+ placeholder: def.placeholder,
86
+ description: def.description,
87
+ options: def.options,
88
+ internal: def.internal,
89
+ group: def.group
90
+ });
91
+ }
92
+ const schema = structType(shape);
93
+ return {
94
+ configFields,
95
+ validate: (data) => {
96
+ const [error, result] = validate(data, schema);
97
+ if (error) {
98
+ const messages = error.failures().map((f) => {
99
+ const path = f.path.join(".");
100
+ return path ? `${path}: ${f.message}` : f.message;
101
+ });
102
+ return { success: false, errors: messages };
103
+ }
104
+ return { success: true, data: result };
105
+ }
106
+ };
107
+ }
108
+
109
+ // src/payment.ts
110
+ var TransactionStatus = /* @__PURE__ */ ((TransactionStatus2) => {
111
+ TransactionStatus2["Pending"] = "Pending";
112
+ TransactionStatus2["PendingRedirect"] = "Pending_Redirect";
113
+ TransactionStatus2["Approved"] = "Approved";
114
+ TransactionStatus2["Declined"] = "Declined";
115
+ TransactionStatus2["Failed"] = "Failed";
116
+ TransactionStatus2["Error"] = "Error";
117
+ return TransactionStatus2;
118
+ })(TransactionStatus || {});
119
+ var GatewayEnvironment = /* @__PURE__ */ ((GatewayEnvironment2) => {
120
+ GatewayEnvironment2["Sandbox"] = "sandbox";
121
+ GatewayEnvironment2["Production"] = "production";
122
+ return GatewayEnvironment2;
123
+ })(GatewayEnvironment || {});
124
+
125
+ // src/shipping.ts
126
+ var ShippingFeature = /* @__PURE__ */ ((ShippingFeature2) => {
127
+ ShippingFeature2["Tracking"] = "tracking";
128
+ ShippingFeature2["Cod"] = "cod";
129
+ ShippingFeature2["Quotes"] = "quotes";
130
+ ShippingFeature2["Labels"] = "labels";
131
+ return ShippingFeature2;
132
+ })(ShippingFeature || {});
133
+ var CodFeeType = /* @__PURE__ */ ((CodFeeType2) => {
134
+ CodFeeType2["Percentage"] = "percentage";
135
+ CodFeeType2["Fixed"] = "fixed";
136
+ return CodFeeType2;
137
+ })(CodFeeType || {});
138
+
139
+ // src/setup-layout.ts
140
+ import {
141
+ array,
142
+ boolean as boolean2,
143
+ enums as enums2,
144
+ lazy,
145
+ literal,
146
+ number as number2,
147
+ object,
148
+ optional as optional2,
149
+ string as string2,
150
+ union,
151
+ unknown,
152
+ validate as validate2
153
+ } from "superstruct";
154
+ var FieldVariant = /* @__PURE__ */ ((FieldVariant2) => {
155
+ FieldVariant2["Text"] = "text";
156
+ FieldVariant2["Secret"] = "secret";
157
+ FieldVariant2["Url"] = "url";
158
+ FieldVariant2["Number"] = "number";
159
+ FieldVariant2["Select"] = "select";
160
+ FieldVariant2["Textarea"] = "textarea";
161
+ FieldVariant2["Segmented"] = "segmented";
162
+ return FieldVariant2;
163
+ })(FieldVariant || {});
164
+ var ActionVariant = /* @__PURE__ */ ((ActionVariant2) => {
165
+ ActionVariant2["Primary"] = "primary";
166
+ ActionVariant2["Secondary"] = "secondary";
167
+ ActionVariant2["Danger"] = "danger";
168
+ return ActionVariant2;
169
+ })(ActionVariant || {});
170
+ var RepeaterSource = /* @__PURE__ */ ((RepeaterSource2) => {
171
+ RepeaterSource2["SupportedCountries"] = "supportedCountries";
172
+ return RepeaterSource2;
173
+ })(RepeaterSource || {});
174
+ var LayoutMode = /* @__PURE__ */ ((LayoutMode2) => {
175
+ LayoutMode2["Wizard"] = "wizard";
176
+ LayoutMode2["Stacked"] = "stacked";
177
+ LayoutMode2["Tabs"] = "tabs";
178
+ LayoutMode2["Sections"] = "sections";
179
+ return LayoutMode2;
180
+ })(LayoutMode || {});
181
+ var BlockType = /* @__PURE__ */ ((BlockType2) => {
182
+ BlockType2["Field"] = "field";
183
+ BlockType2["Group"] = "group";
184
+ BlockType2["Toggle"] = "toggle";
185
+ BlockType2["Action"] = "action";
186
+ BlockType2["Callout"] = "callout";
187
+ BlockType2["Markdown"] = "markdown";
188
+ BlockType2["Divider"] = "divider";
189
+ BlockType2["Conditional"] = "conditional";
190
+ BlockType2["Repeater"] = "repeater";
191
+ BlockType2["Card"] = "card";
192
+ BlockType2["Banner"] = "banner";
193
+ BlockType2["CopyField"] = "copyField";
194
+ BlockType2["InlineRow"] = "inlineRow";
195
+ BlockType2["Tabs"] = "tabs";
196
+ BlockType2["Accordion"] = "accordion";
197
+ BlockType2["KeyValue"] = "keyValue";
198
+ return BlockType2;
199
+ })(BlockType || {});
200
+ var StepStatusFrom = /* @__PURE__ */ ((StepStatusFrom2) => {
201
+ StepStatusFrom2["Fields"] = "fields";
202
+ StepStatusFrom2["Action"] = "action";
203
+ return StepStatusFrom2;
204
+ })(StepStatusFrom || {});
205
+ var sFieldVariant = enums2(Object.values(FieldVariant));
206
+ var sActionVariant = enums2(Object.values(ActionVariant));
207
+ var sRepeaterSource = enums2(Object.values(RepeaterSource));
208
+ var sMessageVariant = enums2(Object.values(MessageVariant));
209
+ var sSpan = optional2(number2());
210
+ var sFieldBlock = object({
211
+ type: literal("field" /* Field */),
212
+ key: string2(),
213
+ variant: optional2(sFieldVariant),
214
+ labelOverride: optional2(string2()),
215
+ optionsFrom: optional2(string2()),
216
+ span: sSpan
217
+ });
218
+ var sGroupBlock = object({
219
+ type: literal("group" /* Group */),
220
+ title: string2(),
221
+ description: optional2(string2()),
222
+ fields: array(string2()),
223
+ collapsible: optional2(boolean2()),
224
+ defaultOpen: optional2(boolean2()),
225
+ span: sSpan
226
+ });
227
+ var sToggleBlock = object({
228
+ type: literal("toggle" /* Toggle */),
229
+ key: string2(),
230
+ label: string2(),
231
+ description: optional2(string2()),
232
+ span: sSpan
233
+ });
234
+ var sActionBlock = object({
235
+ type: literal("action" /* Action */),
236
+ id: string2(),
237
+ label: string2(),
238
+ variant: optional2(sActionVariant),
239
+ resultSlot: optional2(string2()),
240
+ span: sSpan
241
+ });
242
+ var sCalloutBlock = object({
243
+ type: literal("callout" /* Callout */),
244
+ variant: sMessageVariant,
245
+ text: string2(),
246
+ slot: optional2(string2()),
247
+ span: sSpan
248
+ });
249
+ var sMarkdownBlock = object({
250
+ type: literal("markdown" /* Markdown */),
251
+ content: string2(),
252
+ span: sSpan
253
+ });
254
+ var sDividerBlock = object({ type: literal("divider" /* Divider */), span: sSpan });
255
+ var sConditionalBlock = object({
256
+ type: literal("conditional" /* Conditional */),
257
+ when: object({ key: string2(), equals: unknown() }),
258
+ blocks: array(lazy(() => sBlock)),
259
+ span: sSpan
260
+ });
261
+ var sRepeaterBlock = object({
262
+ type: literal("repeater" /* Repeater */),
263
+ source: sRepeaterSource,
264
+ blocks: array(lazy(() => sBlock)),
265
+ span: sSpan
266
+ });
267
+ var sCardBlock = object({
268
+ type: literal("card" /* Card */),
269
+ title: optional2(string2()),
270
+ description: optional2(string2()),
271
+ blocks: array(lazy(() => sBlock)),
272
+ span: sSpan
273
+ });
274
+ var sBannerBlock = object({
275
+ type: literal("banner" /* Banner */),
276
+ variant: sMessageVariant,
277
+ title: optional2(string2()),
278
+ text: string2(),
279
+ span: sSpan
280
+ });
281
+ var sCopyFieldBlock = object({
282
+ type: literal("copyField" /* CopyField */),
283
+ label: string2(),
284
+ value: optional2(string2()),
285
+ computedKey: optional2(string2()),
286
+ fieldKey: optional2(string2()),
287
+ span: sSpan
288
+ });
289
+ var sInlineRowBlock = object({
290
+ type: literal("inlineRow" /* InlineRow */),
291
+ blocks: array(lazy(() => sBlock)),
292
+ span: sSpan
293
+ });
294
+ var sTabsBlock = object({
295
+ type: literal("tabs" /* Tabs */),
296
+ tabs: array(
297
+ object({
298
+ id: string2(),
299
+ label: string2(),
300
+ blocks: array(lazy(() => sBlock))
301
+ })
302
+ ),
303
+ span: sSpan
304
+ });
305
+ var sAccordionBlock = object({
306
+ type: literal("accordion" /* Accordion */),
307
+ sections: array(
308
+ object({
309
+ id: string2(),
310
+ title: string2(),
311
+ defaultOpen: optional2(boolean2()),
312
+ blocks: array(lazy(() => sBlock))
313
+ })
314
+ ),
315
+ span: sSpan
316
+ });
317
+ var sKeyValueBlock = object({
318
+ type: literal("keyValue" /* KeyValue */),
319
+ rows: array(
320
+ object({
321
+ label: string2(),
322
+ value: optional2(string2()),
323
+ computedKey: optional2(string2())
324
+ })
325
+ ),
326
+ span: sSpan
327
+ });
328
+ var sBlock = union([
329
+ sFieldBlock,
330
+ sGroupBlock,
331
+ sToggleBlock,
332
+ sActionBlock,
333
+ sCalloutBlock,
334
+ sMarkdownBlock,
335
+ sDividerBlock,
336
+ sConditionalBlock,
337
+ sRepeaterBlock,
338
+ sCardBlock,
339
+ sBannerBlock,
340
+ sCopyFieldBlock,
341
+ sInlineRowBlock,
342
+ sTabsBlock,
343
+ sAccordionBlock,
344
+ sKeyValueBlock
345
+ ]);
346
+ var sStep = object({
347
+ id: string2(),
348
+ title: string2(),
349
+ intro: optional2(string2()),
350
+ blocks: array(sBlock),
351
+ repeatFor: optional2(sRepeaterSource),
352
+ status: optional2(
353
+ union([
354
+ object({ from: literal("fields" /* Fields */) }),
355
+ object({ from: literal("action" /* Action */), actionId: string2() })
356
+ ])
357
+ )
358
+ });
359
+ var sAction = object({
360
+ id: string2(),
361
+ label: optional2(string2()),
362
+ perCountry: optional2(boolean2())
363
+ });
364
+ var sSetupLayout = object({
365
+ customScreen: optional2(string2()),
366
+ mode: optional2(enums2(Object.values(LayoutMode))),
367
+ intro: optional2(string2()),
368
+ steps: array(sStep),
369
+ actions: optional2(array(sAction))
370
+ });
371
+ function validateSetupLayout(input) {
372
+ const [error, result] = validate2(input, sSetupLayout);
373
+ if (error) {
374
+ return {
375
+ success: false,
376
+ errors: error.failures().map((f) => {
377
+ const path = f.path.join(".");
378
+ return path ? `${path}: ${f.message}` : f.message;
379
+ })
380
+ };
381
+ }
382
+ return { success: true, data: result };
383
+ }
384
+ function crossValidateSetupLayout(layout, configSchema) {
385
+ const errors = [];
386
+ const knownKeys = new Set(configSchema.map((f) => f.key));
387
+ const knownActions = new Set((layout.actions ?? []).map((a) => a.id));
388
+ const walk = (blocks, path) => {
389
+ blocks.forEach((b, i) => {
390
+ const here = `${path}[${i}]`;
391
+ if (b.type === "field" /* Field */ && !knownKeys.has(b.key)) {
392
+ errors.push(`${here}.key: "${b.key}" not in configSchema`);
393
+ } else if (b.type === "group" /* Group */) {
394
+ b.fields.forEach((k, j) => {
395
+ if (!knownKeys.has(k)) {
396
+ errors.push(`${here}.fields[${j}]: "${k}" not in configSchema`);
397
+ }
398
+ });
399
+ } else if (b.type === "action" /* Action */ && knownActions.size > 0) {
400
+ if (!knownActions.has(b.id)) {
401
+ errors.push(`${here}.id: action "${b.id}" not declared`);
402
+ }
403
+ } else if (b.type === "conditional" /* Conditional */ || b.type === "repeater" /* Repeater */ || b.type === "card" /* Card */ || b.type === "inlineRow" /* InlineRow */) {
404
+ walk(b.blocks, `${here}.blocks`);
405
+ } else if (b.type === "tabs" /* Tabs */) {
406
+ b.tabs.forEach((t, j) => walk(t.blocks, `${here}.tabs[${j}].blocks`));
407
+ } else if (b.type === "accordion" /* Accordion */) {
408
+ b.sections.forEach(
409
+ (s, j) => walk(s.blocks, `${here}.sections[${j}].blocks`)
410
+ );
411
+ }
412
+ });
413
+ };
414
+ layout.steps.forEach((s, i) => walk(s.blocks, `steps[${i}].blocks`));
415
+ return errors;
416
+ }
417
+
418
+ // src/capability.ts
419
+ var CapabilityKind = {
420
+ Payment: "payment",
421
+ Shipping: "shipping",
422
+ Analytics: "analytics"
423
+ };
424
+ function capability(kind, impl) {
425
+ return { kind, impl };
426
+ }
427
+ function paymentCapability(impl) {
428
+ return { kind: CapabilityKind.Payment, impl };
429
+ }
430
+ function shippingCapability(impl) {
431
+ return { kind: CapabilityKind.Shipping, impl };
432
+ }
433
+ var PLUGIN_FACTORY_BRAND = /* @__PURE__ */ Symbol.for("vivoa.plugin.factory");
434
+ function definePlugin(factory) {
435
+ Object.defineProperty(factory, PLUGIN_FACTORY_BRAND, {
436
+ value: true,
437
+ enumerable: false
438
+ });
439
+ return factory;
440
+ }
441
+ function isPluginFactory(value) {
442
+ return typeof value === "function" && value[PLUGIN_FACTORY_BRAND] === true;
443
+ }
444
+
445
+ // src/permissions.ts
446
+ var PERMISSION_RE = /^([a-z0-9]+)\.([a-z0-9]+)(?::(.+))?$/i;
447
+ function parsePermission(raw) {
448
+ const m = PERMISSION_RE.exec(raw);
449
+ if (!m) return null;
450
+ const [, domain, action, resource] = m;
451
+ return resource === void 0 ? { domain, action } : { domain, action, resource };
452
+ }
453
+ function isPermissionGranted(required, granted) {
454
+ return granted.some((g) => {
455
+ if (g.domain !== required.domain || g.action !== required.action) {
456
+ return false;
457
+ }
458
+ if (required.resource === void 0) return true;
459
+ return g.resource === "*" || g.resource === required.resource;
460
+ });
461
+ }
462
+ function checkPermissions(required, grantedRaw) {
463
+ const grantedParsed = grantedRaw.map(parsePermission).filter((p) => p !== null);
464
+ const granted = [];
465
+ const missing = [];
466
+ for (const raw of required) {
467
+ const parsed = parsePermission(raw);
468
+ if (parsed && isPermissionGranted(parsed, grantedParsed)) {
469
+ granted.push(raw);
470
+ } else {
471
+ missing.push(raw);
472
+ }
473
+ }
474
+ return { ok: missing.length === 0, granted, missing };
475
+ }
476
+
477
+ // src/version.ts
478
+ var SDK_VERSION = "0.5.0";
479
+ function isSdkCompatible(pluginSdkVersion, hostSdkVersion) {
480
+ const parse = (v) => {
481
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
482
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
483
+ };
484
+ const p = parse(pluginSdkVersion);
485
+ const h = parse(hostSdkVersion);
486
+ if (!p || !h) return false;
487
+ if (p[0] !== h[0]) return false;
488
+ if (p[0] === 0) {
489
+ return p[1] === h[1] && h[2] >= p[2];
490
+ }
491
+ return h[1] > p[1] || h[1] === p[1] && h[2] >= p[2];
492
+ }
493
+ export {
494
+ ActionVariant,
495
+ BlockType,
496
+ CapabilityKind,
497
+ CodFeeType,
498
+ ConfigScope,
499
+ FieldType,
500
+ FieldVariant,
501
+ GatewayEnvironment,
502
+ LayoutMode,
503
+ MessageVariant,
504
+ PAYMENT_PLUGIN_TOKEN,
505
+ PluginType,
506
+ RepeaterSource,
507
+ SDK_VERSION,
508
+ SHIPPING_PLUGIN_TOKEN,
509
+ ShippingFeature,
510
+ StepStatusFrom,
511
+ TransactionStatus,
512
+ capability,
513
+ checkPermissions,
514
+ createPluginConfig,
515
+ crossValidateSetupLayout,
516
+ definePlugin,
517
+ field,
518
+ isConfigurablePlugin,
519
+ isPermissionGranted,
520
+ isPluginFactory,
521
+ isSdkCompatible,
522
+ parsePermission,
523
+ paymentCapability,
524
+ shippingCapability,
525
+ validateSetupLayout
526
+ };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@vivoa/plugin-sdk",
3
+ "version": "0.5.0",
4
+ "description": "Public contracts for Vivoa plugins (types, DI tokens, config + setup-layout validators)",
5
+ "license": "MIT",
6
+ "author": "Vivoa",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/VivoaPlatform/vivoa-plugin-sdk.git"
10
+ },
11
+ "keywords": [
12
+ "vivoa",
13
+ "plugin",
14
+ "sdk",
15
+ "ecommerce",
16
+ "payment",
17
+ "shipping"
18
+ ],
19
+ "main": "dist/index.js",
20
+ "module": "dist/index.mjs",
21
+ "types": "dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.mjs",
26
+ "require": "./dist/index.js"
27
+ }
28
+ },
29
+ "sideEffects": false,
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "publishConfig": {
34
+ "registry": "https://registry.npmjs.org",
35
+ "access": "public"
36
+ },
37
+ "scripts": {
38
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean --target node18",
39
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch --target node18",
40
+ "test": "vitest run",
41
+ "test:watch": "vitest",
42
+ "typecheck": "tsc --noEmit -p tsconfig.json",
43
+ "prepublishOnly": "pnpm run typecheck && pnpm run test && pnpm run build"
44
+ },
45
+ "dependencies": {
46
+ "superstruct": "^2.0.2"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^22.10.7",
50
+ "tsup": "^8.3.5",
51
+ "typescript": "^5.7.3",
52
+ "vitest": "^2.1.8"
53
+ },
54
+ "pnpm": {
55
+ "onlyBuiltDependencies": [
56
+ "esbuild"
57
+ ]
58
+ }
59
+ }