@concavejs/runtime-cf-base 0.0.1-alpha.10
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/adapters/cf-websocket-adapter.d.ts +38 -0
- package/dist/adapters/cf-websocket-adapter.js +83 -0
- package/dist/durable-objects/blobstore-rpc.d.ts +13 -0
- package/dist/durable-objects/blobstore-rpc.js +27 -0
- package/dist/durable-objects/concave-do-base.d.ts +169 -0
- package/dist/durable-objects/concave-do-base.js +466 -0
- package/dist/durable-objects/docstore-rpc.d.ts +46 -0
- package/dist/durable-objects/docstore-rpc.js +63 -0
- package/dist/durable-objects/scheduler-manager.d.ts +19 -0
- package/dist/durable-objects/scheduler-manager.js +53 -0
- package/dist/durable-objects/sync-notifier.d.ts +16 -0
- package/dist/durable-objects/sync-notifier.js +38 -0
- package/dist/env.d.ts +19 -0
- package/dist/env.js +6 -0
- package/dist/http/dx-http.d.ts +43 -0
- package/dist/http/dx-http.js +327 -0
- package/dist/http/http-api.d.ts +38 -0
- package/dist/http/http-api.js +399 -0
- package/dist/http/index.d.ts +7 -0
- package/dist/http/index.js +7 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +27 -0
- package/dist/internal.d.ts +4 -0
- package/dist/internal.js +4 -0
- package/dist/routing/instance.d.ts +25 -0
- package/dist/routing/instance.js +101 -0
- package/dist/routing/sync-topology.d.ts +40 -0
- package/dist/routing/sync-topology.js +669 -0
- package/dist/rpc/blobstore-proxy.d.ts +11 -0
- package/dist/rpc/blobstore-proxy.js +28 -0
- package/dist/rpc/docstore-proxy.d.ts +11 -0
- package/dist/rpc/docstore-proxy.js +73 -0
- package/dist/rpc/index.d.ts +2 -0
- package/dist/rpc/index.js +2 -0
- package/dist/sync/cf-websocket-adapter.d.ts +15 -0
- package/dist/sync/cf-websocket-adapter.js +22 -0
- package/dist/sync/concave-do-udf-executor.d.ts +46 -0
- package/dist/sync/concave-do-udf-executor.js +75 -0
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.js +2 -0
- package/dist/udf/executor/do-client-executor.d.ts +14 -0
- package/dist/udf/executor/do-client-executor.js +58 -0
- package/dist/udf/executor/index.d.ts +8 -0
- package/dist/udf/executor/index.js +8 -0
- package/dist/udf/executor/inline-executor.d.ts +13 -0
- package/dist/udf/executor/inline-executor.js +25 -0
- package/dist/udf/executor/isolated-executor.d.ts +24 -0
- package/dist/udf/executor/isolated-executor.js +31 -0
- package/dist/udf/executor/shim-content.d.ts +1 -0
- package/dist/udf/executor/shim-content.js +3 -0
- package/dist/worker/create-concave-worker.d.ts +79 -0
- package/dist/worker/create-concave-worker.js +196 -0
- package/dist/worker/index.d.ts +6 -0
- package/dist/worker/index.js +6 -0
- package/dist/worker/udf-worker.d.ts +25 -0
- package/dist/worker/udf-worker.js +63 -0
- package/package.json +99 -0
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
import { buildPooledInstanceName as buildSharedPooledInstanceName, buildShardMapFromReports as buildSharedShardMapFromReports, isValidSyncShardMap, normalizeTargetList as normalizeSharedTargetList, parseNodeIds as parseSharedNodeIds, parseNodeIdsByRegion as parseSharedNodeIdsByRegion, resolveBootstrapSyncShardCandidates, resolveStaticSyncShardMap as resolveSharedStaticShardMap, } from "@concavejs/runtime-base";
|
|
2
|
+
const DEFAULT_SYNC_NODE_IDS_ENV = "CONCAVE_SYNC_NODE_IDS";
|
|
3
|
+
const DEFAULT_SYNC_NODE_IDS_BY_REGION_ENV = "CONCAVE_SYNC_NODE_IDS_BY_REGION";
|
|
4
|
+
const DEFAULT_SYNC_AFFINITY_KEY_ENV = "CONCAVE_SYNC_AFFINITY_KEY";
|
|
5
|
+
const DEFAULT_SYNC_REGION_KEY_ENV = "CONCAVE_SYNC_REGION_KEY";
|
|
6
|
+
const DEFAULT_SYNC_DEFAULT_REGION_ENV = "CONCAVE_SYNC_DEFAULT_REGION";
|
|
7
|
+
const DEFAULT_SYNC_REGION_MAP_ENV = "CONCAVE_SYNC_REGION_MAP";
|
|
8
|
+
const DEFAULT_SYNC_USE_COLO_AS_REGION_ENV = "CONCAVE_SYNC_USE_COLO_AS_REGION";
|
|
9
|
+
const DEFAULT_SYNC_SHARD_MAP_CACHE_MS_ENV = "CONCAVE_SYNC_SHARD_MAP_CACHE_MS";
|
|
10
|
+
const DEFAULT_SYNC_NODE_STALE_MS_ENV = "CONCAVE_SYNC_NODE_STALE_MS";
|
|
11
|
+
const DEFAULT_SYNC_MIN_SHARDS_ENV = "CONCAVE_SYNC_MIN_SHARDS_PER_REGION";
|
|
12
|
+
const DEFAULT_SYNC_MAX_SHARDS_ENV = "CONCAVE_SYNC_MAX_SHARDS_PER_REGION";
|
|
13
|
+
const DEFAULT_SYNC_TARGET_SESSIONS_ENV = "CONCAVE_SYNC_TARGET_SESSIONS_PER_SHARD";
|
|
14
|
+
const DEFAULT_SYNC_TARGET_MESSAGE_RATE_ENV = "CONCAVE_SYNC_TARGET_MESSAGE_RATE_PER_SHARD";
|
|
15
|
+
const DEFAULT_SYNC_TARGET_NOTIFY_RATE_ENV = "CONCAVE_SYNC_TARGET_NOTIFY_RATE_PER_SHARD";
|
|
16
|
+
const DEFAULT_SYNC_TARGET_CPU_UTILIZATION_ENV = "CONCAVE_SYNC_TARGET_CPU_UTILIZATION";
|
|
17
|
+
const DEFAULT_SYNC_TARGET_MEMORY_UTILIZATION_ENV = "CONCAVE_SYNC_TARGET_MEMORY_UTILIZATION";
|
|
18
|
+
const DEFAULT_SYNC_AUTO_SHARDS_ENV = "CONCAVE_SYNC_AUTO_SHARDS_PER_REGION";
|
|
19
|
+
const DEFAULT_SYNC_AUTO_SHARD_PREFIX_ENV = "CONCAVE_SYNC_AUTO_SHARD_PREFIX";
|
|
20
|
+
const DEFAULT_SYNC_SCALE_TO_ZERO_ENV = "CONCAVE_SYNC_SCALE_TO_ZERO";
|
|
21
|
+
const DEFAULT_SYNC_AUTOSCALE_PROFILE_ENV = "CONCAVE_SYNC_AUTOSCALE_PROFILE";
|
|
22
|
+
const DEFAULT_SYNC_SCALE_UP_COOLDOWN_MS_ENV = "CONCAVE_SYNC_SCALE_UP_COOLDOWN_MS";
|
|
23
|
+
const DEFAULT_SYNC_SCALE_DOWN_COOLDOWN_MS_ENV = "CONCAVE_SYNC_SCALE_DOWN_COOLDOWN_MS";
|
|
24
|
+
const DEFAULT_SYNC_SCALE_UP_HYSTERESIS_RATIO_ENV = "CONCAVE_SYNC_SCALE_UP_HYSTERESIS_RATIO";
|
|
25
|
+
const DEFAULT_SYNC_SCALE_DOWN_HYSTERESIS_RATIO_ENV = "CONCAVE_SYNC_SCALE_DOWN_HYSTERESIS_RATIO";
|
|
26
|
+
const DEFAULT_SYNC_MAX_SCALE_UP_STEP_ENV = "CONCAVE_SYNC_MAX_SCALE_UP_STEP";
|
|
27
|
+
const DEFAULT_SYNC_MAX_SCALE_DOWN_STEP_ENV = "CONCAVE_SYNC_MAX_SCALE_DOWN_STEP";
|
|
28
|
+
const DEFAULT_PROJECT_HEADER = "X-Concave-Project-Id";
|
|
29
|
+
const DEFAULT_SYNC_COORDINATOR_BINDING = "SYNC_COORDINATOR_DO";
|
|
30
|
+
const DEFAULT_SYNC_SHARD_MAP_KV_BINDING = "SYNC_SHARD_MAP_KV";
|
|
31
|
+
const DEFAULT_SYNC_AFFINITY_KEY = "x-concave-sync-affinity";
|
|
32
|
+
const DEFAULT_SYNC_REGION_KEY = "x-concave-region";
|
|
33
|
+
const DEFAULT_SYNC_DEFAULT_REGION = "global";
|
|
34
|
+
const DEFAULT_SHARD_MAP_CACHE_MS = 5_000;
|
|
35
|
+
const DEFAULT_NODE_STALE_MS = 90_000;
|
|
36
|
+
const DEFAULT_MIN_SHARDS = 1;
|
|
37
|
+
const DEFAULT_TARGET_SESSIONS_PER_SHARD = 500;
|
|
38
|
+
const DEFAULT_TARGET_CPU_UTILIZATION = 0.75;
|
|
39
|
+
const DEFAULT_TARGET_MEMORY_UTILIZATION = 0.8;
|
|
40
|
+
const DEFAULT_AUTO_SHARD_PREFIX = "auto";
|
|
41
|
+
const DEFAULT_SYNC_AUTOSCALE_PROFILE = "advanced";
|
|
42
|
+
const DEFAULT_SCALE_UP_COOLDOWN_MS = 15_000;
|
|
43
|
+
const DEFAULT_SCALE_DOWN_COOLDOWN_MS = 45_000;
|
|
44
|
+
const DEFAULT_SCALE_UP_HYSTERESIS_RATIO = 1.05;
|
|
45
|
+
const DEFAULT_SCALE_DOWN_HYSTERESIS_RATIO = 0.95;
|
|
46
|
+
const DEFAULT_MAX_SCALE_UP_STEP = 2;
|
|
47
|
+
const DEFAULT_MAX_SCALE_DOWN_STEP = 1;
|
|
48
|
+
export function createCfSyncTopologyRuntime(options) {
|
|
49
|
+
const logPrefix = options.logPrefix ?? "[sync-topology]";
|
|
50
|
+
const projectHeaderName = options.projectHeaderName ?? DEFAULT_PROJECT_HEADER;
|
|
51
|
+
const coordinatorBindingName = options.coordinatorBindingName ?? DEFAULT_SYNC_COORDINATOR_BINDING;
|
|
52
|
+
const shardMapKvBindingName = options.shardMapKvBindingName ?? DEFAULT_SYNC_SHARD_MAP_KV_BINDING;
|
|
53
|
+
const resolveProjectId = options.resolveProjectId ?? (() => undefined);
|
|
54
|
+
const buildShardMapCacheKey = options.buildShardMapCacheKey ??
|
|
55
|
+
((logicalInstance, projectId) => {
|
|
56
|
+
const normalizedProject = projectId?.trim() ?? "default";
|
|
57
|
+
return `${normalizedProject}:${logicalInstance}`;
|
|
58
|
+
});
|
|
59
|
+
const shardMapCache = new Map();
|
|
60
|
+
const shardMapInFlight = new Map();
|
|
61
|
+
async function resolveRouteTargets(context) {
|
|
62
|
+
const env = context.env;
|
|
63
|
+
const config = resolveTopologyConfig(env);
|
|
64
|
+
const projectId = resolveProjectId(context.request, env);
|
|
65
|
+
const region = resolveRequestRegion(context.request, env);
|
|
66
|
+
const shardMap = await resolveSyncShardMap({
|
|
67
|
+
request: context.request,
|
|
68
|
+
env,
|
|
69
|
+
logicalInstance: context.instance,
|
|
70
|
+
projectId,
|
|
71
|
+
region,
|
|
72
|
+
config,
|
|
73
|
+
});
|
|
74
|
+
const regionCandidates = resolveRegionShardCandidates(shardMap, region, config.defaultRegion);
|
|
75
|
+
const bootstrapCandidates = regionCandidates.length > 0
|
|
76
|
+
? regionCandidates
|
|
77
|
+
: resolveBootstrapShardCandidates(context.instance, region, config);
|
|
78
|
+
const syncDoName = selectDoName(context.request, context.instance, bootstrapCandidates, config.syncAffinityKey);
|
|
79
|
+
return {
|
|
80
|
+
syncDoName,
|
|
81
|
+
concaveDoName: context.instance,
|
|
82
|
+
syncNotifyDoNames: normalizeTargetList(shardMap.notifyShards),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function resolveSyncFanoutDoNames(logicalInstance, env) {
|
|
86
|
+
const staticMap = resolveStaticSyncShardMap(logicalInstance, env);
|
|
87
|
+
return normalizeTargetList(staticMap.notifyShards);
|
|
88
|
+
}
|
|
89
|
+
async function resolveSyncFanoutDoNamesAsync(logicalInstance, env, optionsInput) {
|
|
90
|
+
const config = resolveTopologyConfig(env);
|
|
91
|
+
const request = optionsInput?.request ?? new Request("https://concave.invalid/sync");
|
|
92
|
+
const projectId = optionsInput?.projectId ?? resolveProjectId(request, env);
|
|
93
|
+
const region = resolveRequestRegion(request, env);
|
|
94
|
+
const shardMap = await resolveSyncShardMap({
|
|
95
|
+
request,
|
|
96
|
+
env,
|
|
97
|
+
logicalInstance,
|
|
98
|
+
projectId,
|
|
99
|
+
region,
|
|
100
|
+
config,
|
|
101
|
+
});
|
|
102
|
+
return normalizeTargetList(shardMap.notifyShards);
|
|
103
|
+
}
|
|
104
|
+
function resolveStaticSyncShardMap(logicalInstance, env, nowMs = Date.now()) {
|
|
105
|
+
const config = resolveTopologyConfig(env);
|
|
106
|
+
return resolveSharedStaticShardMap(logicalInstance, config, nowMs);
|
|
107
|
+
}
|
|
108
|
+
function buildShardMapFromReports(optionsInput) {
|
|
109
|
+
return buildSharedShardMapFromReports({
|
|
110
|
+
...optionsInput,
|
|
111
|
+
config: resolveTopologyConfig(optionsInput.env),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function resolveTopologyConfig(env) {
|
|
115
|
+
const syncNodes = parseNodeIds(env[DEFAULT_SYNC_NODE_IDS_ENV]);
|
|
116
|
+
const syncNodesByRegion = parseNodeIdsByRegion(env[DEFAULT_SYNC_NODE_IDS_BY_REGION_ENV]);
|
|
117
|
+
const syncAffinityKey = resolveHeaderLikeKey(env[DEFAULT_SYNC_AFFINITY_KEY_ENV], DEFAULT_SYNC_AFFINITY_KEY);
|
|
118
|
+
const regionKey = resolveHeaderLikeKey(env[DEFAULT_SYNC_REGION_KEY_ENV], DEFAULT_SYNC_REGION_KEY);
|
|
119
|
+
const defaultRegion = normalizeRegionName(env[DEFAULT_SYNC_DEFAULT_REGION_ENV], DEFAULT_SYNC_DEFAULT_REGION);
|
|
120
|
+
const regionByColo = parseRegionByColoMap(env[DEFAULT_SYNC_REGION_MAP_ENV]);
|
|
121
|
+
const useColoAsRegion = parseBoolean(env[DEFAULT_SYNC_USE_COLO_AS_REGION_ENV], false);
|
|
122
|
+
const scaleProfile = parseScaleProfile(env[DEFAULT_SYNC_AUTOSCALE_PROFILE_ENV], DEFAULT_SYNC_AUTOSCALE_PROFILE);
|
|
123
|
+
const allowScaleToZeroRequested = parseBoolean(env[DEFAULT_SYNC_SCALE_TO_ZERO_ENV], false);
|
|
124
|
+
const allowScaleToZero = allowScaleToZeroRequested && asDurableObjectNamespace(env[coordinatorBindingName]) !== undefined;
|
|
125
|
+
const shardMapCacheMs = parseIntWithMin(env[DEFAULT_SYNC_SHARD_MAP_CACHE_MS_ENV], DEFAULT_SHARD_MAP_CACHE_MS, 500);
|
|
126
|
+
const nodeStaleMs = parseIntWithMin(env[DEFAULT_SYNC_NODE_STALE_MS_ENV], DEFAULT_NODE_STALE_MS, 1_000);
|
|
127
|
+
const minShardFloor = allowScaleToZero ? 0 : 1;
|
|
128
|
+
const minShardsPerRegion = parseIntWithMin(env[DEFAULT_SYNC_MIN_SHARDS_ENV], allowScaleToZero ? 0 : DEFAULT_MIN_SHARDS, minShardFloor);
|
|
129
|
+
const maxShardsPerRegion = parseOptionalIntWithMin(env[DEFAULT_SYNC_MAX_SHARDS_ENV], minShardsPerRegion);
|
|
130
|
+
const targetSessionsPerShard = parseIntWithMin(env[DEFAULT_SYNC_TARGET_SESSIONS_ENV], DEFAULT_TARGET_SESSIONS_PER_SHARD, 1);
|
|
131
|
+
const targetMessageRatePerShard = parseOptionalIntWithMin(env[DEFAULT_SYNC_TARGET_MESSAGE_RATE_ENV], 1);
|
|
132
|
+
const targetNotifyRatePerShard = parseOptionalIntWithMin(env[DEFAULT_SYNC_TARGET_NOTIFY_RATE_ENV], 1);
|
|
133
|
+
const targetCpuUtilizationRaw = parseOptionalFloatWithRange(env[DEFAULT_SYNC_TARGET_CPU_UTILIZATION_ENV], 0.01, 1.5);
|
|
134
|
+
const targetMemoryUtilizationRaw = parseOptionalFloatWithRange(env[DEFAULT_SYNC_TARGET_MEMORY_UTILIZATION_ENV], 0.01, 1.5);
|
|
135
|
+
const targetCpuUtilization = targetCpuUtilizationRaw ?? (scaleProfile === "minimal" ? undefined : DEFAULT_TARGET_CPU_UTILIZATION);
|
|
136
|
+
const targetMemoryUtilization = targetMemoryUtilizationRaw ?? (scaleProfile === "minimal" ? undefined : DEFAULT_TARGET_MEMORY_UTILIZATION);
|
|
137
|
+
const autoShardsPerRegion = parseIntWithMin(env[DEFAULT_SYNC_AUTO_SHARDS_ENV], maxShardsPerRegion ?? minShardsPerRegion, 1);
|
|
138
|
+
const autoShardPrefix = resolveShardPrefix(env[DEFAULT_SYNC_AUTO_SHARD_PREFIX_ENV], DEFAULT_AUTO_SHARD_PREFIX);
|
|
139
|
+
const scaleUpCooldownMs = parseIntWithMin(env[DEFAULT_SYNC_SCALE_UP_COOLDOWN_MS_ENV], DEFAULT_SCALE_UP_COOLDOWN_MS, 0);
|
|
140
|
+
const scaleDownCooldownMs = parseIntWithMin(env[DEFAULT_SYNC_SCALE_DOWN_COOLDOWN_MS_ENV], DEFAULT_SCALE_DOWN_COOLDOWN_MS, 0);
|
|
141
|
+
const scaleUpHysteresisRatio = parseFloatWithRange(env[DEFAULT_SYNC_SCALE_UP_HYSTERESIS_RATIO_ENV], DEFAULT_SCALE_UP_HYSTERESIS_RATIO, 1, 3);
|
|
142
|
+
const scaleDownHysteresisRatio = parseFloatWithRange(env[DEFAULT_SYNC_SCALE_DOWN_HYSTERESIS_RATIO_ENV], DEFAULT_SCALE_DOWN_HYSTERESIS_RATIO, 0, 1);
|
|
143
|
+
const maxScaleUpStep = parseIntWithMin(env[DEFAULT_SYNC_MAX_SCALE_UP_STEP_ENV], DEFAULT_MAX_SCALE_UP_STEP, 1);
|
|
144
|
+
const maxScaleDownStep = parseIntWithMin(env[DEFAULT_SYNC_MAX_SCALE_DOWN_STEP_ENV], DEFAULT_MAX_SCALE_DOWN_STEP, 1);
|
|
145
|
+
return {
|
|
146
|
+
syncNodes,
|
|
147
|
+
syncNodesByRegion,
|
|
148
|
+
syncAffinityKey,
|
|
149
|
+
regionKey,
|
|
150
|
+
defaultRegion,
|
|
151
|
+
regionByColo,
|
|
152
|
+
useColoAsRegion,
|
|
153
|
+
shardMapCacheMs,
|
|
154
|
+
nodeStaleMs,
|
|
155
|
+
minShardsPerRegion,
|
|
156
|
+
maxShardsPerRegion,
|
|
157
|
+
targetSessionsPerShard,
|
|
158
|
+
targetMessageRatePerShard,
|
|
159
|
+
targetNotifyRatePerShard,
|
|
160
|
+
targetCpuUtilization,
|
|
161
|
+
targetMemoryUtilization,
|
|
162
|
+
autoShardsPerRegion,
|
|
163
|
+
autoShardPrefix,
|
|
164
|
+
allowScaleToZero,
|
|
165
|
+
scaleProfile,
|
|
166
|
+
scaleUpCooldownMs,
|
|
167
|
+
scaleDownCooldownMs,
|
|
168
|
+
scaleUpHysteresisRatio,
|
|
169
|
+
scaleDownHysteresisRatio,
|
|
170
|
+
maxScaleUpStep,
|
|
171
|
+
maxScaleDownStep,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function resolveRequestRegion(request, env) {
|
|
175
|
+
const config = resolveTopologyConfig(env);
|
|
176
|
+
const regionFromRequest = resolveHeaderQueryOrCookieValue(request, config.regionKey);
|
|
177
|
+
if (regionFromRequest && regionFromRequest.length > 0) {
|
|
178
|
+
return normalizeRegionName(regionFromRequest, config.defaultRegion);
|
|
179
|
+
}
|
|
180
|
+
const requestWithCf = request;
|
|
181
|
+
const colo = requestWithCf.cf?.colo?.trim();
|
|
182
|
+
if (colo && colo.length > 0) {
|
|
183
|
+
const mapped = config.regionByColo[colo.toUpperCase()];
|
|
184
|
+
if (mapped) {
|
|
185
|
+
return mapped;
|
|
186
|
+
}
|
|
187
|
+
if (config.useColoAsRegion) {
|
|
188
|
+
return normalizeRegionName(colo, config.defaultRegion);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return config.defaultRegion;
|
|
192
|
+
}
|
|
193
|
+
async function resolveSyncShardMap(context) {
|
|
194
|
+
const cacheKey = buildShardMapCacheKey(context.logicalInstance, context.projectId);
|
|
195
|
+
const nowMs = Date.now();
|
|
196
|
+
const cached = shardMapCache.get(cacheKey);
|
|
197
|
+
if (cached && cached.expiresAtMs > nowMs) {
|
|
198
|
+
return cached.value;
|
|
199
|
+
}
|
|
200
|
+
const inFlight = shardMapInFlight.get(cacheKey);
|
|
201
|
+
if (inFlight) {
|
|
202
|
+
return inFlight;
|
|
203
|
+
}
|
|
204
|
+
const resolution = resolveSyncShardMapUncached(context).then((resolved) => {
|
|
205
|
+
const ttlMs = parseIntWithMin(resolved.ttlMs, context.config.shardMapCacheMs, 500);
|
|
206
|
+
const normalized = {
|
|
207
|
+
...resolved,
|
|
208
|
+
ttlMs,
|
|
209
|
+
};
|
|
210
|
+
shardMapCache.set(cacheKey, {
|
|
211
|
+
expiresAtMs: Date.now() + ttlMs,
|
|
212
|
+
value: normalized,
|
|
213
|
+
});
|
|
214
|
+
return normalized;
|
|
215
|
+
});
|
|
216
|
+
shardMapInFlight.set(cacheKey, resolution);
|
|
217
|
+
try {
|
|
218
|
+
return await resolution;
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
shardMapInFlight.delete(cacheKey);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function resolveSyncShardMapUncached(context) {
|
|
225
|
+
const fromKv = await tryReadShardMapFromKv(context);
|
|
226
|
+
if (fromKv) {
|
|
227
|
+
return fromKv;
|
|
228
|
+
}
|
|
229
|
+
const fromCoordinator = await tryReadShardMapFromCoordinator(context);
|
|
230
|
+
if (fromCoordinator) {
|
|
231
|
+
return fromCoordinator;
|
|
232
|
+
}
|
|
233
|
+
return resolveStaticSyncShardMap(context.logicalInstance, context.env);
|
|
234
|
+
}
|
|
235
|
+
async function tryReadShardMapFromKv(context) {
|
|
236
|
+
const kv = asKvNamespace(context.env[shardMapKvBindingName]);
|
|
237
|
+
if (!kv) {
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const key = options.buildShardMapKvKey(context.logicalInstance, context.projectId);
|
|
242
|
+
const raw = await kv.get(key, { type: "json" });
|
|
243
|
+
const parsed = parseShardMap(raw, context.logicalInstance);
|
|
244
|
+
if (!parsed) {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
return { ...parsed, source: "kv" };
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
console.warn(`${logPrefix} Failed to read sync shard map from KV`, error);
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async function tryReadShardMapFromCoordinator(context) {
|
|
255
|
+
const coordinatorNamespace = asDurableObjectNamespace(context.env[coordinatorBindingName]);
|
|
256
|
+
if (!coordinatorNamespace) {
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
const coordinatorName = options.buildCoordinatorDoName(context.logicalInstance, context.projectId);
|
|
261
|
+
const coordinatorId = coordinatorNamespace.idFromName(coordinatorName);
|
|
262
|
+
const coordinatorStub = coordinatorNamespace.get(coordinatorId);
|
|
263
|
+
const headers = new Headers({
|
|
264
|
+
"X-Concave-Instance": context.logicalInstance,
|
|
265
|
+
"X-Concave-Region": context.region,
|
|
266
|
+
});
|
|
267
|
+
if (context.projectId) {
|
|
268
|
+
headers.set(projectHeaderName, context.projectId);
|
|
269
|
+
}
|
|
270
|
+
const response = await coordinatorStub.fetch(`http://do/shard-map?region=${encodeURIComponent(context.region)}`, {
|
|
271
|
+
method: "GET",
|
|
272
|
+
headers,
|
|
273
|
+
});
|
|
274
|
+
if (!response.ok) {
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
const body = (await response.json());
|
|
278
|
+
const parsed = parseShardMap(body, context.logicalInstance);
|
|
279
|
+
if (!parsed) {
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
return { ...parsed, source: "coordinator" };
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
console.warn(`${logPrefix} Failed to read sync shard map from coordinator`, error);
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
resolveRouteTargets,
|
|
291
|
+
resolveSyncFanoutDoNames,
|
|
292
|
+
resolveSyncFanoutDoNamesAsync,
|
|
293
|
+
resolveStaticSyncShardMap,
|
|
294
|
+
buildShardMapFromReports,
|
|
295
|
+
resolveTopologyConfig,
|
|
296
|
+
resolveRequestRegion,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
export function buildPooledInstanceName(logicalInstance, poolKind, nodeId) {
|
|
300
|
+
return buildSharedPooledInstanceName(logicalInstance, poolKind, nodeId);
|
|
301
|
+
}
|
|
302
|
+
export function parseNodeIds(raw) {
|
|
303
|
+
return parseSharedNodeIds(raw);
|
|
304
|
+
}
|
|
305
|
+
export function parseNodeIdsByRegion(raw) {
|
|
306
|
+
return parseSharedNodeIdsByRegion(raw);
|
|
307
|
+
}
|
|
308
|
+
export function normalizeTargetList(targets) {
|
|
309
|
+
return normalizeSharedTargetList(targets);
|
|
310
|
+
}
|
|
311
|
+
function parseShardMap(raw, fallbackInstance) {
|
|
312
|
+
if (isValidSyncShardMap(raw)) {
|
|
313
|
+
return normalizeParsedShardMap(raw, fallbackInstance);
|
|
314
|
+
}
|
|
315
|
+
const data = parseRecordLike(raw);
|
|
316
|
+
if (!data) {
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
const regionsRaw = parseRecordLike(data.regions);
|
|
320
|
+
const regions = {};
|
|
321
|
+
if (regionsRaw) {
|
|
322
|
+
for (const [region, value] of Object.entries(regionsRaw)) {
|
|
323
|
+
const normalizedRegion = normalizeRegionName(region, "");
|
|
324
|
+
if (!normalizedRegion) {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const targets = parseNodeIds(value);
|
|
328
|
+
if (targets.length > 0 || isExplicitEmptyNodeList(value)) {
|
|
329
|
+
regions[normalizedRegion] = targets;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const notifySource = data.notifyShards ?? data.notify;
|
|
334
|
+
const notifyShards = normalizeTargetList(parseNodeIds(notifySource));
|
|
335
|
+
if (Object.keys(regions).length === 0) {
|
|
336
|
+
regions[DEFAULT_SYNC_DEFAULT_REGION] = [fallbackInstance];
|
|
337
|
+
}
|
|
338
|
+
const ttlMs = parseIntWithMin(data.ttlMs, DEFAULT_SHARD_MAP_CACHE_MS, 500);
|
|
339
|
+
const generatedAtMs = parseIntWithMin(data.generatedAtMs, Date.now(), 0);
|
|
340
|
+
return normalizeParsedShardMap({
|
|
341
|
+
generatedAtMs,
|
|
342
|
+
ttlMs,
|
|
343
|
+
regions,
|
|
344
|
+
notifyShards,
|
|
345
|
+
}, fallbackInstance);
|
|
346
|
+
}
|
|
347
|
+
function normalizeParsedShardMap(shardMap, fallbackInstance) {
|
|
348
|
+
const regions = {};
|
|
349
|
+
for (const [region, targets] of Object.entries(shardMap.regions)) {
|
|
350
|
+
const normalizedRegion = normalizeRegionName(region, "");
|
|
351
|
+
if (!normalizedRegion) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
regions[normalizedRegion] = normalizeTargetList(targets);
|
|
355
|
+
}
|
|
356
|
+
if (Object.keys(regions).length === 0) {
|
|
357
|
+
regions[DEFAULT_SYNC_DEFAULT_REGION] = [fallbackInstance];
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
...shardMap,
|
|
361
|
+
generatedAtMs: parseIntWithMin(shardMap.generatedAtMs, Date.now(), 0),
|
|
362
|
+
ttlMs: parseIntWithMin(shardMap.ttlMs, DEFAULT_SHARD_MAP_CACHE_MS, 500),
|
|
363
|
+
regions,
|
|
364
|
+
notifyShards: normalizeTargetList(shardMap.notifyShards),
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function parseRegionByColoMap(raw) {
|
|
368
|
+
const parsed = parseRecordLike(raw);
|
|
369
|
+
if (!parsed) {
|
|
370
|
+
return {};
|
|
371
|
+
}
|
|
372
|
+
const result = {};
|
|
373
|
+
for (const [colo, regionValue] of Object.entries(parsed)) {
|
|
374
|
+
if (typeof regionValue !== "string") {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
const normalizedRegion = normalizeRegionName(regionValue, "");
|
|
378
|
+
if (!normalizedRegion) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
result[colo.trim().toUpperCase()] = normalizedRegion;
|
|
382
|
+
}
|
|
383
|
+
return result;
|
|
384
|
+
}
|
|
385
|
+
function parseRecordLike(raw) {
|
|
386
|
+
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
|
|
387
|
+
return raw;
|
|
388
|
+
}
|
|
389
|
+
if (typeof raw !== "string") {
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
const trimmed = raw.trim();
|
|
393
|
+
if (trimmed.length === 0) {
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
const parsed = JSON.parse(trimmed);
|
|
398
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
399
|
+
return parsed;
|
|
400
|
+
}
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
404
|
+
const result = {};
|
|
405
|
+
for (const token of trimmed.split(";")) {
|
|
406
|
+
const normalizedToken = token.trim();
|
|
407
|
+
if (!normalizedToken) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
const equalsIndex = normalizedToken.indexOf("=");
|
|
411
|
+
const colonIndex = normalizedToken.indexOf(":");
|
|
412
|
+
const separatorIndex = equalsIndex === -1 ? colonIndex : equalsIndex;
|
|
413
|
+
if (separatorIndex === -1) {
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
const key = normalizedToken.slice(0, separatorIndex).trim();
|
|
417
|
+
const value = normalizedToken.slice(separatorIndex + 1).trim();
|
|
418
|
+
if (!key || !value) {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
result[key] = value;
|
|
422
|
+
}
|
|
423
|
+
return result;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function isExplicitEmptyNodeList(raw) {
|
|
427
|
+
if (Array.isArray(raw)) {
|
|
428
|
+
return raw.length === 0;
|
|
429
|
+
}
|
|
430
|
+
if (typeof raw !== "string") {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
return raw.trim() === "[]";
|
|
434
|
+
}
|
|
435
|
+
function resolveRegionShardCandidates(shardMap, region, defaultRegion) {
|
|
436
|
+
const normalizedRegion = normalizeRegionName(region, defaultRegion);
|
|
437
|
+
const directMatch = shardMap.regions[normalizedRegion];
|
|
438
|
+
if (directMatch) {
|
|
439
|
+
return normalizeTargetList(directMatch);
|
|
440
|
+
}
|
|
441
|
+
const defaultMatch = shardMap.regions[defaultRegion];
|
|
442
|
+
if (defaultMatch) {
|
|
443
|
+
return normalizeTargetList(defaultMatch);
|
|
444
|
+
}
|
|
445
|
+
const firstRegion = Object.values(shardMap.regions).find((targets) => targets.length > 0);
|
|
446
|
+
return normalizeTargetList(firstRegion);
|
|
447
|
+
}
|
|
448
|
+
function selectDoName(request, fallbackInstance, targets, affinityKey) {
|
|
449
|
+
const candidates = normalizeTargets(targets, fallbackInstance);
|
|
450
|
+
if (candidates.length <= 1) {
|
|
451
|
+
return candidates[0] ?? fallbackInstance;
|
|
452
|
+
}
|
|
453
|
+
const affinity = resolveAffinityValue(request, affinityKey, fallbackInstance);
|
|
454
|
+
return pickNodeByRendezvousHash(affinity, candidates);
|
|
455
|
+
}
|
|
456
|
+
function resolveAffinityValue(request, affinityKey, fallback) {
|
|
457
|
+
const affinity = resolveHeaderQueryOrCookieValue(request, affinityKey);
|
|
458
|
+
if (affinity && affinity.length > 0) {
|
|
459
|
+
return affinity;
|
|
460
|
+
}
|
|
461
|
+
const authHeader = request.headers.get("Authorization");
|
|
462
|
+
if (authHeader && authHeader.length > 0) {
|
|
463
|
+
return authHeader;
|
|
464
|
+
}
|
|
465
|
+
const clientIp = request.headers.get("CF-Connecting-IP") ?? request.headers.get("X-Forwarded-For");
|
|
466
|
+
if (clientIp && clientIp.length > 0) {
|
|
467
|
+
return clientIp;
|
|
468
|
+
}
|
|
469
|
+
const userAgent = request.headers.get("User-Agent");
|
|
470
|
+
if (userAgent && userAgent.length > 0) {
|
|
471
|
+
return userAgent;
|
|
472
|
+
}
|
|
473
|
+
return fallback;
|
|
474
|
+
}
|
|
475
|
+
function resolveHeaderQueryOrCookieValue(request, key) {
|
|
476
|
+
const url = new URL(request.url);
|
|
477
|
+
const queryValue = url.searchParams.get(key);
|
|
478
|
+
if (queryValue && queryValue.length > 0) {
|
|
479
|
+
return queryValue;
|
|
480
|
+
}
|
|
481
|
+
const headerValue = request.headers.get(key);
|
|
482
|
+
if (headerValue && headerValue.length > 0) {
|
|
483
|
+
return headerValue;
|
|
484
|
+
}
|
|
485
|
+
const cookieValue = readCookieValue(request.headers.get("Cookie"), key);
|
|
486
|
+
if (cookieValue && cookieValue.length > 0) {
|
|
487
|
+
return cookieValue;
|
|
488
|
+
}
|
|
489
|
+
return undefined;
|
|
490
|
+
}
|
|
491
|
+
function readCookieValue(cookieHeader, name) {
|
|
492
|
+
if (!cookieHeader) {
|
|
493
|
+
return undefined;
|
|
494
|
+
}
|
|
495
|
+
const target = name.toLowerCase();
|
|
496
|
+
const entries = cookieHeader.split(";").map((part) => part.trim());
|
|
497
|
+
for (const entry of entries) {
|
|
498
|
+
if (!entry) {
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
const equals = entry.indexOf("=");
|
|
502
|
+
if (equals === -1) {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
const key = entry.slice(0, equals).trim().toLowerCase();
|
|
506
|
+
if (key !== target) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
return decodeURIComponent(entry.slice(equals + 1).trim());
|
|
510
|
+
}
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
function resolveBootstrapShardCandidates(logicalInstance, region, config) {
|
|
514
|
+
return resolveBootstrapSyncShardCandidates(logicalInstance, region, config);
|
|
515
|
+
}
|
|
516
|
+
function normalizeTargets(targets, fallback) {
|
|
517
|
+
const normalized = normalizeTargetList(targets);
|
|
518
|
+
return normalized.length > 0 ? normalized : [fallback];
|
|
519
|
+
}
|
|
520
|
+
function normalizeRegionName(raw, fallback) {
|
|
521
|
+
if (typeof raw !== "string") {
|
|
522
|
+
return fallback;
|
|
523
|
+
}
|
|
524
|
+
const normalized = raw.trim().toLowerCase();
|
|
525
|
+
return normalized.length > 0 ? normalized : fallback;
|
|
526
|
+
}
|
|
527
|
+
function asDurableObjectNamespace(raw) {
|
|
528
|
+
if (!raw || typeof raw !== "object") {
|
|
529
|
+
return undefined;
|
|
530
|
+
}
|
|
531
|
+
const candidate = raw;
|
|
532
|
+
if (typeof candidate.idFromName !== "function" || typeof candidate.get !== "function") {
|
|
533
|
+
return undefined;
|
|
534
|
+
}
|
|
535
|
+
return candidate;
|
|
536
|
+
}
|
|
537
|
+
function asKvNamespace(raw) {
|
|
538
|
+
if (!raw || typeof raw !== "object") {
|
|
539
|
+
return undefined;
|
|
540
|
+
}
|
|
541
|
+
const candidate = raw;
|
|
542
|
+
if (typeof candidate.get !== "function") {
|
|
543
|
+
return undefined;
|
|
544
|
+
}
|
|
545
|
+
return candidate;
|
|
546
|
+
}
|
|
547
|
+
function resolveHeaderLikeKey(raw, fallback) {
|
|
548
|
+
if (typeof raw !== "string") {
|
|
549
|
+
return fallback;
|
|
550
|
+
}
|
|
551
|
+
const trimmed = raw.trim().toLowerCase();
|
|
552
|
+
return trimmed.length > 0 ? trimmed : fallback;
|
|
553
|
+
}
|
|
554
|
+
function parseBoolean(raw, fallback) {
|
|
555
|
+
if (typeof raw === "boolean") {
|
|
556
|
+
return raw;
|
|
557
|
+
}
|
|
558
|
+
if (typeof raw !== "string") {
|
|
559
|
+
return fallback;
|
|
560
|
+
}
|
|
561
|
+
const normalized = raw.trim().toLowerCase();
|
|
562
|
+
if (normalized === "1" || normalized === "true" || normalized === "yes") {
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
if (normalized === "0" || normalized === "false" || normalized === "no") {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
return fallback;
|
|
569
|
+
}
|
|
570
|
+
function resolveShardPrefix(raw, fallback) {
|
|
571
|
+
if (typeof raw !== "string") {
|
|
572
|
+
return fallback;
|
|
573
|
+
}
|
|
574
|
+
const normalized = raw
|
|
575
|
+
.trim()
|
|
576
|
+
.toLowerCase()
|
|
577
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
578
|
+
.replace(/-+/g, "-")
|
|
579
|
+
.replace(/^-|-$/g, "");
|
|
580
|
+
return normalized.length > 0 ? normalized : fallback;
|
|
581
|
+
}
|
|
582
|
+
function parseIntWithMin(raw, fallback, minimum) {
|
|
583
|
+
const value = parseNumber(raw);
|
|
584
|
+
if (value === undefined) {
|
|
585
|
+
return Math.max(minimum, fallback);
|
|
586
|
+
}
|
|
587
|
+
return Math.max(minimum, value);
|
|
588
|
+
}
|
|
589
|
+
function parseOptionalIntWithMin(raw, minimum) {
|
|
590
|
+
const value = parseNumber(raw);
|
|
591
|
+
if (value === undefined) {
|
|
592
|
+
return undefined;
|
|
593
|
+
}
|
|
594
|
+
return Math.max(minimum, value);
|
|
595
|
+
}
|
|
596
|
+
function parseOptionalFloatWithRange(raw, minimum, maximum) {
|
|
597
|
+
const value = parseFloatNumber(raw);
|
|
598
|
+
if (value === undefined) {
|
|
599
|
+
return undefined;
|
|
600
|
+
}
|
|
601
|
+
return clampNumber(value, minimum, maximum);
|
|
602
|
+
}
|
|
603
|
+
function parseFloatWithRange(raw, fallback, minimum, maximum) {
|
|
604
|
+
const value = parseFloatNumber(raw);
|
|
605
|
+
if (value === undefined) {
|
|
606
|
+
return clampNumber(fallback, minimum, maximum);
|
|
607
|
+
}
|
|
608
|
+
return clampNumber(value, minimum, maximum);
|
|
609
|
+
}
|
|
610
|
+
function parseNumber(raw) {
|
|
611
|
+
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
612
|
+
return Math.floor(raw);
|
|
613
|
+
}
|
|
614
|
+
if (typeof raw !== "string") {
|
|
615
|
+
return undefined;
|
|
616
|
+
}
|
|
617
|
+
const trimmed = raw.trim();
|
|
618
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
619
|
+
return undefined;
|
|
620
|
+
}
|
|
621
|
+
return Number(trimmed);
|
|
622
|
+
}
|
|
623
|
+
function parseFloatNumber(raw) {
|
|
624
|
+
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
625
|
+
return raw;
|
|
626
|
+
}
|
|
627
|
+
if (typeof raw !== "string") {
|
|
628
|
+
return undefined;
|
|
629
|
+
}
|
|
630
|
+
const trimmed = raw.trim();
|
|
631
|
+
if (!/^\d+(?:\.\d+)?$/.test(trimmed)) {
|
|
632
|
+
return undefined;
|
|
633
|
+
}
|
|
634
|
+
const parsed = Number(trimmed);
|
|
635
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
636
|
+
}
|
|
637
|
+
function parseScaleProfile(raw, fallback) {
|
|
638
|
+
if (typeof raw !== "string") {
|
|
639
|
+
return fallback;
|
|
640
|
+
}
|
|
641
|
+
const normalized = raw.trim().toLowerCase();
|
|
642
|
+
if (normalized === "minimal" || normalized === "advanced" || normalized === "custom") {
|
|
643
|
+
return normalized;
|
|
644
|
+
}
|
|
645
|
+
return fallback;
|
|
646
|
+
}
|
|
647
|
+
function clampNumber(value, minimum, maximum) {
|
|
648
|
+
return Math.max(minimum, Math.min(maximum, value));
|
|
649
|
+
}
|
|
650
|
+
function pickNodeByRendezvousHash(affinity, nodes) {
|
|
651
|
+
let selected = nodes[0];
|
|
652
|
+
let bestScore = -1;
|
|
653
|
+
for (const nodeId of nodes) {
|
|
654
|
+
const score = fnv1a32(`${affinity}:${nodeId}`);
|
|
655
|
+
if (score > bestScore || (score === bestScore && nodeId > selected)) {
|
|
656
|
+
bestScore = score;
|
|
657
|
+
selected = nodeId;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return selected;
|
|
661
|
+
}
|
|
662
|
+
function fnv1a32(input) {
|
|
663
|
+
let hash = 0x811c9dc5;
|
|
664
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
665
|
+
hash ^= input.charCodeAt(i);
|
|
666
|
+
hash = Math.imul(hash, 0x01000193);
|
|
667
|
+
}
|
|
668
|
+
return hash >>> 0;
|
|
669
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BlobStore } from "@concavejs/core/abstractions";
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a DO stub as a BlobStore.
|
|
4
|
+
* Blobstore methods are prefixed with "blobstore" on the DO to avoid collisions.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createBlobStoreProxy(stub: any): BlobStore;
|
|
7
|
+
/**
|
|
8
|
+
* Wraps a SyscallGateway service binding as a BlobStore.
|
|
9
|
+
* Prepends projectId and instance to each call for multi-tenant routing.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createGatewayBlobStoreProxy(gateway: any, projectId: string, instance: string): BlobStore;
|