devflare 1.0.0-next.44 → 1.0.0-next.46
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/LLM.md +19 -14
- package/dist/_chunks/{build-MzQbenXE.js → build-B6vCBDdQ.js} +1 -1
- package/dist/_chunks/{build-artifacts-tD45Rwmx.js → build-artifacts-CaSTDXLE.js} +1 -1
- package/dist/_chunks/{cli-BiDDz-RY.js → cli-Ba6JBLOz.js} +3 -3
- package/dist/_chunks/{deploy-BmtstRbc.js → deploy-0w_38g8h.js} +1 -1
- package/dist/_chunks/{dev-wVHYCbZF.js → dev-DaREvpZf.js} +9 -3
- package/dist/_chunks/{routes-5OnFhKR2.js → routes-C4S8x2HU.js} +26 -2
- package/dist/_chunks/{vite-DLUELS0D.js → vite-D5dfVb6G.js} +1 -1
- package/dist/bridge/miniflare.d.ts +7 -0
- package/dist/bridge/miniflare.d.ts.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/dev-server/miniflare-bindings.d.ts +16 -0
- package/dist/dev-server/miniflare-bindings.d.ts.map +1 -1
- package/dist/dev-server/miniflare-dev-config.d.ts.map +1 -1
- package/dist/dev-server/miniflare-worker-config.d.ts +5 -1
- package/dist/dev-server/miniflare-worker-config.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/test/alarm.d.ts +55 -0
- package/dist/test/alarm.d.ts.map +1 -0
- package/dist/test/cf.d.ts +22 -0
- package/dist/test/cf.d.ts.map +1 -1
- package/dist/test/index.d.ts +3 -1
- package/dist/test/index.d.ts.map +1 -1
- package/dist/test/index.js +356 -51
- package/dist/test/offline-bindings.d.ts +47 -15
- package/dist/test/offline-bindings.d.ts.map +1 -1
- package/dist/test/simple-context-mfconfig.d.ts.map +1 -1
- package/dist/test/simple-context-multi-worker.d.ts.map +1 -1
- package/dist/test/utilities/analytics-engine.d.ts +22 -0
- package/dist/test/utilities/analytics-engine.d.ts.map +1 -0
- package/dist/test/utilities/env.d.ts +3 -0
- package/dist/test/utilities/env.d.ts.map +1 -1
- package/dist/test/utilities/vectorize.d.ts +38 -0
- package/dist/test/utilities/vectorize.d.ts.map +1 -0
- package/dist/test/utilities.d.ts +2 -0
- package/dist/test/utilities.d.ts.map +1 -1
- package/dist/vite/index.js +1 -1
- package/package.json +1 -1
package/dist/test/index.js
CHANGED
|
@@ -4,12 +4,12 @@ import { _ as normalizeSecretsStoreBinding, f as normalizeMediaBinding, h as nor
|
|
|
4
4
|
import { t as applyLocalDevVarsToConfig } from "../_chunks/local-dev-vars-CTSa-wvF.js";
|
|
5
5
|
import { g as isAuthenticated, p as getApiToken } from "../_chunks/api-TzdliH-6.js";
|
|
6
6
|
import { d as getPrimaryAccount, n as getEffectiveAccountId } from "../_chunks/preferences-BKp_7XJx.js";
|
|
7
|
-
import { A as createTailEvent, D as createFetchEvent, E as createEmailEvent, F as BridgeClient, M as createEnvProxy, O as createQueueEvent, P as setBindingHints, _ as runWithContext, k as createScheduledEvent, v as runWithEventContext } from "../_chunks/context-CX50Y2Kb.js";
|
|
7
|
+
import { A as createTailEvent, D as createFetchEvent, E as createEmailEvent, F as BridgeClient, M as createEnvProxy, O as createQueueEvent, P as setBindingHints, _ as runWithContext, k as createScheduledEvent, v as runWithEventContext, x as createDurableObjectAlarmEvent } from "../_chunks/context-CX50Y2Kb.js";
|
|
8
8
|
import { createLocalSendEmailBinding, wrapEnvSendEmailBindings } from "../utils/send-email.js";
|
|
9
9
|
import { n as __setTestContext, r as env, t as __clearTestContext } from "../_chunks/env-S0_nMVz1.js";
|
|
10
10
|
import { d as invokeFetchModule, m as resolveFetchHandler, r as matchFetchRoute, t as createRouteResolve } from "../_chunks/runtime-FvXvfuZ2.js";
|
|
11
11
|
import { i as extractBindingHints, n as createLocalWorkerLoaderBinding, r as disposeLocalWorkerLoaderBindings, t as createLocalHyperdrive } from "../_chunks/local-hyperdrive-CJ90j46O.js";
|
|
12
|
-
import { a as hasServiceBindings, i as hasCrossWorkerDOs,
|
|
12
|
+
import { T as buildTailConsumersConfig, a as hasServiceBindings, i as hasCrossWorkerDOs, m as buildHyperdrivesConfig, o as resolveDOBindings, r as clearBundleCache, s as resolveServiceBindings, t as discoverRoutes, u as buildAnalyticsEngineConfig } from "../_chunks/routes-C4S8x2HU.js";
|
|
13
13
|
import { i as findFiles, t as DEFAULT_DO_PATTERN } from "../_chunks/glob-CmQOvunB.js";
|
|
14
14
|
import { a as isRemoteModeActive, i as getRemoteModeStatus } from "../_chunks/remote-config-D39BY9ji.js";
|
|
15
15
|
import { t as bundleWorkflowEntrypointScript } from "../_chunks/local-workflow-entrypoints-Cq_WJajd.js";
|
|
@@ -1226,7 +1226,231 @@ function createMockArtifacts(options = {}) {
|
|
|
1226
1226
|
};
|
|
1227
1227
|
}
|
|
1228
1228
|
//#endregion
|
|
1229
|
+
//#region src/test/utilities/vectorize.ts
|
|
1230
|
+
function toNumberArray(values) {
|
|
1231
|
+
return Array.isArray(values) ? values.slice() : Array.from(values);
|
|
1232
|
+
}
|
|
1233
|
+
function cloneVector(vector) {
|
|
1234
|
+
return {
|
|
1235
|
+
id: vector.id,
|
|
1236
|
+
values: toNumberArray(vector.values),
|
|
1237
|
+
...vector.namespace !== void 0 && { namespace: vector.namespace },
|
|
1238
|
+
...vector.metadata !== void 0 && { metadata: structuredClone(vector.metadata) }
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Cosine similarity between two equal-length vectors. Returns 0 when either
|
|
1243
|
+
* vector has zero magnitude (undefined direction), matching a "no similarity"
|
|
1244
|
+
* result rather than throwing.
|
|
1245
|
+
*/
|
|
1246
|
+
function cosineSimilarity(a, b) {
|
|
1247
|
+
let dot = 0;
|
|
1248
|
+
let magA = 0;
|
|
1249
|
+
let magB = 0;
|
|
1250
|
+
const length = Math.min(a.length, b.length);
|
|
1251
|
+
for (let i = 0; i < length; i++) {
|
|
1252
|
+
dot += a[i] * b[i];
|
|
1253
|
+
magA += a[i] * a[i];
|
|
1254
|
+
magB += b[i] * b[i];
|
|
1255
|
+
}
|
|
1256
|
+
if (magA === 0 || magB === 0) return 0;
|
|
1257
|
+
return dot / (Math.sqrt(magA) * Math.sqrt(magB));
|
|
1258
|
+
}
|
|
1259
|
+
function readMetadataField(metadata, field) {
|
|
1260
|
+
return metadata?.[field];
|
|
1261
|
+
}
|
|
1262
|
+
function compareScalar(op, actual, expected) {
|
|
1263
|
+
switch (op) {
|
|
1264
|
+
case "$eq": return actual === expected;
|
|
1265
|
+
case "$ne": return actual !== expected;
|
|
1266
|
+
case "$lt": return typeof actual === "number" && typeof expected === "number" && actual < expected;
|
|
1267
|
+
case "$lte": return typeof actual === "number" && typeof expected === "number" && actual <= expected;
|
|
1268
|
+
case "$gt": return typeof actual === "number" && typeof expected === "number" && actual > expected;
|
|
1269
|
+
case "$gte": return typeof actual === "number" && typeof expected === "number" && actual >= expected;
|
|
1270
|
+
default: throw new Error(`Mock Vectorize query filter operator "${op}" is not supported. Supported operators: $eq, $ne, $lt, $lte, $gt, $gte, $in, $nin.`);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
function evaluateOperator(op, actual, expected) {
|
|
1274
|
+
if (op === "$in") return Array.isArray(expected) && expected.includes(actual);
|
|
1275
|
+
if (op === "$nin") return !Array.isArray(expected) || !expected.includes(actual);
|
|
1276
|
+
return compareScalar(op, actual, expected);
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Evaluates one field's condition: either a scalar `$eq` shorthand or an
|
|
1280
|
+
* operator object where every operator must hold.
|
|
1281
|
+
*/
|
|
1282
|
+
function matchesCondition(actual, condition) {
|
|
1283
|
+
if (condition === null || typeof condition !== "object" || Array.isArray(condition)) return actual === condition;
|
|
1284
|
+
return Object.entries(condition).every(([op, expected]) => evaluateOperator(op, actual, expected));
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Evaluates a Vectorize metadata filter against a stored vector's metadata,
|
|
1288
|
+
* mirroring the documented filter grammar (scalar equality shorthand plus the
|
|
1289
|
+
* `$eq/$ne/$lt/$lte/$gt/$gte` and `$in/$nin` operators). All fields must match.
|
|
1290
|
+
*/
|
|
1291
|
+
function matchesFilter(metadata, filter) {
|
|
1292
|
+
return Object.entries(filter).every(([field, condition]) => matchesCondition(readMetadataField(metadata, field), condition));
|
|
1293
|
+
}
|
|
1294
|
+
function applyMetadataRetrieval(metadata, returnMetadata) {
|
|
1295
|
+
if (metadata === void 0) return;
|
|
1296
|
+
if (returnMetadata === void 0 || returnMetadata === false || returnMetadata === "none") return;
|
|
1297
|
+
return structuredClone(metadata);
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Creates a deterministic in-memory Vectorize index binding for pure unit tests.
|
|
1301
|
+
*
|
|
1302
|
+
* @example
|
|
1303
|
+
* ```ts
|
|
1304
|
+
* const index = createMockVectorize({
|
|
1305
|
+
* vectors: [{ id: 'a', values: [1, 0], metadata: { topic: 'cats' } }]
|
|
1306
|
+
* })
|
|
1307
|
+
* await index.insert([{ id: 'b', values: [0, 1] }])
|
|
1308
|
+
* const result = await index.query([1, 0], { topK: 1, returnMetadata: true })
|
|
1309
|
+
* // result.matches[0].id === 'a'
|
|
1310
|
+
* ```
|
|
1311
|
+
*/
|
|
1312
|
+
function createMockVectorize(options = {}) {
|
|
1313
|
+
const store = /* @__PURE__ */ new Map();
|
|
1314
|
+
let inferredDimensions = options.dimensions;
|
|
1315
|
+
const noteDimensions = (vector) => {
|
|
1316
|
+
if (inferredDimensions === void 0) inferredDimensions = toNumberArray(vector.values).length;
|
|
1317
|
+
};
|
|
1318
|
+
for (const vector of options.vectors ?? []) {
|
|
1319
|
+
const stored = cloneVector(vector);
|
|
1320
|
+
noteDimensions(stored);
|
|
1321
|
+
store.set(stored.id, stored);
|
|
1322
|
+
}
|
|
1323
|
+
const insert = async (vectors, { overwrite }) => {
|
|
1324
|
+
const ids = [];
|
|
1325
|
+
for (const vector of vectors) {
|
|
1326
|
+
if (!overwrite && store.has(vector.id)) throw new Error(`Mock Vectorize insert failed: a vector with id "${vector.id}" already exists. Use upsert() to replace it.`);
|
|
1327
|
+
const stored = cloneVector(vector);
|
|
1328
|
+
noteDimensions(stored);
|
|
1329
|
+
store.set(stored.id, stored);
|
|
1330
|
+
ids.push(stored.id);
|
|
1331
|
+
}
|
|
1332
|
+
return {
|
|
1333
|
+
ids,
|
|
1334
|
+
count: ids.length
|
|
1335
|
+
};
|
|
1336
|
+
};
|
|
1337
|
+
const deleteByIds = async (ids) => {
|
|
1338
|
+
const deleted = [];
|
|
1339
|
+
for (const id of ids) if (store.delete(id)) deleted.push(id);
|
|
1340
|
+
return {
|
|
1341
|
+
ids: deleted,
|
|
1342
|
+
count: deleted.length
|
|
1343
|
+
};
|
|
1344
|
+
};
|
|
1345
|
+
return {
|
|
1346
|
+
async describe() {
|
|
1347
|
+
return {
|
|
1348
|
+
id: `mock-${options.name ?? "mock-vectorize"}`,
|
|
1349
|
+
name: options.name ?? "mock-vectorize",
|
|
1350
|
+
config: {
|
|
1351
|
+
dimensions: inferredDimensions ?? 0,
|
|
1352
|
+
metric: options.metric ?? "cosine"
|
|
1353
|
+
},
|
|
1354
|
+
vectorsCount: store.size
|
|
1355
|
+
};
|
|
1356
|
+
},
|
|
1357
|
+
async insert(vectors) {
|
|
1358
|
+
return insert(vectors, { overwrite: false });
|
|
1359
|
+
},
|
|
1360
|
+
async upsert(vectors) {
|
|
1361
|
+
return insert(vectors, { overwrite: true });
|
|
1362
|
+
},
|
|
1363
|
+
async deleteByIds(ids) {
|
|
1364
|
+
return deleteByIds(ids);
|
|
1365
|
+
},
|
|
1366
|
+
async delete(ids) {
|
|
1367
|
+
return deleteByIds(ids);
|
|
1368
|
+
},
|
|
1369
|
+
async getByIds(ids) {
|
|
1370
|
+
const result = [];
|
|
1371
|
+
for (const id of ids) {
|
|
1372
|
+
const vector = store.get(id);
|
|
1373
|
+
if (vector) result.push(cloneVector(vector));
|
|
1374
|
+
}
|
|
1375
|
+
return result;
|
|
1376
|
+
},
|
|
1377
|
+
async query(vector, queryOptions) {
|
|
1378
|
+
const queryVector = toNumberArray(vector);
|
|
1379
|
+
const topK = queryOptions?.topK ?? 5;
|
|
1380
|
+
const namespace = queryOptions?.namespace;
|
|
1381
|
+
const filter = queryOptions?.filter;
|
|
1382
|
+
const returnValues = queryOptions?.returnValues ?? false;
|
|
1383
|
+
const returnMetadata = queryOptions?.returnMetadata;
|
|
1384
|
+
const matches = Array.from(store.values()).filter((candidate) => namespace === void 0 || candidate.namespace === namespace).filter((candidate) => filter === void 0 || matchesFilter(candidate.metadata, filter)).map((candidate) => ({
|
|
1385
|
+
candidate,
|
|
1386
|
+
score: cosineSimilarity(queryVector, toNumberArray(candidate.values))
|
|
1387
|
+
})).sort((a, b) => b.score - a.score || a.candidate.id.localeCompare(b.candidate.id)).slice(0, topK).map(({ candidate, score }) => {
|
|
1388
|
+
const match = {
|
|
1389
|
+
id: candidate.id,
|
|
1390
|
+
score,
|
|
1391
|
+
...candidate.namespace !== void 0 && { namespace: candidate.namespace }
|
|
1392
|
+
};
|
|
1393
|
+
if (returnValues) match.values = toNumberArray(candidate.values);
|
|
1394
|
+
const metadata = applyMetadataRetrieval(candidate.metadata, returnMetadata);
|
|
1395
|
+
if (metadata !== void 0) match.metadata = metadata;
|
|
1396
|
+
return match;
|
|
1397
|
+
});
|
|
1398
|
+
return {
|
|
1399
|
+
matches,
|
|
1400
|
+
count: matches.length
|
|
1401
|
+
};
|
|
1402
|
+
},
|
|
1403
|
+
_getVectors() {
|
|
1404
|
+
return Array.from(store.values()).map(cloneVector);
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
//#endregion
|
|
1409
|
+
//#region src/test/utilities/analytics-engine.ts
|
|
1410
|
+
/**
|
|
1411
|
+
* Creates a write-only Analytics Engine dataset binding for pure unit tests.
|
|
1412
|
+
*
|
|
1413
|
+
* @example
|
|
1414
|
+
* ```ts
|
|
1415
|
+
* const dataset = createMockAnalyticsEngine()
|
|
1416
|
+
* dataset.writeDataPoint({ indexes: ['user-1'], doubles: [1], blobs: ['signup'] })
|
|
1417
|
+
* expect(dataset.writtenDataPoints).toEqual([
|
|
1418
|
+
* { indexes: ['user-1'], doubles: [1], blobs: ['signup'] }
|
|
1419
|
+
* ])
|
|
1420
|
+
* ```
|
|
1421
|
+
*/
|
|
1422
|
+
function createMockAnalyticsEngine() {
|
|
1423
|
+
const dataPoints = [];
|
|
1424
|
+
return {
|
|
1425
|
+
writeDataPoint(event) {
|
|
1426
|
+
dataPoints.push(event ? structuredClone(event) : {});
|
|
1427
|
+
},
|
|
1428
|
+
get writtenDataPoints() {
|
|
1429
|
+
return dataPoints;
|
|
1430
|
+
},
|
|
1431
|
+
get points() {
|
|
1432
|
+
return dataPoints;
|
|
1433
|
+
},
|
|
1434
|
+
clear() {
|
|
1435
|
+
dataPoints.length = 0;
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
//#endregion
|
|
1229
1440
|
//#region src/test/utilities/env.ts
|
|
1441
|
+
function isVectorizeBinding$1(value) {
|
|
1442
|
+
return typeof value.query === "function";
|
|
1443
|
+
}
|
|
1444
|
+
function addVectorizeMockBindings(env, vectorize) {
|
|
1445
|
+
if (Array.isArray(vectorize)) {
|
|
1446
|
+
for (const name of vectorize) env[name] = createMockVectorize();
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
if (vectorize) for (const [name, value] of Object.entries(vectorize)) env[name] = isVectorizeBinding$1(value) ? value : createMockVectorize(value);
|
|
1450
|
+
}
|
|
1451
|
+
function addAnalyticsEngineMockBindings(env, analyticsEngine) {
|
|
1452
|
+
for (const name of analyticsEngine ?? []) env[name] = createMockAnalyticsEngine();
|
|
1453
|
+
}
|
|
1230
1454
|
/**
|
|
1231
1455
|
* Creates a complete mock environment with specified bindings
|
|
1232
1456
|
*
|
|
@@ -1264,6 +1488,8 @@ function createMockEnv(options = {}) {
|
|
|
1264
1488
|
else if (options.media) env.MEDIA = options.media;
|
|
1265
1489
|
if (Array.isArray(options.artifacts)) for (const name of options.artifacts) env[name] = createMockArtifacts();
|
|
1266
1490
|
else if (options.artifacts) for (const [name, artifactsOptions] of Object.entries(options.artifacts)) env[name] = isArtifactsBinding$1(artifactsOptions) ? artifactsOptions : createMockArtifacts(artifactsOptions);
|
|
1491
|
+
addVectorizeMockBindings(env, options.vectorize);
|
|
1492
|
+
addAnalyticsEngineMockBindings(env, options.analyticsEngine);
|
|
1267
1493
|
if (options.secretsStore) for (const [name, value] of Object.entries(options.secretsStore)) env[name] = createMockSecretsStoreSecret(value);
|
|
1268
1494
|
if (options.vars) Object.assign(env, options.vars);
|
|
1269
1495
|
if (options.secrets) Object.assign(env, options.secrets);
|
|
@@ -2054,7 +2280,7 @@ function createMessageBatch(messages) {
|
|
|
2054
2280
|
* ])
|
|
2055
2281
|
* ```
|
|
2056
2282
|
*/
|
|
2057
|
-
async function trigger$
|
|
2283
|
+
async function trigger$3(messages) {
|
|
2058
2284
|
if (!queueHandlerPath) throw new Error("Queue handler not configured. Make sure your devflare.config.ts has files.queue set, and the file exists at the specified path (default: src/queue.ts)");
|
|
2059
2285
|
if (!configDir$3 || !testEnvGetter$3) throw new Error("Queue helper not initialized. Call createTestContext() before using cf.queue.trigger()");
|
|
2060
2286
|
const handlerModule = await import(join$1(configDir$3, queueHandlerPath));
|
|
@@ -2108,10 +2334,10 @@ async function trigger$2(messages) {
|
|
|
2108
2334
|
* ```
|
|
2109
2335
|
*/
|
|
2110
2336
|
async function send(message) {
|
|
2111
|
-
return trigger$
|
|
2337
|
+
return trigger$3([message]);
|
|
2112
2338
|
}
|
|
2113
2339
|
const queue = {
|
|
2114
|
-
trigger: trigger$
|
|
2340
|
+
trigger: trigger$3,
|
|
2115
2341
|
send
|
|
2116
2342
|
};
|
|
2117
2343
|
//#endregion
|
|
@@ -2159,7 +2385,7 @@ function resetScheduledState() {
|
|
|
2159
2385
|
* // Trigger without cron (just scheduled time)
|
|
2160
2386
|
* await cf.scheduled.trigger()
|
|
2161
2387
|
*/
|
|
2162
|
-
async function trigger$
|
|
2388
|
+
async function trigger$2(cronOrOptions) {
|
|
2163
2389
|
if (!scheduledHandlerPath) throw new Error("Scheduled handler not configured. Make sure your devflare.config.ts has files.scheduled set, and the file exists at the specified path (default: src/scheduled.ts)");
|
|
2164
2390
|
if (!configDir$2 || !testEnvGetter$2) throw new Error("Scheduled helper not initialized. Call createTestContext() before using cf.scheduled.trigger()");
|
|
2165
2391
|
const options = typeof cronOrOptions === "string" ? { cron: cronOrOptions } : cronOrOptions ?? {};
|
|
@@ -2198,7 +2424,7 @@ async function trigger$1(cronOrOptions) {
|
|
|
2198
2424
|
};
|
|
2199
2425
|
}
|
|
2200
2426
|
}
|
|
2201
|
-
const scheduled = { trigger: trigger$
|
|
2427
|
+
const scheduled = { trigger: trigger$2 };
|
|
2202
2428
|
//#endregion
|
|
2203
2429
|
//#region src/test/tail.ts
|
|
2204
2430
|
let tailHandlerPath = null;
|
|
@@ -2267,7 +2493,7 @@ function createTraceItem(options) {
|
|
|
2267
2493
|
* ])
|
|
2268
2494
|
* ```
|
|
2269
2495
|
*/
|
|
2270
|
-
async function trigger(items) {
|
|
2496
|
+
async function trigger$1(items) {
|
|
2271
2497
|
if (!tailHandlerPath) throw new Error("Tail handler not configured. Add a src/tail.ts file exporting tail(), or configure a tail handler before calling cf.tail.trigger().");
|
|
2272
2498
|
if (!configDir$1 || !testEnvGetter$1) throw new Error("Tail helper not initialized. Call createTestContext() before using cf.tail.trigger()");
|
|
2273
2499
|
const traceItems = items.map((item) => {
|
|
@@ -2314,7 +2540,7 @@ function create(options = {}) {
|
|
|
2314
2540
|
return createTraceItem(options);
|
|
2315
2541
|
}
|
|
2316
2542
|
const tail = {
|
|
2317
|
-
trigger,
|
|
2543
|
+
trigger: trigger$1,
|
|
2318
2544
|
create
|
|
2319
2545
|
};
|
|
2320
2546
|
//#endregion
|
|
@@ -3090,7 +3316,11 @@ function buildInlineBridgeMfConfig(config, options = {}) {
|
|
|
3090
3316
|
if (config.bindings?.versionMetadata) mfConfig.versionMetadata = config.bindings.versionMetadata.binding;
|
|
3091
3317
|
if (config.bindings?.workerLoaders) mfConfig.workerLoaders = Object.fromEntries(Object.keys(config.bindings.workerLoaders).map((bindingName) => [bindingName, {}]));
|
|
3092
3318
|
if (config.bindings?.mtlsCertificates) mfConfig.mtlsCertificates = Object.fromEntries(Object.entries(config.bindings.mtlsCertificates).map(([bindingName, binding]) => {
|
|
3093
|
-
|
|
3319
|
+
const normalized = normalizeMtlsCertificateBinding(binding);
|
|
3320
|
+
return [bindingName, {
|
|
3321
|
+
certificate_id: normalized.certificateId,
|
|
3322
|
+
...normalized.remote !== void 0 && { remote: normalized.remote }
|
|
3323
|
+
}];
|
|
3094
3324
|
}));
|
|
3095
3325
|
if (config.bindings?.dispatchNamespaces) mfConfig.dispatchNamespaces = Object.fromEntries(Object.entries(config.bindings.dispatchNamespaces).map(([bindingName, binding]) => {
|
|
3096
3326
|
return [bindingName, { namespace: normalizeDispatchNamespaceBinding(binding).namespace }];
|
|
@@ -3122,6 +3352,10 @@ function buildInlineBridgeMfConfig(config, options = {}) {
|
|
|
3122
3352
|
mfConfig.media = { binding: normalizeMediaBinding(bindingName, binding).binding };
|
|
3123
3353
|
}
|
|
3124
3354
|
}
|
|
3355
|
+
const analyticsEngineConfig = buildAnalyticsEngineConfig(config.bindings ?? {});
|
|
3356
|
+
if (analyticsEngineConfig) mfConfig.analyticsEngineDatasets = analyticsEngineConfig;
|
|
3357
|
+
const tailConsumersConfig = buildTailConsumersConfig(config);
|
|
3358
|
+
if (tailConsumersConfig) mfConfig.tails = tailConsumersConfig;
|
|
3125
3359
|
if (config.bindings?.artifacts) mfConfig.artifacts = Object.fromEntries(Object.entries(config.bindings.artifacts).map(([bindingName, binding]) => {
|
|
3126
3360
|
return [bindingName, { namespace: normalizeArtifactsBinding(binding).namespace }];
|
|
3127
3361
|
}));
|
|
@@ -3188,6 +3422,8 @@ function applyMultiWorkerConfig(mfConfig, config, serviceBindingResolution, doBi
|
|
|
3188
3422
|
...mfConfig.pipelines && { pipelines: mfConfig.pipelines },
|
|
3189
3423
|
...mfConfig.images && { images: mfConfig.images },
|
|
3190
3424
|
...mfConfig.media && { media: mfConfig.media },
|
|
3425
|
+
...mfConfig.analyticsEngineDatasets && { analyticsEngineDatasets: mfConfig.analyticsEngineDatasets },
|
|
3426
|
+
...mfConfig.tails && { tails: mfConfig.tails },
|
|
3191
3427
|
...mfConfig.artifacts && { artifacts: mfConfig.artifacts },
|
|
3192
3428
|
...mfConfig.secretsStoreSecrets && { secretsStoreSecrets: mfConfig.secretsStoreSecrets },
|
|
3193
3429
|
...mfConfig.wrappedBindings && { wrappedBindings: mfConfig.wrappedBindings },
|
|
@@ -3231,6 +3467,8 @@ function applyMultiWorkerConfig(mfConfig, config, serviceBindingResolution, doBi
|
|
|
3231
3467
|
delete mfConfig.pipelines;
|
|
3232
3468
|
delete mfConfig.images;
|
|
3233
3469
|
delete mfConfig.media;
|
|
3470
|
+
delete mfConfig.analyticsEngineDatasets;
|
|
3471
|
+
delete mfConfig.tails;
|
|
3234
3472
|
delete mfConfig.artifacts;
|
|
3235
3473
|
delete mfConfig.secretsStoreSecrets;
|
|
3236
3474
|
delete mfConfig.wrappedBindings;
|
|
@@ -3481,6 +3719,43 @@ async function createTestContext(configPath) {
|
|
|
3481
3719
|
__setTestContext(createBridgeEnvAccessor(state, hints, shouldPreferBridgeBinding), disposeContext);
|
|
3482
3720
|
}
|
|
3483
3721
|
//#endregion
|
|
3722
|
+
//#region src/test/alarm.ts
|
|
3723
|
+
/**
|
|
3724
|
+
* Trigger a Durable Object's `alarm()` handler.
|
|
3725
|
+
*
|
|
3726
|
+
* This mirrors how the Devflare runtime invokes a DO alarm: it builds a
|
|
3727
|
+
* `durable-object-alarm` event with the instance's env + state, installs it into
|
|
3728
|
+
* AsyncLocalStorage via `runWithEventContext`, and calls the instance's
|
|
3729
|
+
* `alarm()` with that event (so `getDurableObjectAlarmEvent()` works inside the
|
|
3730
|
+
* handler). The instance keeps using its own `this.env` / `this.ctx`; the event
|
|
3731
|
+
* is supplied for context-aware code paths.
|
|
3732
|
+
*
|
|
3733
|
+
* @param target - The Durable Object instance whose `alarm()` should be fired.
|
|
3734
|
+
* @param options - Optional explicit `state`/`env` for the alarm event.
|
|
3735
|
+
* @returns Result object with success status.
|
|
3736
|
+
*
|
|
3737
|
+
* @example
|
|
3738
|
+
* const counter = new Counter(state, env)
|
|
3739
|
+
* await counter.scheduleAlarm()
|
|
3740
|
+
* const result = await cf.alarm.trigger(counter)
|
|
3741
|
+
* expect(result.success).toBe(true)
|
|
3742
|
+
*/
|
|
3743
|
+
async function trigger(target, options = {}) {
|
|
3744
|
+
if (!target || typeof target.alarm !== "function") throw new Error("cf.alarm.trigger(target): the target Durable Object instance has no alarm() method. Pass a DO instance whose class defines an async alarm() handler (and enable alarms via the @durableObject({ alarms: true }) decorator so the runtime wraps it).");
|
|
3745
|
+
const state = options.state ?? target.state ?? target.ctx;
|
|
3746
|
+
const alarmEvent = createDurableObjectAlarmEvent(options.env ?? target.env ?? {}, state);
|
|
3747
|
+
try {
|
|
3748
|
+
await runWithEventContext(alarmEvent, () => target.alarm?.(alarmEvent));
|
|
3749
|
+
return { success: true };
|
|
3750
|
+
} catch (error) {
|
|
3751
|
+
return {
|
|
3752
|
+
success: false,
|
|
3753
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3754
|
+
};
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
const alarm = { trigger };
|
|
3758
|
+
//#endregion
|
|
3484
3759
|
//#region src/test/cf.ts
|
|
3485
3760
|
/**
|
|
3486
3761
|
* Unified Cloudflare test helpers.
|
|
@@ -3491,6 +3766,7 @@ async function createTestContext(configPath) {
|
|
|
3491
3766
|
* - `cf.scheduled` — Cron/scheduled handler testing
|
|
3492
3767
|
* - `cf.worker` — Fetch handler testing
|
|
3493
3768
|
* - `cf.tail` — Tail helper surface (uses `files.tail`, or auto-detects `src/tail.ts` when present)
|
|
3769
|
+
* - `cf.alarm` — Durable Object alarm handler testing (fires a DO instance's `alarm()`)
|
|
3494
3770
|
*
|
|
3495
3771
|
* The helpers use the real Miniflare-backed bindings created by `createTestContext()`,
|
|
3496
3772
|
* but several helper surfaces still synthesize event/controller objects around those
|
|
@@ -3541,7 +3817,8 @@ const cf = {
|
|
|
3541
3817
|
queue,
|
|
3542
3818
|
scheduled,
|
|
3543
3819
|
worker,
|
|
3544
|
-
tail
|
|
3820
|
+
tail,
|
|
3821
|
+
alarm
|
|
3545
3822
|
};
|
|
3546
3823
|
//#endregion
|
|
3547
3824
|
//#region src/test/should-skip.ts
|
|
@@ -4356,9 +4633,15 @@ const SUPPORT_MATRIX = {
|
|
|
4356
4633
|
},
|
|
4357
4634
|
vectorize: {
|
|
4358
4635
|
service: "vectorize",
|
|
4359
|
-
tier: "
|
|
4360
|
-
reason: "Cloudflare
|
|
4361
|
-
recommendation: "Use DEVFLARE_REMOTE=1/devflare remote enable for real
|
|
4636
|
+
tier: "offline-fixture",
|
|
4637
|
+
reason: "Vector storage and cosine-similarity query are deterministic in-memory; Cloudflare's real indexing/ranking/scale are hosted — use remote mode for those.",
|
|
4638
|
+
recommendation: "Use createMockVectorize() or createOfflineEnv() for app-level vector tests; use DEVFLARE_REMOTE=1/devflare remote enable for real index relevance and scale."
|
|
4639
|
+
},
|
|
4640
|
+
analyticsEngine: {
|
|
4641
|
+
service: "analyticsEngine",
|
|
4642
|
+
tier: "offline-fixture",
|
|
4643
|
+
reason: "Analytics Engine writes are recordable in-memory (createMockAnalyticsEngine), but there is no in-worker read API — querying is the hosted SQL API/dashboard.",
|
|
4644
|
+
recommendation: "Use createMockAnalyticsEngine() to assert recorded writeDataPoint() calls; query analytics through the hosted SQL API/dashboard."
|
|
4362
4645
|
},
|
|
4363
4646
|
builds: {
|
|
4364
4647
|
service: "builds",
|
|
@@ -4427,6 +4710,9 @@ function isArtifactsBinding(value) {
|
|
|
4427
4710
|
function isAISearchInstance(value) {
|
|
4428
4711
|
return typeof value?.search === "function";
|
|
4429
4712
|
}
|
|
4713
|
+
function isVectorizeBinding(value) {
|
|
4714
|
+
return typeof value?.query === "function";
|
|
4715
|
+
}
|
|
4430
4716
|
function isAISearchNamespace(value) {
|
|
4431
4717
|
return typeof value?.get === "function";
|
|
4432
4718
|
}
|
|
@@ -4444,6 +4730,27 @@ function addStaticBindings(env, config) {
|
|
|
4444
4730
|
function addRateLimitBindings(env, bindings) {
|
|
4445
4731
|
for (const [name, binding] of Object.entries(bindings?.rateLimits ?? {})) env[name] = createMockRateLimit(binding.simple);
|
|
4446
4732
|
}
|
|
4733
|
+
function addKVBindings(env, bindings, fixtures) {
|
|
4734
|
+
for (const name of Object.keys(bindings?.kv ?? {})) env[name] = fixtures.kv?.[name] ?? createMockKV();
|
|
4735
|
+
}
|
|
4736
|
+
function addD1Bindings(env, bindings, fixtures) {
|
|
4737
|
+
for (const name of Object.keys(bindings?.d1 ?? {})) env[name] = fixtures.d1?.[name] ?? createMockD1();
|
|
4738
|
+
}
|
|
4739
|
+
function addR2Bindings(env, bindings, fixtures) {
|
|
4740
|
+
for (const name of Object.keys(bindings?.r2 ?? {})) env[name] = fixtures.r2?.[name] ?? createMockR2();
|
|
4741
|
+
}
|
|
4742
|
+
function addQueueBindings(env, bindings, fixtures) {
|
|
4743
|
+
for (const name of Object.keys(bindings?.queues?.producers ?? {})) env[name] = fixtures.queues?.[name] ?? createMockQueue();
|
|
4744
|
+
}
|
|
4745
|
+
function addVectorizeBindings(env, bindings, fixtures) {
|
|
4746
|
+
for (const name of Object.keys(bindings?.vectorize ?? {})) {
|
|
4747
|
+
const fixture = fixtures.vectorize?.[name];
|
|
4748
|
+
env[name] = isVectorizeBinding(fixture) ? fixture : createMockVectorize(fixture);
|
|
4749
|
+
}
|
|
4750
|
+
}
|
|
4751
|
+
function addAnalyticsEngineBindings(env, bindings, fixtures) {
|
|
4752
|
+
for (const name of Object.keys(bindings?.analyticsEngine ?? {})) env[name] = fixtures.analyticsEngine?.[name] ?? createMockAnalyticsEngine();
|
|
4753
|
+
}
|
|
4447
4754
|
function addVersionMetadataBinding(env, bindings) {
|
|
4448
4755
|
if (bindings?.versionMetadata) env[bindings.versionMetadata.binding] = createMockVersionMetadata();
|
|
4449
4756
|
}
|
|
@@ -4555,55 +4862,47 @@ function addAISearchNamespaceBindings(env, bindings, fixtures) {
|
|
|
4555
4862
|
}
|
|
4556
4863
|
}
|
|
4557
4864
|
/**
|
|
4558
|
-
*
|
|
4559
|
-
*
|
|
4560
|
-
* `
|
|
4561
|
-
*
|
|
4562
|
-
*
|
|
4563
|
-
*
|
|
4564
|
-
*
|
|
4865
|
+
* Wiring bindings (durableObjects/services) have no pure-offline fixture: they
|
|
4866
|
+
* need a real Miniflare runtime, which only `createTestContext()` provides.
|
|
4867
|
+
* `createOfflineBindings()` therefore cannot populate them, and leaving them
|
|
4868
|
+
* silently absent from `env` is a surprise. Surface each one in
|
|
4869
|
+
* `missingFixtures` (with `env` left unset rather than a fake) so callers see
|
|
4870
|
+
* exactly which bindings require `createTestContext()` / a cross-worker setup
|
|
4871
|
+
* instead of getting an `undefined` lookup at use time.
|
|
4872
|
+
*
|
|
4873
|
+
* Storage bindings (kv/d1/r2/queues) DO have deterministic in-memory mocks, so
|
|
4874
|
+
* `createOfflineBindings()` auto-creates them — they are not listed here.
|
|
4565
4875
|
*/
|
|
4566
|
-
const OFFLINE_UNAVAILABLE_STORAGE_BINDINGS = [
|
|
4567
|
-
"kv",
|
|
4568
|
-
"d1",
|
|
4569
|
-
"r2",
|
|
4570
|
-
"durableObjects",
|
|
4571
|
-
"services"
|
|
4572
|
-
];
|
|
4876
|
+
const OFFLINE_UNAVAILABLE_STORAGE_BINDINGS = ["durableObjects", "services"];
|
|
4573
4877
|
function addUnsupportedStorageBindings(bindings, missingFixtures) {
|
|
4574
4878
|
for (const service of OFFLINE_UNAVAILABLE_STORAGE_BINDINGS) {
|
|
4575
4879
|
const group = bindings?.[service];
|
|
4576
4880
|
for (const name of Object.keys(group ?? {})) missingFixtures.push({
|
|
4577
4881
|
service,
|
|
4578
4882
|
binding: name,
|
|
4579
|
-
reason: `${service} binding "${name}" needs a real Miniflare runtime and is not created by createOfflineBindings(); it will be undefined. Use createTestContext() (Miniflare-backed)
|
|
4883
|
+
reason: `${service} binding "${name}" needs a real Miniflare runtime and is not created by createOfflineBindings(); it will be undefined. Use createTestContext() (Miniflare-backed) for this binding.`
|
|
4580
4884
|
});
|
|
4581
4885
|
}
|
|
4582
|
-
const queueProducers = (bindings?.queues)?.producers ?? {};
|
|
4583
|
-
for (const name of Object.keys(queueProducers)) missingFixtures.push({
|
|
4584
|
-
service: "queues",
|
|
4585
|
-
binding: name,
|
|
4586
|
-
reason: `queues producer binding "${name}" needs a real Miniflare runtime and is not created by createOfflineBindings(); it will be undefined. Use createTestContext() or createMockQueue() for this binding.`
|
|
4587
|
-
});
|
|
4588
4886
|
}
|
|
4589
4887
|
function addRemoteBoundaries(remoteBoundaries, bindings) {
|
|
4590
4888
|
if (bindings?.ai) addBoundary(remoteBoundaries, "ai", bindings.ai.binding || "AI", "Workers AI inference is not available in offline local simulations.");
|
|
4591
|
-
for (const name of Object.keys(bindings?.vectorize ?? {})) addBoundary(remoteBoundaries, "vectorize", name, "Vectorize has no offline local simulation in Cloudflare local development.");
|
|
4592
4889
|
for (const name of Object.keys(bindings?.vpcServices ?? {})) addBoundary(remoteBoundaries, "vpcServices", name, "VPC services are proxy-only locally — Miniflare's vpc-services plugin needs a remote proxy connection, so there is no offline simulation.");
|
|
4593
4890
|
for (const name of Object.keys(bindings?.vpcNetworks ?? {})) addBoundary(remoteBoundaries, "vpcNetworks", name, "VPC networks are proxy-only locally — Miniflare's vpc-networks plugin needs a remote proxy connection, so there is no offline simulation.");
|
|
4594
4891
|
}
|
|
4595
4892
|
/**
|
|
4596
4893
|
* Builds a deterministic, pure-test env object from Devflare config.
|
|
4597
4894
|
*
|
|
4598
|
-
* Covers the bindings that have a pure-offline simulator or fixture
|
|
4599
|
-
*
|
|
4600
|
-
*
|
|
4601
|
-
*
|
|
4602
|
-
*
|
|
4603
|
-
*
|
|
4604
|
-
*
|
|
4605
|
-
* (
|
|
4606
|
-
* `createTestContext()`
|
|
4895
|
+
* Covers the bindings that have a pure-offline simulator or fixture: the core
|
|
4896
|
+
* storage mocks (`kv`, `d1`, `r2`, `queues`) are **auto-created** from the
|
|
4897
|
+
* existing `createMock*()` helpers (an explicit `fixtures.{kv,d1,r2,queues}`
|
|
4898
|
+
* entry overrides the auto-mock), alongside rate limits, version metadata,
|
|
4899
|
+
* hyperdrive, worker loaders, mTLS, dispatch namespaces, workflows, pipelines,
|
|
4900
|
+
* images, media, stream, flagship, artifacts, secrets store, AI Search,
|
|
4901
|
+
* Vectorize, and Analytics Engine. It does **not** create the wiring bindings
|
|
4902
|
+
* (`durableObjects`, `services`) — those require a real Miniflare runtime, which
|
|
4903
|
+
* only `createTestContext()` provides. When such a binding is present in config
|
|
4904
|
+
* it is reported in the returned `missingFixtures` (its `env` entry is left
|
|
4905
|
+
* unset rather than silently faked); use `createTestContext()` for those.
|
|
4607
4906
|
*/
|
|
4608
4907
|
function createOfflineBindings(config, fixtures = {}, options = {}) {
|
|
4609
4908
|
const env = {};
|
|
@@ -4612,6 +4911,12 @@ function createOfflineBindings(config, fixtures = {}, options = {}) {
|
|
|
4612
4911
|
const bindings = config.bindings;
|
|
4613
4912
|
const localSecretValues = options.cwd && options.useLocalSecrets !== false ? resolveLocalSecretValuesForBindings(config, options.cwd) : {};
|
|
4614
4913
|
addStaticBindings(env, config);
|
|
4914
|
+
addKVBindings(env, bindings, fixtures);
|
|
4915
|
+
addD1Bindings(env, bindings, fixtures);
|
|
4916
|
+
addR2Bindings(env, bindings, fixtures);
|
|
4917
|
+
addQueueBindings(env, bindings, fixtures);
|
|
4918
|
+
addVectorizeBindings(env, bindings, fixtures);
|
|
4919
|
+
addAnalyticsEngineBindings(env, bindings, fixtures);
|
|
4615
4920
|
addRateLimitBindings(env, bindings);
|
|
4616
4921
|
addVersionMetadataBinding(env, bindings);
|
|
4617
4922
|
addHyperdriveBindings(env, bindings, fixtures, missingFixtures);
|
|
@@ -4641,14 +4946,14 @@ function createOfflineBindings(config, fixtures = {}, options = {}) {
|
|
|
4641
4946
|
/**
|
|
4642
4947
|
* Convenience wrapper for callers that only need the derived env object.
|
|
4643
4948
|
*
|
|
4644
|
-
* Note: the
|
|
4645
|
-
* `durableObjects`, `services`) are **not**
|
|
4646
|
-
* `undefined` in the returned env
|
|
4647
|
-
* `createTestContext()` or
|
|
4648
|
-
* `
|
|
4949
|
+
* Note: the storage mocks (`kv`, `d1`, `r2`, `queues`) are auto-created here, but
|
|
4950
|
+
* the wiring bindings (`durableObjects`, `services`) are **not** — they need a
|
|
4951
|
+
* real Miniflare runtime and will be `undefined` in the returned env. Use
|
|
4952
|
+
* `createTestContext()` for them, or call `createOfflineBindings()` to inspect
|
|
4953
|
+
* `missingFixtures` for the exact list.
|
|
4649
4954
|
*/
|
|
4650
4955
|
function createOfflineEnv(config, fixtures = {}, options = {}) {
|
|
4651
4956
|
return createOfflineBindings(config, fixtures, options).env;
|
|
4652
4957
|
}
|
|
4653
4958
|
//#endregion
|
|
4654
|
-
export { cf, clearBundleCache, containers, createContainerManager, createMockAISearchInstance, createMockAISearchNamespace, createMockArtifacts, createMockD1, createMockDispatchNamespace, createMockEnv, createMockFlagshipBinding, createMockHyperdrive, createMockImagesBinding, createMockKV, createMockMTLSCertificate, createMockMediaBinding, createMockPipeline, createMockQueue, createMockR2, createMockRateLimit, createMockSecretsStoreSecret, createMockStreamBinding, createMockTestContext, createMockVersionMetadata, createMockWorkerLoader, createMockWorkflow, createOfflineBindings, createOfflineEnv, createTestContext, describeOfflineSupport, detectContainerEngine, email, env, getContainerSkipReason, getOfflineSupportMatrix, hasCrossWorkerDOs, hasServiceBindings, queue, resolveDOBindings, resolveServiceBindings, scheduled, shouldSkip, stopActiveContainers, tail, withTestContext, worker };
|
|
4959
|
+
export { alarm, cf, clearBundleCache, containers, createContainerManager, createMockAISearchInstance, createMockAISearchNamespace, createMockAnalyticsEngine, createMockArtifacts, createMockD1, createMockDispatchNamespace, createMockEnv, createMockFlagshipBinding, createMockHyperdrive, createMockImagesBinding, createMockKV, createMockMTLSCertificate, createMockMediaBinding, createMockPipeline, createMockQueue, createMockR2, createMockRateLimit, createMockSecretsStoreSecret, createMockStreamBinding, createMockTestContext, createMockVectorize, createMockVersionMetadata, createMockWorkerLoader, createMockWorkflow, createOfflineBindings, createOfflineEnv, createTestContext, describeOfflineSupport, detectContainerEngine, email, env, getContainerSkipReason, getOfflineSupportMatrix, hasCrossWorkerDOs, hasServiceBindings, queue, resolveDOBindings, resolveServiceBindings, scheduled, shouldSkip, stopActiveContainers, tail, withTestContext, worker };
|