@oktis-works/plugin-runtime 0.5.10 → 0.7.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/bootstrap.d.ts +86 -21
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +254 -42
- package/dist/bootstrap.js.map +1 -1
- package/dist/content-query.d.ts +29 -0
- package/dist/content-query.d.ts.map +1 -0
- package/dist/content-query.js +109 -0
- package/dist/content-query.js.map +1 -0
- package/dist/hook-registry.d.ts +21 -21
- package/dist/hook-registry.d.ts.map +1 -1
- package/dist/hook-registry.js +26 -26
- package/dist/hook-registry.js.map +1 -1
- package/dist/http.d.ts +49 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +313 -0
- package/dist/http.js.map +1 -0
- package/dist/i18n.d.ts +16 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +73 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.js +3 -3
- package/dist/loader.js.map +1 -1
- package/dist/manifest.d.ts +11 -6
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +227 -17
- package/dist/manifest.js.map +1 -1
- package/dist/plugin-api.d.ts +191 -38
- package/dist/plugin-api.d.ts.map +1 -1
- package/dist/plugin-api.js +572 -10
- package/dist/plugin-api.js.map +1 -1
- package/dist/plugin-migrations.d.ts +15 -0
- package/dist/plugin-migrations.d.ts.map +1 -0
- package/dist/plugin-migrations.js +68 -0
- package/dist/plugin-migrations.js.map +1 -0
- package/dist/sandbox.d.ts +13 -11
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/sandbox.js +11 -9
- package/dist/sandbox.js.map +1 -1
- package/dist/style-isolation.d.ts +3 -3
- package/dist/style-isolation.js +6 -6
- package/dist/style-isolation.js.map +1 -1
- package/package.json +7 -6
package/dist/plugin-api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-api.d.ts","sourceRoot":"","sources":["../src/plugin-api.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin-api.d.ts","sourceRoot":"","sources":["../src/plugin-api.ts"],"names":[],"mappings":"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"}
|
package/dist/plugin-api.js
CHANGED
|
@@ -1,17 +1,48 @@
|
|
|
1
|
-
// @oktis-works/plugin-runtime - PluginAPI
|
|
1
|
+
// @oktis-works/plugin-runtime - Full PluginAPI (WP7 + WP8)
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// This module exposes a `createPluginApi` factory that the core injects with
|
|
4
|
+
// the real services (settings, eventBus, db, etc.). The plugin receives an
|
|
5
|
+
// object with typed methods; the runtime only knows the interface (avoids a
|
|
6
|
+
// dependency cycle).
|
|
6
7
|
import { randomUUID } from 'node:crypto';
|
|
8
|
+
import { PERMISSIONS, createPluginAdminApi } from '@oktis-works/plugin-sdk';
|
|
9
|
+
import { createHttpApi } from './http.js';
|
|
10
|
+
import { i18nFallbackChain, i18nInterpolate } from './i18n.js';
|
|
11
|
+
import { ContentQueryBuilder } from './content-query.js';
|
|
7
12
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
13
|
+
* Creates the real PluginAPI from the core dependencies.
|
|
14
|
+
* Called by the core at bootstrap (not by plugin-runtime directly).
|
|
15
|
+
* Returns { api: PluginAPI, internal: PluginApiInternal } to separate the
|
|
16
|
+
* public API from the internal details.
|
|
11
17
|
*/
|
|
18
|
+
/**
|
|
19
|
+
* In-memory cache fallback when the core provided no backend (dev or legacy
|
|
20
|
+
* API). Always available — `api.cache` never throws for unavailability.
|
|
21
|
+
*/
|
|
22
|
+
function createMemoryCacheBackend() {
|
|
23
|
+
const store = new Map();
|
|
24
|
+
return {
|
|
25
|
+
get: async (key) => {
|
|
26
|
+
const entry = store.get(key);
|
|
27
|
+
if (!entry)
|
|
28
|
+
return null;
|
|
29
|
+
if (Date.now() > entry.expiresAt) {
|
|
30
|
+
store.delete(key);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return entry.value;
|
|
34
|
+
},
|
|
35
|
+
set: async (key, value, ttlSeconds) => {
|
|
36
|
+
store.set(key, { value, expiresAt: Date.now() + (ttlSeconds ?? 300) * 1000 });
|
|
37
|
+
},
|
|
38
|
+
del: async (key) => {
|
|
39
|
+
store.delete(key);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
12
43
|
export function createPluginApi(deps) {
|
|
13
|
-
const { hooks, manifest, settings, eventBus, db, getTenantId, getUserId, getTenantById, getUserById, allowedPermissions } = deps;
|
|
14
|
-
//
|
|
44
|
+
const { hooks, manifest, settings, eventBus, db, getTenantId, getUserId, getTenantById, getUserById, allowedPermissions, registerFieldTypes, registerPostTypes, registerTaxonomies } = deps;
|
|
45
|
+
// Validates the permission before running a query
|
|
15
46
|
function checkDbPermission() {
|
|
16
47
|
if (!allowedPermissions.includes('db:query')) {
|
|
17
48
|
throw new Error('Plugin permission denied: db:query');
|
|
@@ -19,6 +50,406 @@ export function createPluginApi(deps) {
|
|
|
19
50
|
}
|
|
20
51
|
const registeredRoutes = [];
|
|
21
52
|
const registeredFields = [];
|
|
53
|
+
// Runtime type includes pluginName and isPluginType added during registration
|
|
54
|
+
const registeredFieldTypes = [];
|
|
55
|
+
const registeredPostTypes = [];
|
|
56
|
+
const registeredTaxonomies = [];
|
|
57
|
+
const registeredBlocks = [];
|
|
58
|
+
const registeredPatterns = [];
|
|
59
|
+
// ── api.admin (FEAT-098/DECI-027): declarative slots registered at
|
|
60
|
+
// runtime; the /api/v1/plugins/extensions endpoint merges with manifest.admin.
|
|
61
|
+
const adminApi = createPluginAdminApi(manifest.name);
|
|
62
|
+
/** CPT/taxonomy slugs: lowercase, digits, underscore and hyphen (WP style). */
|
|
63
|
+
const SLUG_PATTERN = /^[a-z][a-z0-9_-]*$/;
|
|
64
|
+
/** Reserved slugs no plugin can claim. */
|
|
65
|
+
const RESERVED_SLUGS = new Set(['post', 'page', 'media', 'user', 'content', 'attachment', 'category', 'tag']);
|
|
66
|
+
// ── api.cache (Item 2): plugin:{tenant}:{plugin}:{key} namespace + tags ──
|
|
67
|
+
const cacheBackend = deps.cache ?? createMemoryCacheBackend();
|
|
68
|
+
function cacheKeyFor(key) {
|
|
69
|
+
if (typeof key !== 'string' || key.trim().length === 0) {
|
|
70
|
+
throw new Error('cache key must be a non-empty string');
|
|
71
|
+
}
|
|
72
|
+
if (key.length > 200) {
|
|
73
|
+
throw new Error('cache key too long (max 200 chars)');
|
|
74
|
+
}
|
|
75
|
+
return `plugin:${getTenantId() ?? 'platform'}:${manifest.name}:${key}`;
|
|
76
|
+
}
|
|
77
|
+
function tagIndexKeyFor(tag) {
|
|
78
|
+
return `plugin:${getTenantId() ?? 'platform'}:${manifest.name}:__tag__:${tag}`;
|
|
79
|
+
}
|
|
80
|
+
function requirePermission(permission) {
|
|
81
|
+
if (!allowedPermissions.includes(permission)) {
|
|
82
|
+
throw new Error(`Plugin permission denied: ${permission}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// ── Item 18: audit of sensitive plugin operations (write queries,
|
|
86
|
+
// addRoute, jobs) → audit_log. Fire-and-forget: an audit failure never
|
|
87
|
+
// blocks or breaks the plugin operation (same rule as auditService).
|
|
88
|
+
const MUTATION_PATTERN = /^\s*(INSERT|UPDATE|DELETE|CREATE|ALTER|DROP|TRUNCATE|GRANT|REVOKE)\b/i;
|
|
89
|
+
function auditPlugin(action, resourceType, changes) {
|
|
90
|
+
try {
|
|
91
|
+
void deps.audit?.record({
|
|
92
|
+
tenantId: getTenantId(),
|
|
93
|
+
action,
|
|
94
|
+
resourceType,
|
|
95
|
+
resourceId: manifest.name,
|
|
96
|
+
changes: { plugin: manifest.name, ...changes },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// auditing never blocks the operation
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const cacheApi = {
|
|
104
|
+
async get(key) {
|
|
105
|
+
requirePermission(PERMISSIONS.CACHE_READ);
|
|
106
|
+
return cacheBackend.get(cacheKeyFor(key));
|
|
107
|
+
},
|
|
108
|
+
async set(key, value, options) {
|
|
109
|
+
requirePermission(PERMISSIONS.CACHE_WRITE);
|
|
110
|
+
const fullKey = cacheKeyFor(key);
|
|
111
|
+
const ttl = options?.ttlSeconds;
|
|
112
|
+
await cacheBackend.set(fullKey, value, ttl);
|
|
113
|
+
// Tag → keys index (TTL tracking the value; the index expires with it
|
|
114
|
+
// and orphan keys expire through their own value TTL).
|
|
115
|
+
for (const tag of options?.tags ?? []) {
|
|
116
|
+
if (typeof tag !== 'string' || tag.trim().length === 0) {
|
|
117
|
+
throw new Error('cache tag must be a non-empty string');
|
|
118
|
+
}
|
|
119
|
+
const indexKey = tagIndexKeyFor(tag);
|
|
120
|
+
const indexed = (await cacheBackend.get(indexKey)) ?? [];
|
|
121
|
+
if (!indexed.includes(fullKey))
|
|
122
|
+
indexed.push(fullKey);
|
|
123
|
+
await cacheBackend.set(indexKey, indexed, ttl);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
async delete(key) {
|
|
127
|
+
requirePermission(PERMISSIONS.CACHE_WRITE);
|
|
128
|
+
await cacheBackend.del(cacheKeyFor(key));
|
|
129
|
+
},
|
|
130
|
+
async wrap(key, ttlSeconds, producer) {
|
|
131
|
+
requirePermission(PERMISSIONS.CACHE_WRITE);
|
|
132
|
+
const fullKey = cacheKeyFor(key);
|
|
133
|
+
const cached = await cacheBackend.get(fullKey);
|
|
134
|
+
if (cached !== null)
|
|
135
|
+
return cached;
|
|
136
|
+
const value = await producer();
|
|
137
|
+
await cacheBackend.set(fullKey, value, ttlSeconds);
|
|
138
|
+
return value;
|
|
139
|
+
},
|
|
140
|
+
async invalidateTags(tags) {
|
|
141
|
+
requirePermission(PERMISSIONS.CACHE_WRITE);
|
|
142
|
+
for (const tag of tags) {
|
|
143
|
+
const indexKey = tagIndexKeyFor(tag);
|
|
144
|
+
const indexed = (await cacheBackend.get(indexKey)) ?? [];
|
|
145
|
+
for (const key of indexed) {
|
|
146
|
+
await cacheBackend.del(key);
|
|
147
|
+
}
|
|
148
|
+
await cacheBackend.del(indexKey);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
// ── api.jobs (Item 3): local handlers + `plugins` queue (repeat/one-shot) ──
|
|
153
|
+
const JOB_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_.-]{0,63}$/;
|
|
154
|
+
const MAX_SCHEDULED_JOBS = 10;
|
|
155
|
+
const jobHandlers = new Map();
|
|
156
|
+
const scheduledJobNames = new Set();
|
|
157
|
+
const jobsCtx = () => ({ pluginName: manifest.name, tenantId: getTenantId() });
|
|
158
|
+
const jobsApi = {
|
|
159
|
+
on(jobName, handler) {
|
|
160
|
+
if (!JOB_NAME_PATTERN.test(jobName)) {
|
|
161
|
+
throw new Error(`Invalid job name "${jobName}" (expected ${JOB_NAME_PATTERN})`);
|
|
162
|
+
}
|
|
163
|
+
if (typeof handler !== 'function') {
|
|
164
|
+
throw new Error(`Job handler for "${jobName}" must be a function`);
|
|
165
|
+
}
|
|
166
|
+
jobHandlers.set(jobName, handler);
|
|
167
|
+
},
|
|
168
|
+
async schedule(jobName, spec) {
|
|
169
|
+
requirePermission(PERMISSIONS.JOBS_SCHEDULE);
|
|
170
|
+
if (!JOB_NAME_PATTERN.test(jobName)) {
|
|
171
|
+
throw new Error(`Invalid job name "${jobName}" (expected ${JOB_NAME_PATTERN})`);
|
|
172
|
+
}
|
|
173
|
+
const hasEvery = typeof spec?.every === 'number' && spec.every > 0;
|
|
174
|
+
const hasCron = typeof spec?.cron === 'string' && spec.cron.trim().length > 0;
|
|
175
|
+
if (hasEvery === hasCron) {
|
|
176
|
+
throw new Error(`schedule("${jobName}") requires exactly one of: every | cron`);
|
|
177
|
+
}
|
|
178
|
+
// Per-plugin ceiling — only new schedules count (re-scheduling the same name is free)
|
|
179
|
+
if (!scheduledJobNames.has(jobName)) {
|
|
180
|
+
if (scheduledJobNames.size >= MAX_SCHEDULED_JOBS) {
|
|
181
|
+
throw new Error(`Job schedule limit reached (${MAX_SCHEDULED_JOBS} per plugin)`);
|
|
182
|
+
}
|
|
183
|
+
scheduledJobNames.add(jobName);
|
|
184
|
+
}
|
|
185
|
+
if (!deps.jobs)
|
|
186
|
+
return false; // queue unavailable: degradation, never throws
|
|
187
|
+
const scheduled = await deps.jobs.schedule(jobName, hasCron ? { cron: spec.cron } : { every: spec.every }, jobsCtx());
|
|
188
|
+
if (scheduled) {
|
|
189
|
+
auditPlugin('plugin.jobs.schedule', 'job', { jobName, spec: hasCron ? { cron: spec.cron } : { every: spec.every } });
|
|
190
|
+
}
|
|
191
|
+
return scheduled;
|
|
192
|
+
},
|
|
193
|
+
async cancel(jobName) {
|
|
194
|
+
requirePermission(PERMISSIONS.JOBS_SCHEDULE);
|
|
195
|
+
scheduledJobNames.delete(jobName);
|
|
196
|
+
if (!deps.jobs)
|
|
197
|
+
return false;
|
|
198
|
+
const cancelled = await deps.jobs.cancel(jobName, jobsCtx());
|
|
199
|
+
if (cancelled) {
|
|
200
|
+
auditPlugin('plugin.jobs.cancel', 'job', { jobName });
|
|
201
|
+
}
|
|
202
|
+
return cancelled;
|
|
203
|
+
},
|
|
204
|
+
async enqueue(jobName, payload = {}) {
|
|
205
|
+
requirePermission(PERMISSIONS.JOBS_SCHEDULE);
|
|
206
|
+
if (!JOB_NAME_PATTERN.test(jobName)) {
|
|
207
|
+
throw new Error(`Invalid job name "${jobName}" (expected ${JOB_NAME_PATTERN})`);
|
|
208
|
+
}
|
|
209
|
+
if (!deps.jobs)
|
|
210
|
+
return false;
|
|
211
|
+
const enqueued = await deps.jobs.enqueue(jobName, payload, jobsCtx());
|
|
212
|
+
if (enqueued) {
|
|
213
|
+
auditPlugin('plugin.jobs.enqueue', 'job', { jobName });
|
|
214
|
+
}
|
|
215
|
+
return enqueued;
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
// ── api.http (Item 4 + Item 16): anti-SSRF with the manifest allowlist ──
|
|
219
|
+
const httpApi = createHttpApi({
|
|
220
|
+
allowHosts: manifest.httpPolicy?.allowHosts,
|
|
221
|
+
requirePermission,
|
|
222
|
+
});
|
|
223
|
+
// ── api.roles (Item 6): tenant role management, anti-escalation + audit.
|
|
224
|
+
// Permission, tenant scope and backend presence are validated here; the
|
|
225
|
+
// escalation rules (reserved names, wildcards, platform operations) live in
|
|
226
|
+
// the core service — a single enforcement point.
|
|
227
|
+
function rolesBackend() {
|
|
228
|
+
if (!deps.roles) {
|
|
229
|
+
throw new Error('api.roles is not available (roles backend not wired)');
|
|
230
|
+
}
|
|
231
|
+
return deps.roles;
|
|
232
|
+
}
|
|
233
|
+
function requireTenantScopeForRoles() {
|
|
234
|
+
const tenantId = getTenantId();
|
|
235
|
+
if (!tenantId) {
|
|
236
|
+
throw new Error('api.roles requires a tenant scope (platform plugins cannot manage roles)');
|
|
237
|
+
}
|
|
238
|
+
return tenantId;
|
|
239
|
+
}
|
|
240
|
+
const rolesApi = {
|
|
241
|
+
async add(input) {
|
|
242
|
+
requirePermission(PERMISSIONS.ROLES_CREATE);
|
|
243
|
+
const tenantId = requireTenantScopeForRoles();
|
|
244
|
+
if (!input || typeof input.name !== 'string' || input.name.trim().length === 0) {
|
|
245
|
+
throw new Error('Role "name" is required');
|
|
246
|
+
}
|
|
247
|
+
return rolesBackend().add({
|
|
248
|
+
tenantId,
|
|
249
|
+
pluginName: manifest.name,
|
|
250
|
+
name: input.name,
|
|
251
|
+
slug: input.slug,
|
|
252
|
+
permissions: input.permissions,
|
|
253
|
+
});
|
|
254
|
+
},
|
|
255
|
+
async remove(slug) {
|
|
256
|
+
requirePermission(PERMISSIONS.ROLES_CREATE);
|
|
257
|
+
const tenantId = requireTenantScopeForRoles();
|
|
258
|
+
if (!slug || typeof slug !== 'string') {
|
|
259
|
+
throw new Error('Role "slug" is required');
|
|
260
|
+
}
|
|
261
|
+
return rolesBackend().remove({ tenantId, pluginName: manifest.name, slug });
|
|
262
|
+
},
|
|
263
|
+
async addPermission(slug, permission) {
|
|
264
|
+
requirePermission(PERMISSIONS.ROLES_CREATE);
|
|
265
|
+
const tenantId = requireTenantScopeForRoles();
|
|
266
|
+
if (!slug || typeof slug !== 'string') {
|
|
267
|
+
throw new Error('Role "slug" is required');
|
|
268
|
+
}
|
|
269
|
+
if (!permission || typeof permission !== 'string') {
|
|
270
|
+
throw new Error('Permission must be an "action:resource" string');
|
|
271
|
+
}
|
|
272
|
+
return rolesBackend().addPermission({ tenantId, pluginName: manifest.name, slug, permission });
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
// ── api.assets (Item 8): asset enqueue for the public site. Per-plugin
|
|
276
|
+
// URL dedupe (repeated calls are no-ops); the cross-plugin merge happens at
|
|
277
|
+
// render time (first enqueue wins). SRI is declared by the plugin
|
|
278
|
+
// (integrity + crossOrigin); src accepts http(s):// or root-relative (/...).
|
|
279
|
+
const ASSET_SRC_PATTERN = /^(https?:\/\/|\/(?!\/))[^\s"']+$/i;
|
|
280
|
+
const registeredAssets = [];
|
|
281
|
+
function validateAssetSrc(src) {
|
|
282
|
+
if (typeof src !== 'string' || !ASSET_SRC_PATTERN.test(src)) {
|
|
283
|
+
throw new Error(`Invalid asset src "${String(src)}" (expected http(s):// URL or root-relative /path)`);
|
|
284
|
+
}
|
|
285
|
+
return src;
|
|
286
|
+
}
|
|
287
|
+
const assetsApi = {
|
|
288
|
+
enqueueStyle(style) {
|
|
289
|
+
requirePermission(PERMISSIONS.ASSETS_ENQUEUE);
|
|
290
|
+
const src = validateAssetSrc(style?.src);
|
|
291
|
+
if (registeredAssets.some((asset) => asset.src === src))
|
|
292
|
+
return;
|
|
293
|
+
const entry = {
|
|
294
|
+
kind: 'style',
|
|
295
|
+
src,
|
|
296
|
+
version: typeof style?.version === 'string' && style.version ? style.version : undefined,
|
|
297
|
+
media: typeof style?.media === 'string' && style.media ? style.media : undefined,
|
|
298
|
+
integrity: typeof style?.integrity === 'string' && style.integrity ? style.integrity : undefined,
|
|
299
|
+
crossOrigin: typeof style?.crossOrigin === 'string' && style.crossOrigin ? style.crossOrigin : undefined,
|
|
300
|
+
pluginName: manifest.name,
|
|
301
|
+
};
|
|
302
|
+
registeredAssets.push(entry);
|
|
303
|
+
},
|
|
304
|
+
enqueueScript(script) {
|
|
305
|
+
requirePermission(PERMISSIONS.ASSETS_ENQUEUE);
|
|
306
|
+
const src = validateAssetSrc(script?.src);
|
|
307
|
+
if (registeredAssets.some((asset) => asset.src === src))
|
|
308
|
+
return;
|
|
309
|
+
const strategy = script?.strategy === 'defer' || script?.strategy === 'async' ? script.strategy : 'blocking';
|
|
310
|
+
const entry = {
|
|
311
|
+
kind: 'script',
|
|
312
|
+
src,
|
|
313
|
+
version: typeof script?.version === 'string' && script.version ? script.version : undefined,
|
|
314
|
+
strategy,
|
|
315
|
+
placement: script?.placement === 'footer' ? 'footer' : 'head',
|
|
316
|
+
integrity: typeof script?.integrity === 'string' && script.integrity ? script.integrity : undefined,
|
|
317
|
+
crossOrigin: typeof script?.crossOrigin === 'string' && script.crossOrigin ? script.crossOrigin : undefined,
|
|
318
|
+
pluginName: manifest.name,
|
|
319
|
+
};
|
|
320
|
+
registeredAssets.push(entry);
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
// ── api.i18n (Item 9): per-plugin server-side translations. Bundles are
|
|
324
|
+
// private to the plugin (namespacing is natural — each plugin owns its
|
|
325
|
+
// store); fallback chain locale → base language → 'en' → the key itself.
|
|
326
|
+
const i18nBundles = new Map();
|
|
327
|
+
let i18nDefaultLocale = 'en';
|
|
328
|
+
const i18nApi = {
|
|
329
|
+
load(locale, bundle) {
|
|
330
|
+
if (typeof locale !== 'string' || locale.trim().length === 0) {
|
|
331
|
+
throw new Error('i18n.load requires a locale');
|
|
332
|
+
}
|
|
333
|
+
if (!bundle || typeof bundle !== 'object' || Array.isArray(bundle)) {
|
|
334
|
+
throw new Error('i18n.load requires a flat { key: string } bundle');
|
|
335
|
+
}
|
|
336
|
+
const existing = i18nBundles.get(locale) ?? {};
|
|
337
|
+
for (const [key, value] of Object.entries(bundle)) {
|
|
338
|
+
if (typeof value !== 'string') {
|
|
339
|
+
throw new Error(`i18n bundle value for "${key}" must be a string`);
|
|
340
|
+
}
|
|
341
|
+
existing[key] = value;
|
|
342
|
+
}
|
|
343
|
+
i18nBundles.set(locale, existing);
|
|
344
|
+
},
|
|
345
|
+
translate(key, params, locale) {
|
|
346
|
+
if (typeof key !== 'string' || key.length === 0)
|
|
347
|
+
return String(key ?? '');
|
|
348
|
+
const requested = typeof locale === 'string' && locale ? locale : i18nDefaultLocale;
|
|
349
|
+
for (const candidate of i18nFallbackChain(requested)) {
|
|
350
|
+
const value = i18nBundles.get(candidate)?.[key];
|
|
351
|
+
if (typeof value === 'string')
|
|
352
|
+
return i18nInterpolate(value, params);
|
|
353
|
+
}
|
|
354
|
+
return key;
|
|
355
|
+
},
|
|
356
|
+
locale: () => i18nDefaultLocale,
|
|
357
|
+
setDefaultLocale(locale) {
|
|
358
|
+
if (typeof locale !== 'string' || locale.trim().length === 0) {
|
|
359
|
+
throw new Error('i18n.setDefaultLocale requires a locale');
|
|
360
|
+
}
|
|
361
|
+
i18nDefaultLocale = locale;
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
// ── api.health (Item 14): named per-plugin health checks, aggregated by
|
|
365
|
+
// GET /api/v1/health with a 2s budget per check. Duplicate names reject.
|
|
366
|
+
const healthChecks = new Map();
|
|
367
|
+
const healthApi = {
|
|
368
|
+
register(name, check) {
|
|
369
|
+
requirePermission(PERMISSIONS.HEALTH_REGISTER);
|
|
370
|
+
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
371
|
+
throw new Error('health.register requires a non-empty name');
|
|
372
|
+
}
|
|
373
|
+
if (typeof check !== 'function') {
|
|
374
|
+
throw new Error('health.register requires a check function');
|
|
375
|
+
}
|
|
376
|
+
if (healthChecks.has(name)) {
|
|
377
|
+
throw new Error(`Health check "${name}" already registered`);
|
|
378
|
+
}
|
|
379
|
+
healthChecks.set(name, check);
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
// ── api.mail (Item 12): per-plugin templates + send through the core
|
|
383
|
+
// outbox. Templates support {param} (same interpolation as i18n). The
|
|
384
|
+
// backend is optional: absent → send returns false (queue down, like jobs).
|
|
385
|
+
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
386
|
+
const mailTemplates = new Map();
|
|
387
|
+
function renderMailString(template, params) {
|
|
388
|
+
return i18nInterpolate(template, params);
|
|
389
|
+
}
|
|
390
|
+
const mailApi = {
|
|
391
|
+
registerTemplate(name, template) {
|
|
392
|
+
requirePermission(PERMISSIONS.MAIL_SEND);
|
|
393
|
+
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
394
|
+
throw new Error('mail.registerTemplate requires a non-empty name');
|
|
395
|
+
}
|
|
396
|
+
if (!template || typeof template.subject !== 'string' || template.subject.trim().length === 0) {
|
|
397
|
+
throw new Error('mail template "subject" is required');
|
|
398
|
+
}
|
|
399
|
+
if (mailTemplates.has(name)) {
|
|
400
|
+
throw new Error(`Mail template "${name}" already registered`);
|
|
401
|
+
}
|
|
402
|
+
mailTemplates.set(name, {
|
|
403
|
+
subject: template.subject,
|
|
404
|
+
html: typeof template.html === 'string' ? template.html : undefined,
|
|
405
|
+
text: typeof template.text === 'string' ? template.text : undefined,
|
|
406
|
+
});
|
|
407
|
+
},
|
|
408
|
+
async send(input) {
|
|
409
|
+
requirePermission(PERMISSIONS.MAIL_SEND);
|
|
410
|
+
if (!input || typeof input.to !== 'string' || !EMAIL_PATTERN.test(input.to)) {
|
|
411
|
+
throw new Error(`Invalid recipient email "${String(input?.to)}"`);
|
|
412
|
+
}
|
|
413
|
+
let subject = input.subject;
|
|
414
|
+
let html = input.html;
|
|
415
|
+
let text = input.text;
|
|
416
|
+
if (input.template !== undefined) {
|
|
417
|
+
const template = mailTemplates.get(input.template);
|
|
418
|
+
if (!template) {
|
|
419
|
+
throw new Error(`Mail template "${input.template}" is not registered`);
|
|
420
|
+
}
|
|
421
|
+
subject = renderMailString(template.subject, input.params);
|
|
422
|
+
html = template.html !== undefined ? renderMailString(template.html, input.params) : html;
|
|
423
|
+
text = template.text !== undefined ? renderMailString(template.text, input.params) : text;
|
|
424
|
+
}
|
|
425
|
+
if (typeof subject !== 'string' || subject.trim().length === 0) {
|
|
426
|
+
throw new Error('Mail "subject" is required (or register/use a template)');
|
|
427
|
+
}
|
|
428
|
+
if (!html && !text) {
|
|
429
|
+
throw new Error('Mail requires html or text body');
|
|
430
|
+
}
|
|
431
|
+
if (!deps.mail)
|
|
432
|
+
return false;
|
|
433
|
+
return deps.mail.send({
|
|
434
|
+
tenantId: getTenantId(),
|
|
435
|
+
pluginName: manifest.name,
|
|
436
|
+
to: input.to,
|
|
437
|
+
subject,
|
|
438
|
+
html,
|
|
439
|
+
text,
|
|
440
|
+
template: input.template,
|
|
441
|
+
params: input.params,
|
|
442
|
+
});
|
|
443
|
+
},
|
|
444
|
+
};
|
|
445
|
+
// ── api.content (Item 22): typed query builder — declarative filters
|
|
446
|
+
// over allowlisted columns, fully parameterized (db:query).
|
|
447
|
+
const contentApi = {
|
|
448
|
+
query() {
|
|
449
|
+
requirePermission('db:query');
|
|
450
|
+
return new ContentQueryBuilder((query, params) => db.query(query, params));
|
|
451
|
+
},
|
|
452
|
+
};
|
|
22
453
|
// Build the public API object
|
|
23
454
|
const api = {
|
|
24
455
|
registerHook(type, handler) {
|
|
@@ -74,7 +505,7 @@ export function createPluginApi(deps) {
|
|
|
74
505
|
};
|
|
75
506
|
const hookName = hookMap[type];
|
|
76
507
|
if (!hookName) {
|
|
77
|
-
console.warn(`[PluginAPI] Hook type "${type}"
|
|
508
|
+
console.warn(`[PluginAPI] Hook type "${type}" not mapped — ignored`);
|
|
78
509
|
return;
|
|
79
510
|
}
|
|
80
511
|
hooks.addAction(hookName, async (...args) => {
|
|
@@ -91,10 +522,124 @@ export function createPluginApi(deps) {
|
|
|
91
522
|
}
|
|
92
523
|
}
|
|
93
524
|
registeredRoutes.push(route);
|
|
525
|
+
auditPlugin('plugin.route.register', 'route', {
|
|
526
|
+
method: route.method,
|
|
527
|
+
path: route.path,
|
|
528
|
+
});
|
|
94
529
|
},
|
|
95
530
|
addContentField(field) {
|
|
96
531
|
registeredFields.push(field);
|
|
97
532
|
},
|
|
533
|
+
addFieldType(definition) {
|
|
534
|
+
if (!allowedPermissions.includes('field-types:register')) {
|
|
535
|
+
throw new Error('Plugin permission denied: field-types:register');
|
|
536
|
+
}
|
|
537
|
+
// Validate whether the type already exists (built-in or another plugin)
|
|
538
|
+
const existing = registeredFieldTypes.find(f => f.type === definition.type);
|
|
539
|
+
if (existing) {
|
|
540
|
+
throw new Error(`Field type "${definition.type}" already registered by plugin "${existing.pluginName || 'unknown'}"`);
|
|
541
|
+
}
|
|
542
|
+
const withPluginName = {
|
|
543
|
+
...definition,
|
|
544
|
+
pluginName: manifest.name,
|
|
545
|
+
isPluginType: true,
|
|
546
|
+
};
|
|
547
|
+
registeredFieldTypes.push(withPluginName);
|
|
548
|
+
registerFieldTypes(registeredFieldTypes);
|
|
549
|
+
},
|
|
550
|
+
addPostType(declaration) {
|
|
551
|
+
if (!allowedPermissions.includes(PERMISSIONS.POST_TYPES_REGISTER)) {
|
|
552
|
+
throw new Error(`Plugin permission denied: ${PERMISSIONS.POST_TYPES_REGISTER}`);
|
|
553
|
+
}
|
|
554
|
+
if (!SLUG_PATTERN.test(declaration.key)) {
|
|
555
|
+
throw new Error(`Invalid post type key "${declaration.key}" (expected ${SLUG_PATTERN})`);
|
|
556
|
+
}
|
|
557
|
+
if (RESERVED_SLUGS.has(declaration.key)) {
|
|
558
|
+
throw new Error(`Post type key "${declaration.key}" is reserved`);
|
|
559
|
+
}
|
|
560
|
+
if (!declaration.label || typeof declaration.label !== 'string') {
|
|
561
|
+
throw new Error('Post type "label" is required');
|
|
562
|
+
}
|
|
563
|
+
const existing = registeredPostTypes.find((type) => type.key === declaration.key);
|
|
564
|
+
if (existing) {
|
|
565
|
+
throw new Error(`Post type "${declaration.key}" already registered by plugin "${existing.pluginName || 'unknown'}"`);
|
|
566
|
+
}
|
|
567
|
+
const withPluginName = { ...declaration, pluginName: manifest.name };
|
|
568
|
+
registeredPostTypes.push(withPluginName);
|
|
569
|
+
registerPostTypes?.([...registeredPostTypes]);
|
|
570
|
+
},
|
|
571
|
+
addTaxonomy(declaration) {
|
|
572
|
+
if (!allowedPermissions.includes(PERMISSIONS.TAXONOMIES_REGISTER)) {
|
|
573
|
+
throw new Error(`Plugin permission denied: ${PERMISSIONS.TAXONOMIES_REGISTER}`);
|
|
574
|
+
}
|
|
575
|
+
if (!SLUG_PATTERN.test(declaration.key)) {
|
|
576
|
+
throw new Error(`Invalid taxonomy key "${declaration.key}" (expected ${SLUG_PATTERN})`);
|
|
577
|
+
}
|
|
578
|
+
if (RESERVED_SLUGS.has(declaration.key)) {
|
|
579
|
+
throw new Error(`Taxonomy key "${declaration.key}" is reserved`);
|
|
580
|
+
}
|
|
581
|
+
if (!declaration.label || typeof declaration.label !== 'string') {
|
|
582
|
+
throw new Error('Taxonomy "label" is required');
|
|
583
|
+
}
|
|
584
|
+
if (!Array.isArray(declaration.postTypes) || declaration.postTypes.length === 0) {
|
|
585
|
+
throw new Error(`Taxonomy "${declaration.key}" requires a non-empty postTypes[]`);
|
|
586
|
+
}
|
|
587
|
+
const existing = registeredTaxonomies.find((taxonomy) => taxonomy.key === declaration.key);
|
|
588
|
+
if (existing) {
|
|
589
|
+
throw new Error(`Taxonomy "${declaration.key}" already registered by plugin "${existing.pluginName || 'unknown'}"`);
|
|
590
|
+
}
|
|
591
|
+
const withPluginName = { ...declaration, pluginName: manifest.name };
|
|
592
|
+
registeredTaxonomies.push(withPluginName);
|
|
593
|
+
registerTaxonomies?.([...registeredTaxonomies]);
|
|
594
|
+
},
|
|
595
|
+
registerBlock(block) {
|
|
596
|
+
if (!allowedPermissions.includes(PERMISSIONS.BLOCKS_REGISTER)) {
|
|
597
|
+
throw new Error(`Plugin permission denied: ${PERMISSIONS.BLOCKS_REGISTER}`);
|
|
598
|
+
}
|
|
599
|
+
if (!block?.type || typeof block.type !== 'string') {
|
|
600
|
+
throw new Error('Block "type" is required');
|
|
601
|
+
}
|
|
602
|
+
if (!block.label || typeof block.label !== 'string') {
|
|
603
|
+
throw new Error('Block "label" is required');
|
|
604
|
+
}
|
|
605
|
+
if (!block.adminComponentPath || typeof block.adminComponentPath !== 'string') {
|
|
606
|
+
throw new Error('Block "adminComponentPath" is required');
|
|
607
|
+
}
|
|
608
|
+
const existing = registeredBlocks.find((registered) => registered.type === block.type);
|
|
609
|
+
if (existing) {
|
|
610
|
+
throw new Error(`Block type "${block.type}" already registered by plugin "${existing.pluginName || 'unknown'}"`);
|
|
611
|
+
}
|
|
612
|
+
registeredBlocks.push({ ...block, pluginName: manifest.name });
|
|
613
|
+
},
|
|
614
|
+
registerPattern(pattern) {
|
|
615
|
+
if (!allowedPermissions.includes(PERMISSIONS.PATTERNS_REGISTER)) {
|
|
616
|
+
throw new Error(`Plugin permission denied: ${PERMISSIONS.PATTERNS_REGISTER}`);
|
|
617
|
+
}
|
|
618
|
+
if (!pattern?.name || typeof pattern.name !== 'string') {
|
|
619
|
+
throw new Error('Pattern "name" is required');
|
|
620
|
+
}
|
|
621
|
+
if (!pattern.label || typeof pattern.label !== 'string') {
|
|
622
|
+
throw new Error('Pattern "label" is required');
|
|
623
|
+
}
|
|
624
|
+
if (!pattern.content || typeof pattern.content !== 'string') {
|
|
625
|
+
throw new Error('Pattern "content" is required');
|
|
626
|
+
}
|
|
627
|
+
const existing = registeredPatterns.find((registered) => registered.name === pattern.name);
|
|
628
|
+
if (existing) {
|
|
629
|
+
throw new Error(`Pattern name "${pattern.name}" already registered by plugin "${existing.pluginName || 'unknown'}"`);
|
|
630
|
+
}
|
|
631
|
+
registeredPatterns.push({ ...pattern, pluginName: manifest.name });
|
|
632
|
+
},
|
|
633
|
+
cache: cacheApi,
|
|
634
|
+
jobs: jobsApi,
|
|
635
|
+
http: httpApi,
|
|
636
|
+
admin: adminApi,
|
|
637
|
+
roles: rolesApi,
|
|
638
|
+
assets: assetsApi,
|
|
639
|
+
i18n: i18nApi,
|
|
640
|
+
health: healthApi,
|
|
641
|
+
mail: mailApi,
|
|
642
|
+
content: contentApi,
|
|
98
643
|
async getSetting(key, group) {
|
|
99
644
|
if (!allowedPermissions.includes('settings:read')) {
|
|
100
645
|
throw new Error('Plugin permission denied: settings:read');
|
|
@@ -129,6 +674,14 @@ export function createPluginApi(deps) {
|
|
|
129
674
|
},
|
|
130
675
|
async query(sql, params) {
|
|
131
676
|
checkDbPermission();
|
|
677
|
+
// Item 18: only MUTATIONS hit the audit log (mass SELECTs would flood
|
|
678
|
+
// it; reads stay in the database query logs).
|
|
679
|
+
if (MUTATION_PATTERN.test(sql)) {
|
|
680
|
+
auditPlugin('plugin.db.query', 'db', {
|
|
681
|
+
statement: sql.slice(0, 500),
|
|
682
|
+
paramsCount: Array.isArray(params) ? params.length : 0,
|
|
683
|
+
});
|
|
684
|
+
}
|
|
132
685
|
return db.query(sql, params);
|
|
133
686
|
},
|
|
134
687
|
async emit(eventType, payload) {
|
|
@@ -168,6 +721,15 @@ export function createPluginApi(deps) {
|
|
|
168
721
|
internal: {
|
|
169
722
|
getRoutes: () => registeredRoutes,
|
|
170
723
|
getFields: () => registeredFields,
|
|
724
|
+
getFieldTypes: () => registeredFieldTypes,
|
|
725
|
+
getPostTypes: () => registeredPostTypes,
|
|
726
|
+
getTaxonomies: () => registeredTaxonomies,
|
|
727
|
+
getJobHandlers: () => jobHandlers,
|
|
728
|
+
getAdminState: () => adminApi.state,
|
|
729
|
+
getBlocks: () => registeredBlocks,
|
|
730
|
+
getPatterns: () => registeredPatterns,
|
|
731
|
+
getEnqueuedAssets: () => registeredAssets,
|
|
732
|
+
getHealthChecks: () => [...healthChecks.entries()].map(([name, check]) => ({ name, check })),
|
|
171
733
|
},
|
|
172
734
|
};
|
|
173
735
|
}
|