@vivoa/plugin-sdk 0.6.5 → 0.6.7
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/chunk-DGJZW46B.mjs +37 -0
- package/dist/host-api-yX49e7zE.d.mts +1533 -0
- package/dist/host-api-yX49e7zE.d.ts +1533 -0
- package/dist/index.d.mts +5 -1382
- package/dist/index.d.ts +5 -1382
- package/dist/index.js +155 -14
- package/dist/index.mjs +166 -44
- 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 +15 -9
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import {
|
|
2
|
+
checkPermissions
|
|
3
|
+
} from "../chunk-DGJZW46B.mjs";
|
|
4
|
+
|
|
5
|
+
// src/testing/mock-host.ts
|
|
6
|
+
var DEFAULT_RESPONSE = { status: 200, headers: {}, body: {} };
|
|
7
|
+
function createMockHostApi(opts = {}) {
|
|
8
|
+
const calls = {
|
|
9
|
+
"http.request": [],
|
|
10
|
+
"secrets.get": [],
|
|
11
|
+
"storage.getUrl": [],
|
|
12
|
+
"storage.put": [],
|
|
13
|
+
"sequence.next": [],
|
|
14
|
+
"host.on": []
|
|
15
|
+
};
|
|
16
|
+
const registered = [];
|
|
17
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
18
|
+
const seqState = new Map(
|
|
19
|
+
Object.entries(opts.sequences ?? {})
|
|
20
|
+
);
|
|
21
|
+
const enforce = (required) => {
|
|
22
|
+
if (!opts.grantedPermissions) return;
|
|
23
|
+
const check = checkPermissions([required], opts.grantedPermissions);
|
|
24
|
+
if (!check.ok) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`MockHostApi: permission denied \u2014 plugin needs "${required}" but it is not in grantedPermissions. Declare it in your manifest.`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const noop = () => {
|
|
31
|
+
};
|
|
32
|
+
const logger = {
|
|
33
|
+
debug: noop,
|
|
34
|
+
info: noop,
|
|
35
|
+
warn: noop,
|
|
36
|
+
error: noop
|
|
37
|
+
};
|
|
38
|
+
const http = {
|
|
39
|
+
// async so a denied-permission throw surfaces as a rejected promise, not a
|
|
40
|
+
// synchronous throw — matches the real host's async port contract.
|
|
41
|
+
request: async (req) => {
|
|
42
|
+
const hostname = safeHostname(req.url);
|
|
43
|
+
enforce(`http.outbound:${hostname}`);
|
|
44
|
+
calls["http.request"].push(req);
|
|
45
|
+
return opts.httpResponses?.[req.url] ?? Object.entries(opts.httpResponses ?? {}).find(
|
|
46
|
+
([k]) => req.url.includes(k)
|
|
47
|
+
)?.[1] ?? DEFAULT_RESPONSE;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const secrets = {
|
|
51
|
+
get: async (key) => {
|
|
52
|
+
enforce(`secrets.read:${key}`);
|
|
53
|
+
calls["secrets.get"].push(key);
|
|
54
|
+
return opts.secrets?.[key];
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const storage = {
|
|
58
|
+
getUrl: (key) => {
|
|
59
|
+
calls["storage.getUrl"].push(key);
|
|
60
|
+
return Promise.resolve(`mock://storage/${key}`);
|
|
61
|
+
},
|
|
62
|
+
put: (key, body) => {
|
|
63
|
+
calls["storage.put"].push({ key, body });
|
|
64
|
+
return Promise.resolve();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const sequence = {
|
|
68
|
+
next: (scope) => {
|
|
69
|
+
calls["sequence.next"].push(scope);
|
|
70
|
+
const current = seqState.get(scope) ?? 0;
|
|
71
|
+
const next = current + 1;
|
|
72
|
+
seqState.set(scope, next);
|
|
73
|
+
return Promise.resolve(next);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const host = {
|
|
77
|
+
logger,
|
|
78
|
+
http,
|
|
79
|
+
secrets,
|
|
80
|
+
storage,
|
|
81
|
+
sequence,
|
|
82
|
+
calls,
|
|
83
|
+
registered,
|
|
84
|
+
handlers,
|
|
85
|
+
on: (event, handler) => {
|
|
86
|
+
calls["host.on"].push(event);
|
|
87
|
+
const list = handlers.get(event) ?? [];
|
|
88
|
+
list.push(handler);
|
|
89
|
+
handlers.set(event, list);
|
|
90
|
+
},
|
|
91
|
+
registerCapability: (cap) => {
|
|
92
|
+
registered.push(cap);
|
|
93
|
+
},
|
|
94
|
+
emit: async (event, payload) => {
|
|
95
|
+
for (const h of handlers.get(event) ?? []) await h(payload);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
return host;
|
|
99
|
+
}
|
|
100
|
+
function safeHostname(url) {
|
|
101
|
+
try {
|
|
102
|
+
return new URL(url).hostname;
|
|
103
|
+
} catch {
|
|
104
|
+
return url;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function createDispatchContext(config = {}, overrides = {}) {
|
|
108
|
+
return { config, ...overrides };
|
|
109
|
+
}
|
|
110
|
+
export {
|
|
111
|
+
createDispatchContext,
|
|
112
|
+
createMockHostApi
|
|
113
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vivoa/plugin-sdk",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "Public contracts for Vivoa plugins (types, DI tokens, config + setup-layout validators)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Vivoa",
|
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
"types": "./dist/index.d.ts",
|
|
25
25
|
"import": "./dist/index.mjs",
|
|
26
26
|
"require": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./testing": {
|
|
29
|
+
"types": "./dist/testing/index.d.ts",
|
|
30
|
+
"import": "./dist/testing/index.mjs",
|
|
31
|
+
"require": "./dist/testing/index.js"
|
|
27
32
|
}
|
|
28
33
|
},
|
|
29
34
|
"sideEffects": false,
|
|
@@ -34,6 +39,14 @@
|
|
|
34
39
|
"registry": "https://registry.npmjs.org",
|
|
35
40
|
"access": "public"
|
|
36
41
|
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup src/index.ts src/testing/index.ts --format cjs,esm --dts --clean --target node18",
|
|
44
|
+
"dev": "tsup src/index.ts src/testing/index.ts --format cjs,esm --dts --watch --target node18",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
48
|
+
"prepublishOnly": "pnpm run typecheck && pnpm run test && pnpm run build"
|
|
49
|
+
},
|
|
37
50
|
"dependencies": {
|
|
38
51
|
"superstruct": "^2.0.2"
|
|
39
52
|
},
|
|
@@ -42,12 +55,5 @@
|
|
|
42
55
|
"tsup": "^8.3.5",
|
|
43
56
|
"typescript": "^5.7.3",
|
|
44
57
|
"vitest": "^2.1.8"
|
|
45
|
-
},
|
|
46
|
-
"scripts": {
|
|
47
|
-
"build": "tsup src/index.ts --format cjs,esm --dts --clean --target node18",
|
|
48
|
-
"dev": "tsup src/index.ts --format cjs,esm --dts --watch --target node18",
|
|
49
|
-
"test": "vitest run",
|
|
50
|
-
"test:watch": "vitest",
|
|
51
|
-
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
52
58
|
}
|
|
53
|
-
}
|
|
59
|
+
}
|