@emdash-cms/cloudflare 0.0.1
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/auth/index.d.mts +81 -0
- package/dist/auth/index.mjs +147 -0
- package/dist/cache/config.d.mts +52 -0
- package/dist/cache/config.mjs +55 -0
- package/dist/cache/runtime.d.mts +40 -0
- package/dist/cache/runtime.mjs +191 -0
- package/dist/d1-introspector-bZf0_ylK.mjs +57 -0
- package/dist/db/d1.d.mts +43 -0
- package/dist/db/d1.mjs +74 -0
- package/dist/db/do.d.mts +96 -0
- package/dist/db/do.mjs +489 -0
- package/dist/db/playground-middleware.d.mts +20 -0
- package/dist/db/playground-middleware.mjs +533 -0
- package/dist/db/playground.d.mts +39 -0
- package/dist/db/playground.mjs +26 -0
- package/dist/do-class-DY2Ba2RJ.mjs +174 -0
- package/dist/do-class-x5Xh_G62.d.mts +73 -0
- package/dist/do-dialect-BhFcRSFQ.mjs +58 -0
- package/dist/do-playground-routes-CmwFeGwJ.mjs +49 -0
- package/dist/do-types-CY0G0oyh.d.mts +14 -0
- package/dist/images-4RT9Ag8_.d.mts +76 -0
- package/dist/index.d.mts +200 -0
- package/dist/index.mjs +214 -0
- package/dist/media/images-runtime.d.mts +10 -0
- package/dist/media/images-runtime.mjs +215 -0
- package/dist/media/stream-runtime.d.mts +10 -0
- package/dist/media/stream-runtime.mjs +218 -0
- package/dist/plugins/index.d.mts +32 -0
- package/dist/plugins/index.mjs +163 -0
- package/dist/sandbox/index.d.mts +255 -0
- package/dist/sandbox/index.mjs +945 -0
- package/dist/storage/r2.d.mts +31 -0
- package/dist/storage/r2.mjs +116 -0
- package/dist/stream-DdbcvKi0.d.mts +78 -0
- package/package.json +109 -0
- package/src/auth/cloudflare-access.ts +303 -0
- package/src/auth/index.ts +16 -0
- package/src/cache/config.ts +81 -0
- package/src/cache/runtime.ts +328 -0
- package/src/cloudflare.d.ts +31 -0
- package/src/db/d1-introspector.ts +120 -0
- package/src/db/d1.ts +112 -0
- package/src/db/do-class.ts +275 -0
- package/src/db/do-dialect.ts +125 -0
- package/src/db/do-playground-routes.ts +65 -0
- package/src/db/do-preview-routes.ts +48 -0
- package/src/db/do-preview-sign.ts +100 -0
- package/src/db/do-preview.ts +268 -0
- package/src/db/do-types.ts +12 -0
- package/src/db/do.ts +62 -0
- package/src/db/playground-middleware.ts +340 -0
- package/src/db/playground-toolbar.ts +341 -0
- package/src/db/playground.ts +49 -0
- package/src/db/preview-toolbar.ts +220 -0
- package/src/index.ts +285 -0
- package/src/media/images-runtime.ts +353 -0
- package/src/media/images.ts +114 -0
- package/src/media/stream-runtime.ts +392 -0
- package/src/media/stream.ts +118 -0
- package/src/plugins/index.ts +7 -0
- package/src/plugins/vectorize-search.ts +393 -0
- package/src/sandbox/bridge.ts +1008 -0
- package/src/sandbox/index.ts +13 -0
- package/src/sandbox/runner.ts +357 -0
- package/src/sandbox/types.ts +181 -0
- package/src/sandbox/wrapper.ts +238 -0
- package/src/storage/r2.ts +200 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { extractPlainText } from "emdash";
|
|
2
|
+
|
|
3
|
+
//#region src/plugins/vectorize-search.ts
|
|
4
|
+
/** Safely extract a string from an unknown value */
|
|
5
|
+
function toString(value) {
|
|
6
|
+
return typeof value === "string" ? value : "";
|
|
7
|
+
}
|
|
8
|
+
/** Type guard: check if value is a record-like object */
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Get Cloudflare runtime environment from request
|
|
14
|
+
*/
|
|
15
|
+
function getCloudflareEnv(request) {
|
|
16
|
+
const locals = request[Symbol.for("astro.locals")];
|
|
17
|
+
if (locals?.runtime?.env) return locals.runtime.env;
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Extract searchable text from content entry
|
|
22
|
+
*/
|
|
23
|
+
function extractSearchableText(content) {
|
|
24
|
+
const parts = [];
|
|
25
|
+
if (typeof content.title === "string") parts.push(content.title);
|
|
26
|
+
for (const [key, value] of Object.entries(content)) {
|
|
27
|
+
if (key === "title" || key === "id" || key === "slug") continue;
|
|
28
|
+
if (typeof value === "string") {
|
|
29
|
+
const text = extractPlainText(value);
|
|
30
|
+
if (text) parts.push(text);
|
|
31
|
+
} else if (Array.isArray(value)) {
|
|
32
|
+
const text = extractPlainText(value);
|
|
33
|
+
if (text) parts.push(text);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return parts.join("\n");
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Create a Vectorize Search plugin definition
|
|
40
|
+
*
|
|
41
|
+
* Note: This returns a plain plugin definition object, not a resolved plugin.
|
|
42
|
+
* It should be passed to the emdash() integration's plugins array.
|
|
43
|
+
*/
|
|
44
|
+
function vectorizeSearch(config = {}) {
|
|
45
|
+
const model = config.model ?? "@cf/bge-base-en-v1.5";
|
|
46
|
+
const targetCollections = config.collections;
|
|
47
|
+
let cachedEnv = null;
|
|
48
|
+
return {
|
|
49
|
+
id: "vectorize-search",
|
|
50
|
+
version: "1.0.0",
|
|
51
|
+
capabilities: ["read:content"],
|
|
52
|
+
hooks: {
|
|
53
|
+
"content:afterSave": { handler: async (event, _ctx) => {
|
|
54
|
+
const { content, collection } = event;
|
|
55
|
+
if (targetCollections && !targetCollections.includes(collection)) return;
|
|
56
|
+
if (!cachedEnv) {
|
|
57
|
+
console.warn("[vectorize-search] Environment not available in hook. Call the /query route first to initialize, or reindex manually.");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const env = cachedEnv;
|
|
61
|
+
if (!env.AI || !env.VECTORIZE) {
|
|
62
|
+
console.warn("[vectorize-search] AI or VECTORIZE binding not available, skipping indexing");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const text = extractSearchableText(content);
|
|
67
|
+
if (!text.trim()) return;
|
|
68
|
+
const embedResult = await env.AI.run(model, { text: [text] });
|
|
69
|
+
if (!embedResult?.data?.[0]) {
|
|
70
|
+
console.error("[vectorize-search] Failed to generate embedding");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const contentId = toString(content.id);
|
|
74
|
+
const contentSlug = toString(content.slug);
|
|
75
|
+
const contentTitle = toString(content.title);
|
|
76
|
+
await env.VECTORIZE.upsert([{
|
|
77
|
+
id: contentId,
|
|
78
|
+
values: embedResult.data[0],
|
|
79
|
+
metadata: {
|
|
80
|
+
collection,
|
|
81
|
+
slug: contentSlug ?? "",
|
|
82
|
+
title: contentTitle ?? ""
|
|
83
|
+
}
|
|
84
|
+
}]);
|
|
85
|
+
console.log(`[vectorize-search] Indexed ${collection}/${contentId}`);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error("[vectorize-search] Error indexing content:", error);
|
|
88
|
+
}
|
|
89
|
+
} },
|
|
90
|
+
"content:afterDelete": { handler: async (event, _ctx) => {
|
|
91
|
+
const { id, collection } = event;
|
|
92
|
+
if (targetCollections && !targetCollections.includes(collection)) return;
|
|
93
|
+
if (!cachedEnv?.VECTORIZE) return;
|
|
94
|
+
try {
|
|
95
|
+
await cachedEnv.VECTORIZE.deleteByIds([id]);
|
|
96
|
+
console.log(`[vectorize-search] Removed ${collection}/${id} from index`);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error("[vectorize-search] Error removing from index:", error);
|
|
99
|
+
}
|
|
100
|
+
} }
|
|
101
|
+
},
|
|
102
|
+
routes: {
|
|
103
|
+
query: { handler: async (ctx) => {
|
|
104
|
+
const { request } = ctx;
|
|
105
|
+
const input = isRecord(ctx.input) ? ctx.input : void 0;
|
|
106
|
+
const env = getCloudflareEnv(request);
|
|
107
|
+
if (env) cachedEnv = env;
|
|
108
|
+
if (!env?.AI || !env?.VECTORIZE) return {
|
|
109
|
+
error: "Vectorize or AI binding not available",
|
|
110
|
+
results: []
|
|
111
|
+
};
|
|
112
|
+
const query = typeof input?.q === "string" ? input.q : void 0;
|
|
113
|
+
if (!query) return {
|
|
114
|
+
error: "Query parameter 'q' is required",
|
|
115
|
+
results: []
|
|
116
|
+
};
|
|
117
|
+
try {
|
|
118
|
+
const embedResult = await env.AI.run(model, { text: [query] });
|
|
119
|
+
if (!embedResult?.data?.[0]) return {
|
|
120
|
+
error: "Failed to generate query embedding",
|
|
121
|
+
results: []
|
|
122
|
+
};
|
|
123
|
+
const queryOptions = {
|
|
124
|
+
topK: typeof input?.limit === "number" ? input.limit : 20,
|
|
125
|
+
returnMetadata: "all"
|
|
126
|
+
};
|
|
127
|
+
const collection = typeof input?.collection === "string" ? input.collection : void 0;
|
|
128
|
+
if (collection) queryOptions.filter = { collection };
|
|
129
|
+
return { results: (await env.VECTORIZE.query(embedResult.data[0], queryOptions)).matches.map((match) => ({
|
|
130
|
+
id: match.id,
|
|
131
|
+
score: match.score,
|
|
132
|
+
collection: toString(match.metadata?.collection),
|
|
133
|
+
slug: toString(match.metadata?.slug),
|
|
134
|
+
title: toString(match.metadata?.title)
|
|
135
|
+
})) };
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error("[vectorize-search] Query error:", error);
|
|
138
|
+
return {
|
|
139
|
+
error: error instanceof Error ? error.message : "Query failed",
|
|
140
|
+
results: []
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
} },
|
|
144
|
+
reindex: { handler: async (ctx) => {
|
|
145
|
+
const { request } = ctx;
|
|
146
|
+
const env = getCloudflareEnv(request);
|
|
147
|
+
if (env) cachedEnv = env;
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
error: "REINDEX_NOT_SUPPORTED"
|
|
151
|
+
};
|
|
152
|
+
} }
|
|
153
|
+
},
|
|
154
|
+
admin: { pages: [{
|
|
155
|
+
path: "/settings",
|
|
156
|
+
label: "Vectorize Search",
|
|
157
|
+
icon: "search"
|
|
158
|
+
}] }
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
//#endregion
|
|
163
|
+
export { vectorizeSearch };
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { WorkerEntrypoint } from "cloudflare:workers";
|
|
2
|
+
import { PluginManifest, SandboxEmailSendCallback, SandboxOptions, SandboxRunner, SandboxRunnerFactory, SandboxedPlugin } from "emdash";
|
|
3
|
+
|
|
4
|
+
//#region src/sandbox/runner.d.ts
|
|
5
|
+
interface PluginBridgeProps {
|
|
6
|
+
pluginId: string;
|
|
7
|
+
pluginVersion: string;
|
|
8
|
+
capabilities: string[];
|
|
9
|
+
allowedHosts: string[];
|
|
10
|
+
storageCollections: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Cloudflare sandbox runner using Worker Loader.
|
|
14
|
+
*/
|
|
15
|
+
declare class CloudflareSandboxRunner implements SandboxRunner {
|
|
16
|
+
private plugins;
|
|
17
|
+
private options;
|
|
18
|
+
private resolvedLimits;
|
|
19
|
+
private siteInfo?;
|
|
20
|
+
constructor(options: SandboxOptions);
|
|
21
|
+
/**
|
|
22
|
+
* Set the email send callback for sandboxed plugins.
|
|
23
|
+
* Called after the EmailPipeline is created, since the pipeline
|
|
24
|
+
* doesn't exist when the sandbox runner is constructed.
|
|
25
|
+
*/
|
|
26
|
+
setEmailSend(callback: SandboxEmailSendCallback | null): void;
|
|
27
|
+
/**
|
|
28
|
+
* Check if Worker Loader is available.
|
|
29
|
+
*/
|
|
30
|
+
isAvailable(): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Load a sandboxed plugin.
|
|
33
|
+
*
|
|
34
|
+
* @param manifest - Plugin manifest with capabilities and storage declarations
|
|
35
|
+
* @param code - The bundled plugin JavaScript code
|
|
36
|
+
*/
|
|
37
|
+
load(manifest: PluginManifest, code: string): Promise<SandboxedPlugin>;
|
|
38
|
+
/**
|
|
39
|
+
* Terminate all loaded plugins.
|
|
40
|
+
*/
|
|
41
|
+
terminateAll(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Factory function for creating the Cloudflare sandbox runner.
|
|
45
|
+
*
|
|
46
|
+
* Matches the SandboxRunnerFactory signature. The LOADER and PluginBridge
|
|
47
|
+
* are obtained internally from cloudflare:workers imports.
|
|
48
|
+
*/
|
|
49
|
+
declare const createSandboxRunner: SandboxRunnerFactory;
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/sandbox/bridge.d.ts
|
|
52
|
+
/**
|
|
53
|
+
* Set the email send callback for all bridge instances.
|
|
54
|
+
* Called by the runner when the EmailPipeline is available.
|
|
55
|
+
*/
|
|
56
|
+
declare function setEmailSendCallback(callback: SandboxEmailSendCallback | null): void;
|
|
57
|
+
/**
|
|
58
|
+
* Environment bindings required by PluginBridge
|
|
59
|
+
*/
|
|
60
|
+
interface PluginBridgeEnv {
|
|
61
|
+
DB: D1Database;
|
|
62
|
+
MEDIA?: R2Bucket;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Props passed to the bridge via ctx.props when creating the loopback binding
|
|
66
|
+
*/
|
|
67
|
+
interface PluginBridgeProps$1 {
|
|
68
|
+
pluginId: string;
|
|
69
|
+
pluginVersion: string;
|
|
70
|
+
capabilities: string[];
|
|
71
|
+
allowedHosts: string[];
|
|
72
|
+
storageCollections: string[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* PluginBridge WorkerEntrypoint
|
|
76
|
+
*
|
|
77
|
+
* Provides the context API to sandboxed plugins via RPC.
|
|
78
|
+
* All methods validate capabilities and scope operations to the plugin.
|
|
79
|
+
*
|
|
80
|
+
* Usage:
|
|
81
|
+
* 1. Export this class from your worker entrypoint
|
|
82
|
+
* 2. Sandboxed plugins get a binding to it via ctx.exports.PluginBridge({...})
|
|
83
|
+
* 3. Plugins call bridge methods which validate and proxy to the database
|
|
84
|
+
*/
|
|
85
|
+
declare class PluginBridge extends WorkerEntrypoint<PluginBridgeEnv, PluginBridgeProps$1> {
|
|
86
|
+
/**
|
|
87
|
+
* KV operations use _plugin_storage with a special "__kv" collection.
|
|
88
|
+
* This provides consistent storage across sandboxed and non-sandboxed modes.
|
|
89
|
+
*/
|
|
90
|
+
kvGet(key: string): Promise<unknown>;
|
|
91
|
+
kvSet(key: string, value: unknown): Promise<void>;
|
|
92
|
+
kvDelete(key: string): Promise<boolean>;
|
|
93
|
+
kvList(prefix?: string): Promise<Array<{
|
|
94
|
+
key: string;
|
|
95
|
+
value: unknown;
|
|
96
|
+
}>>;
|
|
97
|
+
storageGet(collection: string, id: string): Promise<unknown>;
|
|
98
|
+
storagePut(collection: string, id: string, data: unknown): Promise<void>;
|
|
99
|
+
storageDelete(collection: string, id: string): Promise<boolean>;
|
|
100
|
+
storageQuery(collection: string, opts?: {
|
|
101
|
+
limit?: number;
|
|
102
|
+
cursor?: string;
|
|
103
|
+
}): Promise<{
|
|
104
|
+
items: Array<{
|
|
105
|
+
id: string;
|
|
106
|
+
data: unknown;
|
|
107
|
+
}>;
|
|
108
|
+
hasMore: boolean;
|
|
109
|
+
cursor?: string;
|
|
110
|
+
}>;
|
|
111
|
+
storageCount(collection: string): Promise<number>;
|
|
112
|
+
storageGetMany(collection: string, ids: string[]): Promise<Map<string, unknown>>;
|
|
113
|
+
storagePutMany(collection: string, items: Array<{
|
|
114
|
+
id: string;
|
|
115
|
+
data: unknown;
|
|
116
|
+
}>): Promise<void>;
|
|
117
|
+
storageDeleteMany(collection: string, ids: string[]): Promise<number>;
|
|
118
|
+
contentGet(collection: string, id: string): Promise<{
|
|
119
|
+
id: string;
|
|
120
|
+
type: string;
|
|
121
|
+
data: Record<string, unknown>;
|
|
122
|
+
createdAt: string;
|
|
123
|
+
updatedAt: string;
|
|
124
|
+
} | null>;
|
|
125
|
+
contentList(collection: string, opts?: {
|
|
126
|
+
limit?: number;
|
|
127
|
+
cursor?: string;
|
|
128
|
+
}): Promise<{
|
|
129
|
+
items: Array<{
|
|
130
|
+
id: string;
|
|
131
|
+
type: string;
|
|
132
|
+
data: Record<string, unknown>;
|
|
133
|
+
createdAt: string;
|
|
134
|
+
updatedAt: string;
|
|
135
|
+
}>;
|
|
136
|
+
cursor?: string;
|
|
137
|
+
hasMore: boolean;
|
|
138
|
+
}>;
|
|
139
|
+
contentCreate(collection: string, data: Record<string, unknown>): Promise<{
|
|
140
|
+
id: string;
|
|
141
|
+
type: string;
|
|
142
|
+
data: Record<string, unknown>;
|
|
143
|
+
createdAt: string;
|
|
144
|
+
updatedAt: string;
|
|
145
|
+
}>;
|
|
146
|
+
contentUpdate(collection: string, id: string, data: Record<string, unknown>): Promise<{
|
|
147
|
+
id: string;
|
|
148
|
+
type: string;
|
|
149
|
+
data: Record<string, unknown>;
|
|
150
|
+
createdAt: string;
|
|
151
|
+
updatedAt: string;
|
|
152
|
+
}>;
|
|
153
|
+
contentDelete(collection: string, id: string): Promise<boolean>;
|
|
154
|
+
mediaGet(id: string): Promise<{
|
|
155
|
+
id: string;
|
|
156
|
+
filename: string;
|
|
157
|
+
mimeType: string;
|
|
158
|
+
size: number | null;
|
|
159
|
+
url: string;
|
|
160
|
+
createdAt: string;
|
|
161
|
+
} | null>;
|
|
162
|
+
mediaList(opts?: {
|
|
163
|
+
limit?: number;
|
|
164
|
+
cursor?: string;
|
|
165
|
+
mimeType?: string;
|
|
166
|
+
}): Promise<{
|
|
167
|
+
items: Array<{
|
|
168
|
+
id: string;
|
|
169
|
+
filename: string;
|
|
170
|
+
mimeType: string;
|
|
171
|
+
size: number | null;
|
|
172
|
+
url: string;
|
|
173
|
+
createdAt: string;
|
|
174
|
+
}>;
|
|
175
|
+
cursor?: string;
|
|
176
|
+
hasMore: boolean;
|
|
177
|
+
}>;
|
|
178
|
+
/**
|
|
179
|
+
* Create a pending media record and write bytes directly to R2.
|
|
180
|
+
*
|
|
181
|
+
* Unlike the admin UI flow (presigned URL → client PUT → confirm), sandboxed
|
|
182
|
+
* plugins are network-isolated and can't make external requests. The bridge
|
|
183
|
+
* accepts the file bytes directly and writes them to storage.
|
|
184
|
+
*
|
|
185
|
+
* Returns the media ID, storage key, and confirm URL. The plugin should
|
|
186
|
+
* call the confirm endpoint after this to finalize the record.
|
|
187
|
+
*/
|
|
188
|
+
mediaUpload(filename: string, contentType: string, bytes: ArrayBuffer): Promise<{
|
|
189
|
+
mediaId: string;
|
|
190
|
+
storageKey: string;
|
|
191
|
+
url: string;
|
|
192
|
+
}>;
|
|
193
|
+
mediaDelete(id: string): Promise<boolean>;
|
|
194
|
+
httpFetch(url: string, init?: RequestInit): Promise<{
|
|
195
|
+
status: number;
|
|
196
|
+
headers: Record<string, string>;
|
|
197
|
+
text: string;
|
|
198
|
+
}>;
|
|
199
|
+
userGet(id: string): Promise<{
|
|
200
|
+
id: string;
|
|
201
|
+
email: string;
|
|
202
|
+
name: string | null;
|
|
203
|
+
role: number;
|
|
204
|
+
createdAt: string;
|
|
205
|
+
} | null>;
|
|
206
|
+
userGetByEmail(email: string): Promise<{
|
|
207
|
+
id: string;
|
|
208
|
+
email: string;
|
|
209
|
+
name: string | null;
|
|
210
|
+
role: number;
|
|
211
|
+
createdAt: string;
|
|
212
|
+
} | null>;
|
|
213
|
+
userList(opts?: {
|
|
214
|
+
role?: number;
|
|
215
|
+
limit?: number;
|
|
216
|
+
cursor?: string;
|
|
217
|
+
}): Promise<{
|
|
218
|
+
items: Array<{
|
|
219
|
+
id: string;
|
|
220
|
+
email: string;
|
|
221
|
+
name: string | null;
|
|
222
|
+
role: number;
|
|
223
|
+
createdAt: string;
|
|
224
|
+
}>;
|
|
225
|
+
nextCursor?: string;
|
|
226
|
+
}>;
|
|
227
|
+
emailSend(message: {
|
|
228
|
+
to: string;
|
|
229
|
+
subject: string;
|
|
230
|
+
text: string;
|
|
231
|
+
html?: string;
|
|
232
|
+
}): Promise<void>;
|
|
233
|
+
log(level: "debug" | "info" | "warn" | "error", msg: string, data?: unknown): void;
|
|
234
|
+
}
|
|
235
|
+
//#endregion
|
|
236
|
+
//#region src/sandbox/wrapper.d.ts
|
|
237
|
+
/**
|
|
238
|
+
* Options for wrapper generation
|
|
239
|
+
*
|
|
240
|
+
* **Known limitation:** `site` info is baked into the generated wrapper code
|
|
241
|
+
* at load time. If site settings change (e.g., admin updates site name/URL),
|
|
242
|
+
* sandboxed plugins will see stale values until the worker restarts.
|
|
243
|
+
* Trusted-mode plugins always read fresh values from the database.
|
|
244
|
+
*/
|
|
245
|
+
interface WrapperOptions {
|
|
246
|
+
/** Site info to inject into the context (no RPC needed) */
|
|
247
|
+
site?: {
|
|
248
|
+
name: string;
|
|
249
|
+
url: string;
|
|
250
|
+
locale: string;
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
declare function generatePluginWrapper(manifest: PluginManifest, options?: WrapperOptions): string;
|
|
254
|
+
//#endregion
|
|
255
|
+
export { CloudflareSandboxRunner, PluginBridge, type PluginBridgeEnv, type PluginBridgeProps, createSandboxRunner, generatePluginWrapper, setEmailSendCallback };
|