@tinycloud/sdk-services 2.0.4 → 2.1.0-beta.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/{BaseService-D9BFm_rV.d.cts → BaseService-BiS6HRwE.d.cts} +18 -1
- package/dist/{BaseService-D9BFm_rV.d.ts → BaseService-BiS6HRwE.d.ts} +18 -1
- package/dist/index.cjs +686 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -3
- package/dist/index.d.ts +100 -3
- package/dist/index.js +685 -0
- package/dist/index.js.map +1 -1
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +1 -1
- package/dist/kv/index.d.ts +1 -1
- package/dist/kv/index.js.map +1 -1
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +1 -1
- package/dist/sql/index.d.ts +1 -1
- package/dist/sql/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -29,6 +29,7 @@ __export(index_exports, {
|
|
|
29
29
|
ErrorCodes: () => ErrorCodes,
|
|
30
30
|
GenericKVResponseSchema: () => GenericKVResponseSchema,
|
|
31
31
|
GenericResultSchema: () => GenericResultSchema,
|
|
32
|
+
HooksService: () => HooksService,
|
|
32
33
|
KVAction: () => KVAction,
|
|
33
34
|
KVListResponseSchema: () => KVListResponseSchema,
|
|
34
35
|
KVListResultSchema: () => KVListResultSchema,
|
|
@@ -361,6 +362,7 @@ var ServiceContext = class {
|
|
|
361
362
|
this._eventHandlers = /* @__PURE__ */ new Map();
|
|
362
363
|
this._abortController = new AbortController();
|
|
363
364
|
this._invoke = config.invoke;
|
|
365
|
+
this._invokeAny = config.invokeAny;
|
|
364
366
|
this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
365
367
|
this._hosts = config.hosts;
|
|
366
368
|
this._session = config.session ?? null;
|
|
@@ -405,6 +407,12 @@ var ServiceContext = class {
|
|
|
405
407
|
get invoke() {
|
|
406
408
|
return this._invoke;
|
|
407
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* Get the multi-resource invoke function when available.
|
|
412
|
+
*/
|
|
413
|
+
get invokeAny() {
|
|
414
|
+
return this._invokeAny;
|
|
415
|
+
}
|
|
408
416
|
/**
|
|
409
417
|
* Get the fetch function for HTTP requests.
|
|
410
418
|
*/
|
|
@@ -1991,6 +1999,683 @@ var DuckDbService = class extends BaseService {
|
|
|
1991
1999
|
};
|
|
1992
2000
|
DuckDbService.serviceName = "duckdb";
|
|
1993
2001
|
|
|
2002
|
+
// src/hooks/HooksService.ts
|
|
2003
|
+
var AsyncQueue = class {
|
|
2004
|
+
constructor() {
|
|
2005
|
+
this.values = [];
|
|
2006
|
+
this.waiters = [];
|
|
2007
|
+
this.closed = false;
|
|
2008
|
+
}
|
|
2009
|
+
[Symbol.asyncIterator]() {
|
|
2010
|
+
return this;
|
|
2011
|
+
}
|
|
2012
|
+
push(value) {
|
|
2013
|
+
if (this.closed) {
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
const waiter = this.waiters.shift();
|
|
2017
|
+
if (waiter) {
|
|
2018
|
+
waiter({ value, done: false });
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
this.values.push(value);
|
|
2022
|
+
}
|
|
2023
|
+
close() {
|
|
2024
|
+
if (this.closed) {
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
this.closed = true;
|
|
2028
|
+
while (this.waiters.length > 0) {
|
|
2029
|
+
const waiter = this.waiters.shift();
|
|
2030
|
+
waiter?.({ value: void 0, done: true });
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
next() {
|
|
2034
|
+
if (this.values.length > 0) {
|
|
2035
|
+
const value = this.values.shift();
|
|
2036
|
+
return Promise.resolve({ value, done: false });
|
|
2037
|
+
}
|
|
2038
|
+
if (this.closed) {
|
|
2039
|
+
return Promise.resolve({ value: void 0, done: true });
|
|
2040
|
+
}
|
|
2041
|
+
return new Promise((resolve) => {
|
|
2042
|
+
this.waiters.push(resolve);
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
};
|
|
2046
|
+
var HooksService = class extends BaseService {
|
|
2047
|
+
constructor(config = {}) {
|
|
2048
|
+
super();
|
|
2049
|
+
this._subscribers = /* @__PURE__ */ new Set();
|
|
2050
|
+
this._refreshChain = Promise.resolve();
|
|
2051
|
+
this._activeSignature = "";
|
|
2052
|
+
this._config = config;
|
|
2053
|
+
}
|
|
2054
|
+
get config() {
|
|
2055
|
+
return this._config;
|
|
2056
|
+
}
|
|
2057
|
+
get host() {
|
|
2058
|
+
return this._config.host ?? this.context.hosts[0];
|
|
2059
|
+
}
|
|
2060
|
+
async *subscribe(subscriptions, options = {}) {
|
|
2061
|
+
if (!this.requireAuth()) {
|
|
2062
|
+
throw new Error("Authentication required for hooks subscription");
|
|
2063
|
+
}
|
|
2064
|
+
if (subscriptions.length === 0) {
|
|
2065
|
+
throw new Error("At least one hook subscription is required");
|
|
2066
|
+
}
|
|
2067
|
+
const normalized = subscriptions.map(normalizeSubscription);
|
|
2068
|
+
const subscriber = {
|
|
2069
|
+
requested: normalized,
|
|
2070
|
+
ttlSeconds: options.ttlSeconds,
|
|
2071
|
+
queue: new AsyncQueue()
|
|
2072
|
+
};
|
|
2073
|
+
this._subscribers.add(subscriber);
|
|
2074
|
+
const abortHandler = () => {
|
|
2075
|
+
this._subscribers.delete(subscriber);
|
|
2076
|
+
subscriber.queue.close();
|
|
2077
|
+
void this.scheduleSharedStreamRefresh();
|
|
2078
|
+
};
|
|
2079
|
+
if (options.signal) {
|
|
2080
|
+
if (options.signal.aborted) {
|
|
2081
|
+
abortHandler();
|
|
2082
|
+
} else {
|
|
2083
|
+
options.signal.addEventListener("abort", abortHandler, { once: true });
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
void this.scheduleSharedStreamRefresh();
|
|
2087
|
+
try {
|
|
2088
|
+
for await (const event of subscriber.queue) {
|
|
2089
|
+
yield event;
|
|
2090
|
+
}
|
|
2091
|
+
} finally {
|
|
2092
|
+
if (options.signal) {
|
|
2093
|
+
options.signal.removeEventListener("abort", abortHandler);
|
|
2094
|
+
}
|
|
2095
|
+
abortHandler();
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
async register(webhook) {
|
|
2099
|
+
if (!this.requireAuth()) {
|
|
2100
|
+
return err(authRequiredError("hooks"));
|
|
2101
|
+
}
|
|
2102
|
+
if (typeof webhook.secret !== "string" || webhook.secret.trim().length === 0) {
|
|
2103
|
+
return err(
|
|
2104
|
+
serviceError(
|
|
2105
|
+
ErrorCodes.INVALID_INPUT,
|
|
2106
|
+
"Webhook secret is required",
|
|
2107
|
+
"hooks",
|
|
2108
|
+
{ meta: { field: "secret" } }
|
|
2109
|
+
)
|
|
2110
|
+
);
|
|
2111
|
+
}
|
|
2112
|
+
try {
|
|
2113
|
+
const response = await this.context.fetch(`${this.host}/hooks/webhooks`, {
|
|
2114
|
+
method: "POST",
|
|
2115
|
+
headers: {
|
|
2116
|
+
...serviceHeadersToRecord(
|
|
2117
|
+
this.createHookHeaders(
|
|
2118
|
+
"tinycloud.hooks/register",
|
|
2119
|
+
buildScopePath(webhook.service, webhook.pathPrefix)
|
|
2120
|
+
)
|
|
2121
|
+
),
|
|
2122
|
+
"content-type": "application/json"
|
|
2123
|
+
},
|
|
2124
|
+
body: JSON.stringify({
|
|
2125
|
+
space: webhook.space,
|
|
2126
|
+
service: webhook.service,
|
|
2127
|
+
pathPrefix: normalizePathPrefix(webhook.pathPrefix),
|
|
2128
|
+
abilities: webhook.abilities ?? [],
|
|
2129
|
+
callbackUrl: webhook.callbackUrl,
|
|
2130
|
+
secret: webhook.secret
|
|
2131
|
+
})
|
|
2132
|
+
});
|
|
2133
|
+
if (!response.ok) {
|
|
2134
|
+
return err(
|
|
2135
|
+
await responseError("hooks", "failed to register webhook", response)
|
|
2136
|
+
);
|
|
2137
|
+
}
|
|
2138
|
+
const data = normalizeWebhookRecord(await response.json());
|
|
2139
|
+
if (!data) {
|
|
2140
|
+
return err(
|
|
2141
|
+
wrapError(
|
|
2142
|
+
"hooks",
|
|
2143
|
+
new Error("Webhook registration response did not include a record")
|
|
2144
|
+
)
|
|
2145
|
+
);
|
|
2146
|
+
}
|
|
2147
|
+
return ok(data);
|
|
2148
|
+
} catch (error) {
|
|
2149
|
+
return err(wrapError("hooks", error));
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
async list(options = {}) {
|
|
2153
|
+
if (!this.requireAuth()) {
|
|
2154
|
+
return err(authRequiredError("hooks"));
|
|
2155
|
+
}
|
|
2156
|
+
try {
|
|
2157
|
+
const query = new URLSearchParams();
|
|
2158
|
+
if (options.space) {
|
|
2159
|
+
query.set("space", options.space);
|
|
2160
|
+
}
|
|
2161
|
+
if (options.service) {
|
|
2162
|
+
query.set("service", options.service);
|
|
2163
|
+
}
|
|
2164
|
+
if (options.pathPrefix) {
|
|
2165
|
+
const normalizedPrefix = normalizePathPrefix(options.pathPrefix);
|
|
2166
|
+
if (normalizedPrefix) {
|
|
2167
|
+
query.set("prefix", normalizedPrefix);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
const response = await this.context.fetch(
|
|
2171
|
+
`${this.host}/hooks/webhooks${query.size > 0 ? `?${query.toString()}` : ""}`,
|
|
2172
|
+
{
|
|
2173
|
+
method: "GET",
|
|
2174
|
+
headers: serviceHeadersToRecord(
|
|
2175
|
+
this.createHookHeaders(
|
|
2176
|
+
"tinycloud.hooks/list",
|
|
2177
|
+
options.service ? buildScopePath(options.service, options.pathPrefix) : "webhooks"
|
|
2178
|
+
)
|
|
2179
|
+
)
|
|
2180
|
+
}
|
|
2181
|
+
);
|
|
2182
|
+
if (!response.ok) {
|
|
2183
|
+
return err(
|
|
2184
|
+
await responseError("hooks", "failed to list webhooks", response)
|
|
2185
|
+
);
|
|
2186
|
+
}
|
|
2187
|
+
const payload = await response.json();
|
|
2188
|
+
const records = normalizeWebhookRecordList(payload);
|
|
2189
|
+
if (!records) {
|
|
2190
|
+
return err(
|
|
2191
|
+
wrapError(
|
|
2192
|
+
"hooks",
|
|
2193
|
+
new Error("Webhook list response did not include records")
|
|
2194
|
+
)
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
return ok(records);
|
|
2198
|
+
} catch (error) {
|
|
2199
|
+
return err(wrapError("hooks", error));
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
async unregister(id, options = {}) {
|
|
2203
|
+
if (!this.requireAuth()) {
|
|
2204
|
+
return err(authRequiredError("hooks"));
|
|
2205
|
+
}
|
|
2206
|
+
try {
|
|
2207
|
+
const response = await this.context.fetch(
|
|
2208
|
+
`${this.host}/hooks/webhooks/${encodeURIComponent(id)}`,
|
|
2209
|
+
{
|
|
2210
|
+
method: "DELETE",
|
|
2211
|
+
headers: serviceHeadersToRecord(
|
|
2212
|
+
this.createHookHeaders(
|
|
2213
|
+
"tinycloud.hooks/unregister",
|
|
2214
|
+
options.target ? buildScopePath(
|
|
2215
|
+
options.target.service,
|
|
2216
|
+
options.target.pathPrefix
|
|
2217
|
+
) : `webhooks/${id}`
|
|
2218
|
+
)
|
|
2219
|
+
)
|
|
2220
|
+
}
|
|
2221
|
+
);
|
|
2222
|
+
if (!response.ok) {
|
|
2223
|
+
return err(
|
|
2224
|
+
await responseError(
|
|
2225
|
+
"hooks",
|
|
2226
|
+
"failed to unregister webhook",
|
|
2227
|
+
response
|
|
2228
|
+
)
|
|
2229
|
+
);
|
|
2230
|
+
}
|
|
2231
|
+
return ok(void 0);
|
|
2232
|
+
} catch (error) {
|
|
2233
|
+
return err(wrapError("hooks", error));
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
async scheduleSharedStreamRefresh() {
|
|
2237
|
+
this._refreshChain = this._refreshChain.then(() => this.refreshSharedStream()).catch(() => void 0);
|
|
2238
|
+
await this._refreshChain;
|
|
2239
|
+
}
|
|
2240
|
+
async refreshSharedStream() {
|
|
2241
|
+
if (!this.requireAuth() || this._subscribers.size === 0) {
|
|
2242
|
+
this.abortSharedStream();
|
|
2243
|
+
this._activeSignature = "";
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
const state = this.collectSharedStreamState();
|
|
2247
|
+
if (state.signature !== this._activeSignature) {
|
|
2248
|
+
this._activeSignature = state.signature;
|
|
2249
|
+
this.abortSharedStream();
|
|
2250
|
+
}
|
|
2251
|
+
if (!this._sharedStreamTask) {
|
|
2252
|
+
this._sharedStreamTask = this.runSharedStream(state).catch((error) => {
|
|
2253
|
+
if (!isAbortError(error)) {
|
|
2254
|
+
throw error;
|
|
2255
|
+
}
|
|
2256
|
+
}).finally(() => {
|
|
2257
|
+
this._sharedStreamTask = void 0;
|
|
2258
|
+
this._sharedStreamAbort = void 0;
|
|
2259
|
+
if (this._subscribers.size > 0) {
|
|
2260
|
+
void this.scheduleSharedStreamRefresh();
|
|
2261
|
+
}
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
collectSharedStreamState() {
|
|
2266
|
+
const merged = /* @__PURE__ */ new Map();
|
|
2267
|
+
const ttlCandidates = [];
|
|
2268
|
+
for (const subscriber of this._subscribers) {
|
|
2269
|
+
if (typeof subscriber.ttlSeconds === "number") {
|
|
2270
|
+
ttlCandidates.push(subscriber.ttlSeconds);
|
|
2271
|
+
}
|
|
2272
|
+
for (const subscription of subscriber.requested) {
|
|
2273
|
+
merged.set(subscriptionSignature(subscription), subscription);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
const subscriptions = [...merged.values()].sort(
|
|
2277
|
+
(left, right) => subscriptionSignature(left).localeCompare(subscriptionSignature(right))
|
|
2278
|
+
);
|
|
2279
|
+
const ttlSeconds = ttlCandidates.length > 0 ? Math.min(...ttlCandidates) : void 0;
|
|
2280
|
+
const signature = JSON.stringify({
|
|
2281
|
+
subscriptions: subscriptions.map(subscriptionSignature),
|
|
2282
|
+
ttlSeconds
|
|
2283
|
+
});
|
|
2284
|
+
return {
|
|
2285
|
+
subscriptions,
|
|
2286
|
+
ttlSeconds,
|
|
2287
|
+
signature
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2290
|
+
async runSharedStream(state) {
|
|
2291
|
+
const abortController = new AbortController();
|
|
2292
|
+
this._sharedStreamAbort = abortController;
|
|
2293
|
+
try {
|
|
2294
|
+
const host = this._config.host ?? this.context.hosts[0];
|
|
2295
|
+
const ticketResponse = await this.mintHookTicket(
|
|
2296
|
+
state.subscriptions,
|
|
2297
|
+
state.ttlSeconds,
|
|
2298
|
+
abortController.signal
|
|
2299
|
+
);
|
|
2300
|
+
const streamResponse = await this.openHookStream(
|
|
2301
|
+
host,
|
|
2302
|
+
ticketResponse.ticket,
|
|
2303
|
+
abortController.signal
|
|
2304
|
+
);
|
|
2305
|
+
for await (const message of parseSseStream(
|
|
2306
|
+
streamResponse.body,
|
|
2307
|
+
abortController.signal
|
|
2308
|
+
)) {
|
|
2309
|
+
if (!message.data) {
|
|
2310
|
+
continue;
|
|
2311
|
+
}
|
|
2312
|
+
const event = parseHookEvent(message);
|
|
2313
|
+
for (const subscriber of this._subscribers) {
|
|
2314
|
+
if (matchesAnySubscription(event, subscriber.requested)) {
|
|
2315
|
+
subscriber.queue.push(event);
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
} finally {
|
|
2320
|
+
if (this._sharedStreamAbort === abortController) {
|
|
2321
|
+
this._sharedStreamAbort = void 0;
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
abortSharedStream() {
|
|
2326
|
+
this._sharedStreamAbort?.abort();
|
|
2327
|
+
}
|
|
2328
|
+
createHookHeaders(action, path) {
|
|
2329
|
+
return this.context.invoke(this.session, "hooks", path, action);
|
|
2330
|
+
}
|
|
2331
|
+
async mintHookTicket(subscriptions, ttlSeconds, signal) {
|
|
2332
|
+
const host = this._config.host ?? this.context.hosts[0];
|
|
2333
|
+
const headers = this.createInvokeHeaders(subscriptions);
|
|
2334
|
+
const ticketResponse = await this.context.fetch(`${host}/hooks/tickets`, {
|
|
2335
|
+
method: "POST",
|
|
2336
|
+
headers: {
|
|
2337
|
+
...serviceHeadersToRecord(headers),
|
|
2338
|
+
"content-type": "application/json"
|
|
2339
|
+
},
|
|
2340
|
+
body: JSON.stringify({
|
|
2341
|
+
subscriptions,
|
|
2342
|
+
ttlSeconds
|
|
2343
|
+
}),
|
|
2344
|
+
signal
|
|
2345
|
+
});
|
|
2346
|
+
if (!ticketResponse.ok) {
|
|
2347
|
+
throw await responseError(
|
|
2348
|
+
"hooks",
|
|
2349
|
+
"failed to mint hook ticket",
|
|
2350
|
+
ticketResponse
|
|
2351
|
+
);
|
|
2352
|
+
}
|
|
2353
|
+
const ticketJson = await ticketResponse.json();
|
|
2354
|
+
if (!ticketJson?.ticket) {
|
|
2355
|
+
throw new Error("Hook ticket response did not include a ticket");
|
|
2356
|
+
}
|
|
2357
|
+
return ticketJson;
|
|
2358
|
+
}
|
|
2359
|
+
async openHookStream(host, ticket, signal) {
|
|
2360
|
+
const streamResponse = await this.context.fetch(
|
|
2361
|
+
`${host}/hooks/events?ticket=${encodeURIComponent(ticket)}`,
|
|
2362
|
+
{
|
|
2363
|
+
method: "GET",
|
|
2364
|
+
headers: { accept: "text/event-stream" },
|
|
2365
|
+
signal
|
|
2366
|
+
}
|
|
2367
|
+
);
|
|
2368
|
+
if (!streamResponse.ok) {
|
|
2369
|
+
throw await responseError(
|
|
2370
|
+
"hooks",
|
|
2371
|
+
"failed to open hook stream",
|
|
2372
|
+
streamResponse
|
|
2373
|
+
);
|
|
2374
|
+
}
|
|
2375
|
+
return streamResponse;
|
|
2376
|
+
}
|
|
2377
|
+
createInvokeHeaders(subscriptions) {
|
|
2378
|
+
const entries = subscriptions.map((subscription) => ({
|
|
2379
|
+
spaceId: subscription.space,
|
|
2380
|
+
service: "hooks",
|
|
2381
|
+
path: subscription.pathPrefix ? `${subscription.service}/${subscription.pathPrefix}` : subscription.service,
|
|
2382
|
+
action: "tinycloud.hooks/subscribe"
|
|
2383
|
+
}));
|
|
2384
|
+
if (this.context.invokeAny) {
|
|
2385
|
+
return this.context.invokeAny(this.session, entries);
|
|
2386
|
+
}
|
|
2387
|
+
if (entries.length === 1) {
|
|
2388
|
+
const entry = entries[0];
|
|
2389
|
+
return this.context.invoke(
|
|
2390
|
+
this.session,
|
|
2391
|
+
entry.service,
|
|
2392
|
+
entry.path,
|
|
2393
|
+
entry.action
|
|
2394
|
+
);
|
|
2395
|
+
}
|
|
2396
|
+
throw new Error(
|
|
2397
|
+
"This SDK runtime does not support multi-scope hook invocations"
|
|
2398
|
+
);
|
|
2399
|
+
}
|
|
2400
|
+
};
|
|
2401
|
+
HooksService.serviceName = "hooks";
|
|
2402
|
+
function buildScopePath(service, pathPrefix) {
|
|
2403
|
+
const normalized = normalizePathPrefix(pathPrefix);
|
|
2404
|
+
return normalized ? `${service}/${normalized}` : service;
|
|
2405
|
+
}
|
|
2406
|
+
function normalizeSubscription(subscription) {
|
|
2407
|
+
return {
|
|
2408
|
+
...subscription,
|
|
2409
|
+
pathPrefix: normalizePathPrefix(subscription.pathPrefix),
|
|
2410
|
+
abilities: subscription.abilities ?? []
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
function subscriptionSignature(subscription) {
|
|
2414
|
+
return JSON.stringify({
|
|
2415
|
+
space: subscription.space,
|
|
2416
|
+
service: subscription.service,
|
|
2417
|
+
pathPrefix: subscription.pathPrefix ?? "",
|
|
2418
|
+
abilities: [...subscription.abilities ?? []].sort()
|
|
2419
|
+
});
|
|
2420
|
+
}
|
|
2421
|
+
function matchesAnySubscription(event, subscriptions) {
|
|
2422
|
+
return subscriptions.some(
|
|
2423
|
+
(subscription) => matchesSubscription(event, subscription)
|
|
2424
|
+
);
|
|
2425
|
+
}
|
|
2426
|
+
function matchesSubscription(event, subscription) {
|
|
2427
|
+
if (event.space !== subscription.space) {
|
|
2428
|
+
return false;
|
|
2429
|
+
}
|
|
2430
|
+
if (event.service !== subscription.service) {
|
|
2431
|
+
return false;
|
|
2432
|
+
}
|
|
2433
|
+
if (subscription.pathPrefix) {
|
|
2434
|
+
const prefix = subscription.pathPrefix.endsWith("/") ? subscription.pathPrefix : `${subscription.pathPrefix}/`;
|
|
2435
|
+
if (event.path && event.path !== subscription.pathPrefix && !event.path.startsWith(prefix)) {
|
|
2436
|
+
return false;
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
const abilities = subscription.abilities ?? [];
|
|
2440
|
+
if (abilities.length > 0 && !abilities.includes(event.ability)) {
|
|
2441
|
+
return false;
|
|
2442
|
+
}
|
|
2443
|
+
return true;
|
|
2444
|
+
}
|
|
2445
|
+
function normalizePathPrefix(pathPrefix) {
|
|
2446
|
+
if (!pathPrefix) {
|
|
2447
|
+
return void 0;
|
|
2448
|
+
}
|
|
2449
|
+
const trimmed = pathPrefix.replace(/^\/+|\/+$/g, "");
|
|
2450
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
2451
|
+
}
|
|
2452
|
+
function serviceHeadersToRecord(headers) {
|
|
2453
|
+
if (Array.isArray(headers)) {
|
|
2454
|
+
return Object.fromEntries(headers);
|
|
2455
|
+
}
|
|
2456
|
+
return { ...headers };
|
|
2457
|
+
}
|
|
2458
|
+
async function responseError(service, message, response) {
|
|
2459
|
+
let detail = response.statusText;
|
|
2460
|
+
try {
|
|
2461
|
+
const text = await response.text();
|
|
2462
|
+
if (text) {
|
|
2463
|
+
detail = text;
|
|
2464
|
+
}
|
|
2465
|
+
} catch {
|
|
2466
|
+
}
|
|
2467
|
+
return wrapError(
|
|
2468
|
+
service,
|
|
2469
|
+
new Error(`${message}: ${response.status} ${detail}`)
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2472
|
+
function isAbortError(error) {
|
|
2473
|
+
return error instanceof DOMException && error.name === "AbortError";
|
|
2474
|
+
}
|
|
2475
|
+
async function* parseSseStream(body, signal) {
|
|
2476
|
+
if (!body) {
|
|
2477
|
+
throw new Error("Hook stream response does not expose a readable body");
|
|
2478
|
+
}
|
|
2479
|
+
const decoder = new TextDecoder();
|
|
2480
|
+
let buffer = "";
|
|
2481
|
+
for await (const chunk of readBodyChunks(body, signal)) {
|
|
2482
|
+
buffer += decoder.decode(chunk, { stream: true }).replace(/\r\n/g, "\n");
|
|
2483
|
+
let separatorIndex = buffer.indexOf("\n\n");
|
|
2484
|
+
while (separatorIndex >= 0) {
|
|
2485
|
+
const rawEvent = buffer.slice(0, separatorIndex);
|
|
2486
|
+
buffer = buffer.slice(separatorIndex + 2);
|
|
2487
|
+
const parsed = parseSseEvent(rawEvent);
|
|
2488
|
+
if (parsed) {
|
|
2489
|
+
yield parsed;
|
|
2490
|
+
}
|
|
2491
|
+
separatorIndex = buffer.indexOf("\n\n");
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
buffer += decoder.decode();
|
|
2495
|
+
const trailing = parseSseEvent(buffer.trim());
|
|
2496
|
+
if (trailing) {
|
|
2497
|
+
yield trailing;
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
async function* readBodyChunks(body, signal) {
|
|
2501
|
+
const asyncIterable = body;
|
|
2502
|
+
if (typeof asyncIterable?.[Symbol.asyncIterator] === "function") {
|
|
2503
|
+
for await (const chunk of asyncIterable) {
|
|
2504
|
+
if (signal?.aborted) {
|
|
2505
|
+
break;
|
|
2506
|
+
}
|
|
2507
|
+
yield chunk;
|
|
2508
|
+
}
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2511
|
+
const stream = body;
|
|
2512
|
+
if (typeof stream.getReader !== "function") {
|
|
2513
|
+
throw new Error("Unsupported hook stream body type");
|
|
2514
|
+
}
|
|
2515
|
+
const reader = stream.getReader();
|
|
2516
|
+
try {
|
|
2517
|
+
while (!signal?.aborted) {
|
|
2518
|
+
const { done, value } = await reader.read();
|
|
2519
|
+
if (done) {
|
|
2520
|
+
break;
|
|
2521
|
+
}
|
|
2522
|
+
if (value) {
|
|
2523
|
+
yield value;
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
} finally {
|
|
2527
|
+
try {
|
|
2528
|
+
await reader.cancel?.();
|
|
2529
|
+
} catch {
|
|
2530
|
+
}
|
|
2531
|
+
reader.releaseLock?.();
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
function parseSseEvent(rawEvent) {
|
|
2535
|
+
if (!rawEvent) {
|
|
2536
|
+
return null;
|
|
2537
|
+
}
|
|
2538
|
+
let event = "message";
|
|
2539
|
+
let id;
|
|
2540
|
+
const dataLines = [];
|
|
2541
|
+
for (const line of rawEvent.split("\n")) {
|
|
2542
|
+
if (!line || line.startsWith(":")) {
|
|
2543
|
+
continue;
|
|
2544
|
+
}
|
|
2545
|
+
const [field, ...rest] = line.split(":");
|
|
2546
|
+
const value = rest.join(":").replace(/^ /, "");
|
|
2547
|
+
switch (field) {
|
|
2548
|
+
case "event":
|
|
2549
|
+
event = value;
|
|
2550
|
+
break;
|
|
2551
|
+
case "id":
|
|
2552
|
+
id = value;
|
|
2553
|
+
break;
|
|
2554
|
+
case "data":
|
|
2555
|
+
dataLines.push(value);
|
|
2556
|
+
break;
|
|
2557
|
+
default:
|
|
2558
|
+
break;
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
if (dataLines.length === 0) {
|
|
2562
|
+
return null;
|
|
2563
|
+
}
|
|
2564
|
+
return {
|
|
2565
|
+
event,
|
|
2566
|
+
id,
|
|
2567
|
+
data: dataLines.join("\n")
|
|
2568
|
+
};
|
|
2569
|
+
}
|
|
2570
|
+
function parseHookEvent(message) {
|
|
2571
|
+
const parsed = JSON.parse(message.data);
|
|
2572
|
+
return {
|
|
2573
|
+
type: "write",
|
|
2574
|
+
id: parsed.id ?? message.id ?? "",
|
|
2575
|
+
space: parsed.space ?? "",
|
|
2576
|
+
service: parsed.service ?? "",
|
|
2577
|
+
ability: parsed.ability ?? "",
|
|
2578
|
+
path: parsed.path,
|
|
2579
|
+
actor: parsed.actor ?? "",
|
|
2580
|
+
epoch: parsed.epoch ?? "",
|
|
2581
|
+
eventIndex: parsed.eventIndex ?? 0,
|
|
2582
|
+
timestamp: parsed.timestamp ?? ""
|
|
2583
|
+
};
|
|
2584
|
+
}
|
|
2585
|
+
function normalizeWebhookRecord(data) {
|
|
2586
|
+
if (!data || typeof data !== "object") {
|
|
2587
|
+
return null;
|
|
2588
|
+
}
|
|
2589
|
+
const record = isRecordContainer(data);
|
|
2590
|
+
const candidate = pickWebhookRecord(record) ?? normalizeWebhookRecord(record.webhook) ?? normalizeWebhookRecord(record.hook) ?? normalizeWebhookRecord(record.subscription) ?? normalizeWebhookRecord(record.data);
|
|
2591
|
+
return candidate ?? null;
|
|
2592
|
+
}
|
|
2593
|
+
function normalizeWebhookRecordList(data) {
|
|
2594
|
+
if (Array.isArray(data)) {
|
|
2595
|
+
const records = data.map((entry) => normalizeWebhookRecord(entry)).filter((entry) => entry !== null);
|
|
2596
|
+
return records;
|
|
2597
|
+
}
|
|
2598
|
+
if (!data || typeof data !== "object") {
|
|
2599
|
+
return null;
|
|
2600
|
+
}
|
|
2601
|
+
const record = isRecordContainer(data);
|
|
2602
|
+
const nested = maybeRecordArray(record.webhooks) ?? maybeRecordArray(record.subscriptions) ?? maybeRecordArray(record.hooks) ?? maybeRecordArray(record.data);
|
|
2603
|
+
if (nested) {
|
|
2604
|
+
return nested;
|
|
2605
|
+
}
|
|
2606
|
+
const single = pickWebhookRecord(record);
|
|
2607
|
+
return single ? [single] : null;
|
|
2608
|
+
}
|
|
2609
|
+
function maybeRecordArray(value) {
|
|
2610
|
+
if (!Array.isArray(value)) {
|
|
2611
|
+
return null;
|
|
2612
|
+
}
|
|
2613
|
+
const records = value.map((entry) => normalizeWebhookRecord(entry)).filter((entry) => entry !== null);
|
|
2614
|
+
return records;
|
|
2615
|
+
}
|
|
2616
|
+
function pickWebhookRecord(value) {
|
|
2617
|
+
const id = stringField(value, "id");
|
|
2618
|
+
const space = stringField(value, "space") ?? stringField(value, "spaceId");
|
|
2619
|
+
const service = stringField(value, "service");
|
|
2620
|
+
const callbackUrl = stringField(value, "callbackUrl") ?? stringField(value, "callback_url");
|
|
2621
|
+
if (!id || !space || !service || !callbackUrl) {
|
|
2622
|
+
return null;
|
|
2623
|
+
}
|
|
2624
|
+
return {
|
|
2625
|
+
id,
|
|
2626
|
+
space,
|
|
2627
|
+
service,
|
|
2628
|
+
pathPrefix: optionalStringField(value, "pathPrefix") ?? optionalStringField(value, "path_prefix"),
|
|
2629
|
+
abilities: stringArrayField(value, "abilities") ?? parsedStringArrayField(value, "abilitiesJson") ?? parsedStringArrayField(value, "abilities_json"),
|
|
2630
|
+
callbackUrl,
|
|
2631
|
+
active: booleanField(value, "active") ?? true,
|
|
2632
|
+
createdAt: stringField(value, "createdAt") ?? stringField(value, "created_at") ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2633
|
+
subscriberDid: optionalStringField(value, "subscriberDid") ?? optionalStringField(value, "subscriber_did")
|
|
2634
|
+
};
|
|
2635
|
+
}
|
|
2636
|
+
function isRecordContainer(value) {
|
|
2637
|
+
return value;
|
|
2638
|
+
}
|
|
2639
|
+
function stringField(value, key) {
|
|
2640
|
+
const field = value[key];
|
|
2641
|
+
return typeof field === "string" ? field : void 0;
|
|
2642
|
+
}
|
|
2643
|
+
function optionalStringField(value, key) {
|
|
2644
|
+
return stringField(value, key);
|
|
2645
|
+
}
|
|
2646
|
+
function booleanField(value, key) {
|
|
2647
|
+
const field = value[key];
|
|
2648
|
+
return typeof field === "boolean" ? field : void 0;
|
|
2649
|
+
}
|
|
2650
|
+
function stringArrayField(value, key) {
|
|
2651
|
+
const field = value[key];
|
|
2652
|
+
if (!Array.isArray(field)) {
|
|
2653
|
+
return void 0;
|
|
2654
|
+
}
|
|
2655
|
+
const strings = field.filter(
|
|
2656
|
+
(item) => typeof item === "string"
|
|
2657
|
+
);
|
|
2658
|
+
return strings.length === field.length ? strings : void 0;
|
|
2659
|
+
}
|
|
2660
|
+
function parsedStringArrayField(value, key) {
|
|
2661
|
+
const field = value[key];
|
|
2662
|
+
if (typeof field !== "string") {
|
|
2663
|
+
return void 0;
|
|
2664
|
+
}
|
|
2665
|
+
try {
|
|
2666
|
+
const parsed = JSON.parse(field);
|
|
2667
|
+
if (!Array.isArray(parsed)) {
|
|
2668
|
+
return void 0;
|
|
2669
|
+
}
|
|
2670
|
+
const strings = parsed.filter(
|
|
2671
|
+
(item) => typeof item === "string"
|
|
2672
|
+
);
|
|
2673
|
+
return strings.length === parsed.length ? strings : void 0;
|
|
2674
|
+
} catch {
|
|
2675
|
+
return void 0;
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
|
|
1994
2679
|
// src/quota/TinyCloudQuota.ts
|
|
1995
2680
|
var TinyCloudQuota = class {
|
|
1996
2681
|
constructor(config = {}) {
|
|
@@ -3170,6 +3855,7 @@ function createVaultCrypto(wasm) {
|
|
|
3170
3855
|
ErrorCodes,
|
|
3171
3856
|
GenericKVResponseSchema,
|
|
3172
3857
|
GenericResultSchema,
|
|
3858
|
+
HooksService,
|
|
3173
3859
|
KVAction,
|
|
3174
3860
|
KVListResponseSchema,
|
|
3175
3861
|
KVListResultSchema,
|